A More Flexible Dockerfile for Rails

Andrew Tomaka
2 min readMar 1, 2017

--

One of my primary motivations for working with Docker was creating a single artifact that I could toss into any environment. It has been fantastic at this. I can throw together a simple Dockerfile that will build my Rails application as an image for production in about five minutes.

Except now that when I need to run the application’s test suite, I do not have the dependencies I need. That Dockerfile might look something like this.

Many people decide to include both of these Dockerfiles in their repository as Dockerfile and Dockerfile.dev. This works perfectly fine. But now we have a production Dockerfile that never gets used during development. Of course, it is going through at least one staging environment (hopefully) but it would be nice if we had a little more testing against it.

Much like Docker provides us the ability to have a single artifact to move from system to system, I wanted to have a single Dockerfile shared between all environments. Luckily, Docker provides us with build arguments. A build argument allows us to specify a variable when building the image and then use that variable inside our Dockerfile.

In our current Rails Dockerfile, we have two primary differences between our environments:

  1. The gem groups that are installed
  2. The environment that the application runs as

Bundler’s BUNDLE_WITHOUT allows us to specify the gem groups to skip via an environment variable making both of these resolvable through environment configuration. Using this, our shared Dockerfile could look like this:

The secret sauce here is ARG BUNDLE_WITHOUT=test:development. Running docker build -t rails-app . will use the default value provided for the BUNDLE_WITHOUT build argument, test:development, and a production Docker image will be built. And if we specify the appropriate build arguments, we can generate an image suitable for development.

docker build -t rails-app --build-arg BUNDLE_WITHOUT= --build-arg RAILS_ENV=development .

will generate our Docker image with all test and development dependencies available. Typing this for building in development would get pretty tedious so we can use docker-compose to make it easier

Now, docker-compose up -d is all we need in development to both build and launch our development image.

Finally, we have a single Dockerfile that can be used to build an image for any of our application’s needs. Of course, there are some trade-offs. For example, build time in development will suffer in some cases. But I have found only maintaining a single Dockerfile to be worth these costs.

Have another way to deal with this issue? Please share!

--

--

Andrew Tomaka

Software Developer and systems administrator at Michigan State University.