Running a Rails app on Docker using the Passenger image

06 Mar 2015   docker, ruby

This is a 2-part post on getting a Rails app running on Docker using the Passenger image from Phusion. This first part covers setting up a development environment using Docker Compose (formerly called Fig) and boot2docker. The second part will cover deploying the Rails app as a container to AWS using Elastic Beanstalk.

Production Architecture

The Rails app is for a new project I’m working on. It consists of Rails web and worker tiers running on Elastic Beanstalk and using DynamoDB for storage. Usually I use RDS Postgres when deploying to AWS but this is expensive and isn’t needed for this project, which has very basic storage requirements. If you’re developing on Postgres or MySQL these steps should still work you’ll just need to use a different image for the database container.

Development Architecture

One of the features that attracted me to DynamoDB is that AWS have released DynamoDB Local. This lets you run the database locally in development and only use the cloud version for production. DynamoDB Local is a Java application and has already been packaged as a Docker image.

Currently in development I’m running 2 containers, a database container running DynamoDB Local and an app container running the Rails app. Later on it’s likely I’ll split the app container into web and worker containers to match more closely the Elastic Beanstalk architecture.

I develop on OS X but Docker only runs natively on Linux. So I’m using boot2docker, which is a lightweight VM designed for use in development on OS X and Windows. The diagram below shows the system architecture in development.

Docker development architecture diagram

Passenger Image

For the Rails container I’m using the Passenger image from Phusion. This image is based on Ubuntu and has Ruby installed along with Nginx and Passenger.

There is some controversy about this image because it runs multiple processes including an init process. This is more than the single process per container pattern often advocated for Docker. However it meets the single logical service per container pattern.

For my purposes this is the best image for hosting Rails apps on Docker at the moment. Phusion have put a lot of time and thought into producing a well-designed image that does a lot of the hard work in running Rails on Docker. The single logical service per container pattern also works best for hosting with Elastic Beanstalk. As one of the restrictions with Beanstalk is currently you can only host a single container per EC2 instance.

# Dockerfile

FROM phusion/passenger-ruby21:0.9.15
MAINTAINER Ross Fairbanks ""

# Set correct environment variables.
ENV HOME /root

# Use baseimage-docker's init system.
CMD ["/sbin/my_init"]

# Expose Nginx HTTP service

# Start Nginx / Passenger
RUN rm -f /etc/service/nginx/down

# Remove the default site
RUN rm /etc/nginx/sites-enabled/default

# Add the nginx site and config
ADD nginx.conf /etc/nginx/sites-enabled/webapp.conf
ADD rails-env.conf /etc/nginx/main.d/rails-env.conf

# Install bundle of gems
ADD Gemfile /tmp/
ADD Gemfile.lock /tmp/
RUN bundle install

# Add the Rails app
ADD . /home/app/webapp
RUN chown -R app:app /home/app/webapp

# Clean up APT and bundler when done.
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

The Dockerfile starts the init process, enables Passenger and Nginx and adds the Nginx config files.

Bundler is used to install the RubyGems required by the Rails app. By adding both the Gemfile and Gemfile.lock to /tmp the bundle is cached if there have been no changes to the Gemfile. An issue with the bundler install is that it runs as root, which is not recommended and triggers a warning message.

The next step is to add the Rails app code and set the file ownership to the app user that ships with the image. The final step removes any temp files from the apt or bundler installs.

This is the simple Nginx config file that configures the Rails app.

# webapp.conf

server {
  listen 80;
  root /home/app/webapp/public;
  passenger_enabled on;
  passenger_user app;
  passenger_ruby /usr/bin/ruby2.1;

A further config file is required to pass environment variables from Nginx to Passenger. As otherwise Nginx does not pass any environment variables to child processes.

# rails-env.conf

# Set Nginx config environment based on
# the values set in the .env file


Docker Compose

Docker Compose (previously called Fig) lets you construct applications out of multiple containers. In this case the YAML file defines both the web and database containers.

# docker-compose.yml

  build: .
    - "80:80"
    - .env
    - db
    - "/webapp:/home/app/webapp"
  image: tray/dynamodb-local
  command: "-inMemory"
    - "8000:8000"

The env_file setting means that the variables defined in the .env file will be added to the environment in the container. The .env file can then excluded from source control and used to hold sensitive data such as AWS access keys.

The links section defines a link between the web and db container meaning that it can access it. The volumes section means that the /webapp shared folder on the boot2docker VM is mounted in the container in the /home/app/webapp directory.

Shared Folders Problem

Generally using boot2docker rather than running Docker natively is pretty painless. However volumes is an area that is still tricky. The situation is improving, as the boot2docker VM now has Virtual Box shared folder support pre-installed. It auto mounts your Users directory as /Users on OS X or /c/Users/ on Windows.

However the app user on the Passenger image has a UID of 9999. This meant that when the volume was mounted in the container the files didn’t belong to the app user and they couldn’t be written to without making them world readable.

So instead I created my own Virtual Box shared folder and mounted it as UID 9999. This is working well and means the Rails files have the correct ownership and permissions on both the host and the container.

$ boot2docker halt
$ vboxmanage sharedfolder add "boot2docker-vm" --name webapp --hostpath /Users/ross/src/webapp/

$ boot2docker up
$ boot2docker ssh "sudo modprobe vboxsf && sudo mkdir /webapp && sudo mount -v -t vboxsf  -o uid=9999,gid=9999 webapp /webapp"


Overall I’m pretty happy with the setup. Previously I was using Vagrant to manage my development environments. I like to think of the Dockerfile and the docker-compose.yml files as together providing the same functionality as a Vagrantfile. The next post will cover deploying the Rails image to AWS using Elastic Beanstalk.


Thanks to Phusion for their hard work on Passenger and the Docker images. Also thanks to Jeroen van Baarsen for his post on using the Passenger image which was very helpful.

comments powered by Disqus