---
title: Rails on Docker
teaser: Develop a Rails app in Docker.
tags: web,osx,unix,docker,rails
author: Josh Clayton
published_on: 2015-02-19
---

<p class="notice">Note: This post was updated March 2016 to use
<code>docker-compose</code> instead of <code>fig</code> and Docker Machine
instead of <code>boot2docker</code> directly.</p>

I recently volunteered to investigate deployment solutions for one of our
clients. There were a number of aspects that we knew would be tricky:

* the app needed to run behind the client's firewall
* the app required a number of other dependencies, including [Ragel], [Racc], and
  [Solr]
* the app needed to connect to MS SQL Server for data import
* the app needed to connect to PostgreSQL

Given these requirements, the team and I determined that [Docker] would be a
good first solution to explore.

## Why Docker?

Docker is a tool which allows developers to define containers for
applications; this allows for control over the operating system and
software running the application. If you're running against an older version
of MySQL, a patched version of Ruby, or other dependencies which make setting
up a development environment difficult, Docker may simplify development across
a team. Even without complicated dependencies, forced encapsulation within a
VM ensures development parity across a team and, if deploying with Docker to
staging or production, across environments.

[docker]: https://www.docker.com/
[ragel]: http://www.complang.org/ragel/
[racc]: https://github.com/tenderlove/racc
[solr]: http://lucene.apache.org/solr/

## Getting started on OS X

I'll be outlining how to install all dependencies on OS X with [Homebrew].
I'll be using [VirtualBox] to manage my VM; however, Docker Machine supports
[a number of other drivers][drivers] in case you'd prefer to use something
else.

[homebrew]: http://brew.sh/
[virtualbox]: https://www.virtualbox.org/
[drivers]: https://docs.docker.com/machine/drivers/

### Install VirtualBox with Homebrew Cask

[Homebrew Cask][homebrew-cask] is a great tool to install applications on OS
X. To install VirtualBox:

```sh
brew install caskroom/cask/brew-cask
brew cask install virtualbox
```

[homebrew-cask]: http://caskroom.io/

### Install Docker

With VirtualBox installed, we now install Docker and [Docker Machine]. OS X
and Windows don't support native virtualization, so Docker Machine will use
[boot2docker] under the hood, which is a minimal shim to get around that.

[Docker Machine]: https://docs.docker.com/machine/overview/
[boot2docker]: http://boot2docker.io/

```sh
brew install docker docker-machine
```

Once Docker Machine completes installation, create a machine named `default`
using the VirtualBox driver:

```sh
docker-machine create --driver virtualbox default
```

Finally, to export the Docker environment variables to your shell, run:

```sh
eval "$(docker-machine env default)"
```

You'll likely want to add the provided `export`s to your `.zshrc`, `.bashrc`,
or other appropriate file so they're available any time you subsequently open
a terminal. To do this automatically, add to one of the aforementioned files:

```bash
eval `docker-machine env 2>/dev/null`
```

### Install Docker Compose

Once Docker is installed, next up is [Docker Compose]. Docker Compose provides a
configuration syntax (with YAML) and a CLI to manage multiple containers
simultaneously.

You can install Docker Compose with Homebrew:

```sh
brew install docker-compose
```

With Docker Compose installed, we're ready to set up our Rails application to
run in a Docker container.

[Docker Compose]: https://docs.docker.com/compose/

## Developing with Docker, Docker Compose, and Rails

With Docker and Docker Compose running on OS X, we can move forward by preparing
a Rails app to run in a Dockerized environment. You can find an example Rails
application running in Docker [here][backbone-data-bootstrap].

[backbone-data-bootstrap]: https://github.com/joshuaclayton/backbone-data-bootstrap

### Write a Dockerfile

Let's start with a simple `Dockerfile`, which lives in Rails' root:

```dockerfile
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/
RUN bundle install

ADD . $APP_HOME
```

There's only a handful of commands that we're using with Docker, but let's go
through them:

1. **`FROM`**: This describes the image we'll be basing our container off of;
   in this case, we're using the Ruby 2.2 image.
1. **`RUN`**: This is how we run commands; in this example, `RUN` is used
   primarily to install various pieces of software with [Apt].
1. **`WORKDIR`**: This defines the base directory from which all our commands
   are executed.
1. **`ADD`**: This copies files from the host machine (in our case, relative
   to Dockerfile on OS X) to the container.

Each command generates a new cached image, which can be built upon later;
files with higher churn, or packages that may change, should be placed further
down in the Dockerfile to leverage the cache as much as possible.

