Warning: This post is written by the DevOps guy, not the Rails guy. This post, however, is to be read by you, Rails girls and boys. It assumes the reader is not familiar with Docker. For those of you who already know Docker, I'm sure you'll enjoy Fig.
If you're in a hurry, jump straight to the TL;DR.
Onboarding a member to a project is usually a painful process, especially when it has multiple dependencies: a PostgreSQL database, a Redis instance, ElasticSearch... It may take a while until the development environment is properly configured so we may actually... code. Additional pain comes from managing multiple Ruby versions. Setting everything up properly takes way too much time and energy, and it shouldn't be that way.
We wanted to ease the pain of setting up a basic Rails development environment, so it becomes repeatable on every developer machine with fewer keystrokes and headaches. For that we chose Docker with the help of a neat tool called Fig.
By the end of this post, you should be able to tell your coworkers to just
fig up and they'll be ready to go!
fig rm and it's gone !
Some people refer to Docker as a "lightweight VM", but that's a description we would like to avoid. Solomon Hykes, Docker's Founder & CTO calls Docker "a standard format for shipping software".
The techy explanation: Docker uses low level primitives of the Linux kernel that make it possible for us to sandbox the execution of processes (cgroups, kernel namespaces). This means our processes are able to run isolated from the remaining processes of the host system (the system on which the Docker containers run). The advantages of Docker go beyond process isolation, but I won't go there.
Since it uses Linux kernel features, Docker containers only run on Linux systems, but hey, don't worry OS X and Windows guys! There's a simple solution for you, too!
Although Docker is pretty easy to use once you understand it, most people just want the web app and the database up and running as fast as they can. Fig simplifies the process of setting up multiple services using Docker by allowing us to describe them and their relations on a
fig.yml like this:
web: build: . command: python app.py links: - db ports: - "8000:8000" db: image: postgres
It is mostly self explanatory (probably with the exception of the
build: . bit), but we rather explain it in detail for our specific Rails setup.
For our Rails Development Environment approach, it is necessary to have the following software installed:
- Docker 1.3 (or higher)
- Fig 1.0 (or higher)
There's a timing for this post and a reason for being specific about the Docker and Fig releases: they all come after the announcement of the Docker 1.3 release, which fixed Shared directories on Mac OS X on
boot2docker (explained below). This is good news for our Mac OS X coworkers, since this means it is now possible for them to update-and-refresh , the Rails way (as long as the code is under
/Users, such as
To install Docker, choose your OS on the Docker installation page and follow the instructions. While Linux systems are able to install it directly on their machines, OS X relies on the use of a VM with a lightweight Linux distro, called
boot2docker; the Mac OS X instructions are really easy to follow, so you should be good. Please make sure to setup your environment properly, (the
$(boot2docker shellinit) part), so that Fig is able to find Docker.
To install Fig, just follow the Fig installation instructions (you're just a
pip install away!) and you'll be all set.
The Rails Dev Env
If you read so far, congratulations! The fun part begins now.
As we said when talking about Fig, we'll be using a
fig.yml that describes the services that compose our application. In this setup we'll Dockerize:
- A Ruby on Rails web application, listening on port 3000.
- A PostgreSQL database, listening on port 5432, only accessible by the RoR web app.
For this, you may clone our barebones development environment GitHub repo (whitesmith/fig-tree/rails-pg-fig-devenv) or follow the instructions on our sample Rails app GitHub repo (whitesmith/rails-pg-fig-sample).
As you can see from the barebones dev env repo, the magic relies on two files:
fig.yml and a
Let's see our
db: image: "postgres:9.3" volumes: - ~/.docker-volumes/app-name/db/:/var/lib/postgresql/data/ expose: - 5432 web: build: . command: bundle exec unicorn -p 3000 -c ./config/unicorn.rb volumes: - .:/usr/src/app ports: - "3000:3000" links: - db
We can see the
db service is using the
postgres:9.3 image (more about it on postgres official repo) and that this container exposes a service on its port 5432 (only accessible to the
web container). It also mounts a volume:
volumes: - ~/.docker-volumes/app-name/db/:/var/lib/postgresql/data/
This means that the container's
/var/lib/postgresql/data/ directory will have its contents mapped to the host's
~/.docker-volumes/app-name/db/ directory. This allows us to remove the container safely when not in use, knowing we'll be able to reuse an already setup database when needed. Before continuing, change the volume path to match your application's name and create that path since Docker assumes it already exists (
mkdir -p ~/.docker-volumes/hello-world/db/).
web is doing a
build . and, as you may have guessed, this is the command using the
Dockerfile simply contains
which pulls and builds the
rails:onbuild image. If you need to specify your Ruby version, please see Choosing Ruby version. As you may see in the rails official repo, the
Dockerfile assumes it is on the same directory as the root of the Rails application.
volumes: - .:/usr/src/app
is where the magic happens. This mounts our Rails application's code directory inside the Docker container at
/usr/src/app. This is what enables us to change the code and see our changes happen live. The
ports: "3000:3000" bit means our application's port 3000 will be mapped to our Docker host's port 3000, so we can access it on our browser.
Docker makes the link between services and lets them know of each other using hosts; this means we need to change our
test environments on
database.yml to use the
db host and the default username:
development: host: db username: postgres test: host: db username: postgres
You should use the
database.yml provided in whitesmith/fig-tree/rails-pg-fig-devenv as reference.
Please make sure:
- You have copied
Dockerfileto your application's folder.
- You have adapted
fig.ymlto match your application's name.
- You have created the database's volume directory on the host.
- You have adapted
config/database.ymlto use the
First time running
Our commands will be different when running for the first time.
First, we need to run our database.
fig up -d db
-d flag means we're running it in detached mode, so we can continue to enter commands. If this your first time using the
postgres:9.3 image, this may take a while, since it will be downloaded; future projects that use this image won't need to download it again.
Now we need to setup the database.
fig run web rake db:setup
This will create an instance of the
web container and run
rake db:setup inside it. When the command is over, the container will stop running. If this is your first time using the
rails:onbuild image, this will also take a while.
Now we just have to run
db container was already running, this will simply start the
Voilà! Open your browser at http://localhost:3000!
Note: If you're using
boot2docker, it will be available at
http://192.168.59.103:3000, but YMMV; please check
your Docker host's IP by running
Second time running
and you're good to go.
fig stop when you're done with your work for the day and
fig rm for clean up; because we've persisted the database on the host, removing the container won't destroy the database unless you remove the folder. If you're completely done with your project, see  for details.
We hope you find this helpful. Any feedback, questions or issues are welcome. Just drop by whitesmith/fig-tree and let us know.
This is an experiment; we are currently starting to use it on some of our projects and if it works, you can expect us to share more complex setups in the future.
- Install Docker.
- Install Fig.
git clone [email protected]:whitesmith/rails-pg-fig-devenv.git
- Add the repo's
Dockerfileto your RoR application's root.
fig.ymlto match your application's name (the
dbvolume path) and create that folder structure.
fig up -d db(This downloads the PostgreSQL image, grab a beer.)
fig run web rake db:setup
fig up(This downloads the Ruby image, another beer.)
- Open your browser at http://localhost:3000 (if you're using
boot2docker(aka OS X), replace
localhostby the output of
boot2docker ip, most likely http://192.168.59.103:3000).
The images downloaded in steps 4. and 6. will be reused by future projects, so don't worry: this will only happen the first time you run these commands on your machine (unless you use different versions).
Choosing Ruby version
If you want to specify the Ruby version used by your
web container, an easier way to do so is simply copying the contents of rails:onbuild Dockerfile to your
Dockerfile, removing the
ONBUILD prefixing some of the Docker commands  (since those commands would only be triggered when using this as a parent image) and changing the
FROM ruby:X to match your desired version. Remember that
X is a Docker tag, which means you should check the ruby official repo first for the list of supported tags. Remember: you can always write your first Dockerfile!
Cleaning up the project
fig rm removes your project containers. If you would like to completely remove the project, you should:
- Remove the
dbvolume. Remember creating
~/.docker-volumes/app-name/db/? This is where your database data resides. If you're done with it, simply remove it. You'll have to
rake db:setupthe next time.
- Remove unused Docker images: When you
fig upfor the first time, it downloads the necessary Docker images (Ruby and PostgreSQL). If you're using them for different projects, great, that means you found this useful! But if you're not using them in other projects and you're not thinking of doing
fig upanytime soon, maybe you should remove them. Do
docker imagesto list them and
docker rmi <image-id>to remove them.
Update & Refresh
Note 2: Update-and-refresh on non-Linux systems was already possible using other approaches such as NFS. But following that approach while keeping Linux/Mac OS X dev envs as similar as possible would require us to either:
- Maintain the Vagrantfile/Linux-setup-strategy in sync or
- Make Linux users use the Mac OS X guys VM and make them install NFS, so they could all use the same Vagrantfile (not cool OS X guys, Linux guys don't need to use VMs).
While it would be possible, for instance, to reuse the setup scripts used by the Vagrantfile on Linux systems, I feel the Docker 1.3 release simply eliminates the need for heterogeneity amongst OSes (after all, that's Docker's philosophy). Instead, It just works™.
Note 3: A simple
sed -i 's/^ONBUILD //g' Dockerfile does the trick.