I got to spend some time playing with Docker and Rails, and really enjoyed it. Here’s a list of problems I had while at it, and how I solved them.
Introduction
By the end of 2015, I got to spend a full week experimenting with Docker, as part of the Cookies Labs plan. My goals were to get to know the Docker system and philosophy, dockerize an existing project and evaluate the viability of adopting it for new projects right away.
So, I guess this is not your usual “dockerize your rails app” tutorial, there are quite a few of those around the Internet if you google around. What I intend to achieve with this post is to explain and solve some of the problems I found while following some of the aforementioned posts.
Getting the app image to build faster
Many tutorials out there for Rails apps tell you to have something like this in your dockerfile:
# ...
WORKDIR /app
COPY . ./
RUN gem install bundler && bundle install
# ...
This will run bundle install from scratch everytime you build the image if any file in your app has changed, even if Gemfile or Gemfile.lock haven’t because the caché is invalidated.
However, more and more new posts about starting with Docker and Rails cover this steps, and several ways of speeding up this process have appeared:
- COPY the Gemfile and Gemile.lock first, RUN bundle install, then copy the rest of the app. Example
- Similar to (1), COPY both files to a tmp folder, RUN bundle install, then COPY the rest of the app. Example
- Have a bundle cache as a separate service if you work with docker-compose. A working set of files would be like:
A working Dockerfile for this technique would be:
FROM ruby:2.2.1
MAINTAINER Julio Antequera Galiano <[email protected]>
RUN apt-get update && apt-get install -y \
build-essential
ENV app /app
RUN mkdir $app
WORKDIR $app
ENV BUNDLE_PATH /bundle
WORKDIR $app
COPY . ./
Our docker-compose.yml
app:
build: .
command: ./script/start.sh
volumes:
- .:/app
ports:
- "3000:3000"
links:
- db
volumes_from:
- bundle_cache
db:
image: postgres:9.4
ports:
- "5432"
bundle_cache:
image: busybox
volumes:
- /bundle_cache
And our start.sh
#!/bin/bash
bundle check || bundle install
As you see, the bundle_cache service is using busybox. On the comments for the post linked above, someone mentioned the possibility of some gems having problems because their native extensions would have been compiled against a different base system (busybox) than the one our app is using (debian). During my tests I didn’t find any problems with this, but it might be something to take into consideration.
Any of these three solutions will speed up your build time generously. The third one seems more elegant, if you want to take the cool EaaS (Everything-as-a-Service) approach.
Getting your Capybara Specs to pass
My acceptance tests were failing, and I recurred to this Thoughtbot’s post to try and fix them. However, the configuration they provided for Headless in the rails_helper
didn’t make it for me. Most probably it was caused by a clash with my project’s current specs config, starting from. In any case, here’s how I got it working, given that you already have the xvfb
package in your apt-get
line and the headless
gem installed:
RSpec.configure do |config|
config.include FactoryGirl::Syntax::Methods
config.before(:suite) do
@headless = Headless.new
@headless.start
end
config.after(:suite) do
@headless.destroy
end
Consider moving to puma and foreman
As of Rails 5, puma is the default server for Rails apps, but if you’re dockerizing a Rails 4 (or even older!) app, you’ll find that good old Webrick is really slow behind Docker. Switching to puma and foreman really helps overcome this.
Whitelist your IP for the web console
To avoid warnings about blocked ip’s in your server log and get the Rails web console working again, In your config/environments/development.rb
, add the following line:
config.web_console.whitelisted_ips = '192.168.99.100' # your docker machine IP
Keep your html emails working
If you are sending emails with embedded styles and images, you might find they’re not working properly from inside docker. On your mailer configuration, you need to change the ‘localhost’ by your IP:
config.action_mailer.raise_delivery_errors = true # not needed but helpful
config.action_mailer.default_url_options = { host: '192.168.99.100', port: 3000 }
config.action_mailer.asset_host = "http://192.168.99.100:3000"
Guard won’t work 🙁
I couldn’t get Guard to work out of the box. After researching a bit, I found a couple of interesting posts:
The first link mentions docker-osx-dev
a script that enforces file sync between the host and the container. It was designed to work with boot2docker
, and it’s support for docker-machine
is experimental yet, so tread with care. More info here.
Conclusions
I hope this post is helpful. After a week, I think Docker is really great and will be very helpful for us in the near future. However, we prefer to wait to have things like Guard working again, so we can completely translate the way we work into Docker, and not get crazy about getting it done. In any case, we are going to keep an eye on it!
Picture ‘Breaching Humpback Whale (Megaptera novaeangliae)’ by Gregory “Slobirdr” Smith, used under CC BY-SA 2.0 license.