Make bundler fast again in Docker Compose

You read thoughtbot’s Rails on Docker article, moved your entire development environment into Docker containers via Docker Compose, then realized everytime you want to bump or add a gem to your project you have to rebuild the rails container and wait forever to run bundle from scratch.

With Docker data containers and a little hack in the docker-compose.yml file you don’t have to waste so much time running bundler from scratch everytime you update the Gemfile.

Set the bundler cache path in the Dockerfile

Building on thoughtbot’s Dockerfile example, set the BUNDLE_PATH environment variable to store the bundler cache outside of the rails project path:

FROM ruby:2.2.0

RUN apt-get update -qq && apt-get install -y build-essential

# for postgres
RUN apt-get install -y libpq-dev

# for nokogiri
RUN apt-get install -y libxml2-dev libxslt1-dev

# for capybara-webkit
RUN apt-get install -y libqt4-webkit libqt4-dev xvfb

# for a JS runtime
RUN apt-get install -y nodejs

ENV APP_HOME /myapp
RUN mkdir $APP_HOME
WORKDIR $APP_HOME

ADD Gemfile* $APP_HOME/

# --- Add this to your Dockerfile ---
ENV BUNDLE_GEMFILE=$APP_HOME/Gemfile \
  BUNDLE_JOBS=2 \
  BUNDLE_PATH=/bundle

RUN bundle install

ADD . $APP_HOME

Create a data volume to persist the bundler cache between docker container runs

Now add a bundle container to the docker-compose.yml file. This may be the fig.yml file if you haven’t upgraded from Fig 1.0 to Docker Compose 1.1 yet.

web:
  build: .
  command: bin/rails server --port 3000 --binding 0.0.0.0
  ports:
    - "3000:3000"
  links:
    - db
  volumes:
    - .:/myapp
  # This tells the web container to mount the `bundle` images'
  # /bundle volume to the `web` containers /bundle path.
  volumes_from:
    - bundle

# --- Add this to your fig.yml or docker-compose.yml file ---
bundle:
  # 'image' will vary depending on your docker-compose 
  # project name. You may need to run `docker-compose build web`
  # before this works.
  image: myapp_web
  command: echo I'm a little data container, short and stout...
  volumes:
    - /bundle

Now when the web container runs, it mounts the bundle container’s /bundle path that we specified in the BUNDLE_GEMFILE environment variable.

Update the bundle

To update the project’s bundle execute:

$ docker-compose run web bundle

Bundler picks up the existing bundler cache from the /bundle volume on the bundle container and updates all the gems. After the bundle container exits, the /bundle folder sticks around because we told Docker to persist it to the host machine via the volumes directive. The web container picks up the changes from the /bundle path without needing to rebuild everything from scratch.

If you intend on deploying the web container you’ll need to rebuild it without linking the bundle data container, but that’s not a big deal since you’d have to rebuild the container anyway with your code changes.

My oath: only run development environments in Docker containers

A few months back decided that I’d only run development environments inside Docker containers so that I’d Learn The Hard Way™ all of the workflow pitfalls before rolling it out to the rest of the crew at Poll Everywhere. I’ve got a few more articles in the works that uncover more Docker development environment naunces that I’ll tweet from @bradgessler.

Bg