To build this Docker image, from Rails root run:

```sh
docker build .
```

This will build an image with the appropriately installed software - but we're
not done.

[apt]: https://wiki.debian.org/Apt

### Configure Docker

Even though we're able to successfully build a Docker image to house our Rails
app, we have other dependencies. In this case, we'll also need to configure a
database like Postgres, and ensure that the database container and the
application container can talk to one another.

This is where Docker Compose really shines.

Here's an example `docker-compose.yml` file:

```yml
version: '2'
services:
  db:
    image: postgres:9.4.1
    ports:
      - "5432:5432"

  web:
    build: .
    command: bin/rails server --port 3000 --binding 0.0.0.0
    ports:
      - "3000:3000"
    links:
      - db
    volumes:
      - .:/myapp
```

In `docker-compose.yml`, we're describing two containers. The first is `db`,
which is based on another image (`postgres:9.4.1`) and exposes port 5432 on port
5432 to the outside world.

The second is `web`, which uses the Dockerfile (`build: .`), spins up a Rails
server when `docker-compose up` is run, exposes port 3000 (the Rails app) on
port 3000, links the database container, and bases the directory `/myapp`
(`WORKDIR` from the `Dockerfile`) off of the Rails app on the host machine.

With `docker-compose.yml` complete, run:

```sh
docker-compose build
```

This will build all the necessary containers.

### Configure the database

Because we're connecting to a container running Postgres for our database,
we'll need to update our `config/database.yml`, leveraging the appropriate
host based on the name in the `docker-compose.yml`.

```yml
development: &default
  adapter: postgresql
  database: backbone_data_bootstrap_development
  min_messages: WARNING
  pool: 5
  username: postgres
  host: db

test:
  <<: *default
  database: backbone_data_bootstrap_test
```

Be sure to use the correct environment variables and host names available to
your Rails app.

With these settings in place, create and set up the database:

```sh
docker-compose run web rake db:create db:setup
```

### Serve the app

To bring the Rails app up, it's as simple as `docker-compose up`. You should be
able to access the application via a web browser by visiting the IP address of
Docker Machine (you can find this out with `docker-machine ip default`) on port
3000.

### Interact via a Rails console

Running a Rails console is as simple as:

```sh
docker-compose run web rails console
```

The structure of the commands here is `docker-compose run {CONTAINER_NAME}
{COMMAND}`. Note that running commands with `docker-compose run` are in new
containers and not a running container (e.g. if you're already running
`docker-compose up`).

### Run specs

If your tests don't require a JavaScript driver, you can run tests with:

```sh
docker-compose run web rake
```

If your app does use a JavaScript driver for any tests (I'm using
[capybara-webkit] in the example app), I recommend looking into the [headless
gem][headless-gem]. Its only dependency is `xvfb`, and requires minimal tweaks
to work:

```ruby
# spec/support/headless.rb

RSpec.configure do |config|
  config.around type: :feature do |example|
    Headless.ly do
      example.run
    end
  end
end
```

This runs all feature specs within a Headless block.

With [Konacha], it was a bit trickier:

```ruby
# config/initializers/konacha.rb
if defined?(Konacha)
  Konacha.configure do |config|
    require 'capybara/webkit'
    config.driver = :webkit
  end
end
```

This configures Konacha to use the capybara-webkit driver.

Next, we need to wrap the `konacha:run` rake task to run within the Headless
gem:

```ruby
# Rakefile
namespace :konacha do
  task :run_with_headless do
    require "headless"

    Headless.ly do
      Rake::Task["konacha:run"].invoke
    end
  end
end

task default: ["konacha:run_with_headless", :spec]
```

[capybara-webkit]: https://github.com/thoughtbot/capybara-webkit
[headless-gem]: https://github.com/leonid-shevtsov/headless
[konacha]: https://github.com/jfirebaugh/konacha

## Wrap up

This is an example of just one application running inside Docker; your apps
likely have different sets of dependencies, so you may become comfortable with
`Dockerfile`s (and Apt!) quickly.

What apps have you run in Docker recently?

## Additional Resources

* docker-compose.yml documentation: [https://docs.docker.com/compose/compose-file/](https://docs.docker.com/compose/compose-file/)
* Dockerfile reference: [https://docs.docker.com/engine/reference/builder/](https://docs.docker.com/engine/reference/builder/)
* Fine-tuning your `Dockerfile`: [http://jonathan.bergknoff.com/journal/building-good-docker-images](http://jonathan.bergknoff.com/journal/building-good-docker-images)
