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 update a gem you have to run bundle from scratch and it takes forever.

With Docker data containers and a clever hack in the docker-compose.yml file you don't have to wait around for bundler to build from scratch everytime you bump a gem.

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 your project folder:

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

Store the bundler cache in a Docker data volume so it persists between docker runs

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

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`
  # before this works.
  image: myapp_web
  command: echo I'm a little data container, short and stout...
  volumes:
    - /bundle

When the web container runs it mounts the bundle container's /bundle folder. Between runs the bundle container's /bundle directory is persisted so you don't have to rebuild your entire bundle from scratch everytime you add or upgrade a gem from the web container.

My oath: only run development environments in Docker containers

A few months back and took an oath to only run development environments inside Docker containers so that I'd Learn The Hard Way™ all of its 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 discuss the nuances of Docker in a development environment that I'll tweet about @bradgessler. Stay tuned!