When I started Rails development in 2018, the first thing I did was dockerize my project. Why you ask? It’s because Rails projects often require specific versions of Ruby, Bundler, and gems (dependencies). As soon as you start working on multiple projects with different dependencies things often get complicated. You can use tools like Chruby, RVM, or Rbenv to manage versions of Ruby and gem, however, using Docker gives you even more flexibility.
In this article, we’ll take a look at setting up a basic, dockerized Ruby on Rails application with a PostgreSQL database.
Two minute version
- Clone the docker-rails-example repository.
- Create
.env
based on .env.example. - Put a Rails project (new or existing) in the
ror
directory. - Update
config/database.yml
based on example.database.yml. - Run
docker-compose build
. - Run
docker-compose up
. - Your Rails app should be available at
localhost:3000
.
Requirements
- Basic Rails knowledge, at least the installation part.
- Basic Docker concepts, e.g. what an image and a container is.
- A working installation of Docker.
- My Docker version:
19.03.13
. - My Compose version:
1.27.4
.
- My Docker version:
Step 1: Prepare project directory
You need a directory in which you can put your project. You can start by cloning the docker-rails-example repository that contains code for this example.
Step 2: Create docker-compose.yml
Reference: docker-compose.yml
We start by creating a docker-compose file which contains the following top-level keys:
version
: Version of the syntax used in thedocker-compose.yml
file.services
: Services required by the project.
A basic Rails setup needs 2 services, viz, Ruby and a database. Now let’s see how to define them.
Database
Since a standard Rails app needs to store data in a database, we start by creating a database service. This example uses PostgreSQL, but you can use a different backend as well.
services:
# ...
database:
image: postgres
env_file:
- .env
volumes:
- ./.docker/volumes/database:/var/lib/postgresql/data
Now let’s take a look at the important keys of this YML code:
image
: This specifies the image to use for the database service. Docker will download the latest version of the postgres image and use it for the database service. You can specify a version, e.g.postgres:12
.env_file
: An env file contains environment variables which usually contain parameters for configuring services. In this example, the.env
file in the root of the project will be used to define the database user and password.volumes
: This is an optional key that tells Docker to expose Postgres’ data storage directory on the host machine. If you need to delete/rebuild your database container, the data still remains accessible.
Ruby
The Ruby on Rails framework is written on Ruby, so we need Ruby to run it. Here’s the Ruby service definition used in this example.
services:
# ...
web:
build:
context: .
dockerfile: .docker/ror/Dockerfile
env_file:
- .env
volumes:
- ./ror:/ror
ports:
- "3000:3000"
depends_on:
- database
Now let’s take a look at the important keys of this YML code:
web
: A name for the service. Some people like to call it Ruby, but I prefer web.build
: This is the most important key as it tells Docker how to build an image for this service.- The
dockerfile
defines a path to aDockerfile
. - The
context
specifies the directory from which those commands should be executed (sort of).
- The
ports
: This ensures that the port3000
of the container is accessible to the outside world, say, your browser.volumes
: Tells Docker to synchronize the contents of theror
directory with the/ror
directory inside the web service container. Thus, code changes in this directory are automatically detected during development.env_file
: Lets you define environment variables likeRAILS_ENV
in a.env
file in the root of your project.
Step 3: Create Dockerfile
Reference: Dockerfile
Initially, I wanted to use the bitnami/rails image for the example since it is maintained by the community. However, at the time of writing this article, that image only supports MariaDB while I wanted PostgreSQL for my example. Thus, we use a Dockerfile
to build a custom image for our Ruby/Rails service. Let’s create the Dockerfile
that we mentioned under the services/web/build/dockerfile
key in the docker-compose.yml
file.
Define base image
FROM ruby:2.7
We begin by referencing ruby:2.7
as a base image for our Rails image. Docker will run the rest of the commands with this image to get to our custom Rails image.
Install additional packages
# Register Yarn package source.
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
# Install packages.
RUN apt update -qq
RUN apt install -y postgresql-client nodejs yarn
Rails uses the Yarn package manager, so we need to install it. If you’re not using Yarn for your project, you can remove the parts related to Yarn. However, you will still need to install postgresql-client
to connect to your PostgreSQL database.
Prepare WORKDIR
# Prepare working directory.
WORKDIR /ror
COPY ./ror /ror
RUN gem install bundler
RUN bundle install
Let’s break down this chunk of code into 3 parts:
WORKDIR /ror
: Creates a working directory and uses it for the subsequent commands. You can think of it asmkdir /ror && cd /ror
.COPY ./ror /ror
: Copies the contents of theror
directory on your dev machine to the/ror
directory inside our image.RUN gem install bundler
: Installs Bundler.RUN bundle install
: Installs the gems mentioned in your project’sGemfile
, i.e.ror/Gemfile
.
Define ENTRYPOINT
This part is out of scope for this tutorial. You can learn more about it in the Dockerfile reference. For this tutorial, let’s say that this helps with the docker run
command.
Define CMD
Lastly, we configure a command that should be executed when our Ruby/Rails service container is ready. We simply run the command rails server
with some parameters.
Step 4: Create .env
COMPOSE_PROJECT_NAME=docker_rails
POSTGRES_USER=rails
POSTGRES_PASSWORD=postgres
RAILS_ENV=development
It is common practice to put certain project config parameters in .env
files. These files simply define variables that are used by the project and usually, they are excluded from the version control system. For this example, create your .env
file based on the .env.example file and change the variables if you know what you’re doing.
Step 5: Create a Rails project
With the Docker setup is out of the way, we are only left with setting up a Rails project. This step ensures that you have a valid Rails project in the ror
directory.
The Rails code directory has been named ror
instead of rails
on purpose. The rails command-line tool does not allow the Rails project name to be rails (to avoid confusion and conflict).
Existing app
If you have an existing Rails app, simply copy the contents of your app into the ror
directory such that ror/Gemfile
exists.
New app
To create a new Rails app in the ror
directory,
- Create
ror/Gemfile
based on example.Gemfile. - Create an empty file at
ror/Gemfile.lock
. - Run this command from the directory containing the
docker-compose.yml
file:
# Add the --minimal flag for a fast, minimal installation.
docker-compose run --no-deps web rails new . --force --database=postgresql
You should now have a Rails project in the ror
directory.
If you have the rails gem installed on the host machine, you can run that instead of having to do docker-compose run
.
Step 6: Configure Rails
To make the Rails app work, we need to configure it correctly.
- Ensure correct database connection parameters in
config/database.yml
.- You can base your configuration on example.database.yml.
Step 7: See it in action
Here’s what you need to see your Docker setup in action:
docker-compose build # Builds project images.
docker-compose up # Boots up the app.
docker-compose run web rake db:create # Create databases.
docker-compose run web rake db:migrate # Run migrations.
Your Rails app should now be available at localhost:3000
.
Rakefile
Reference: Rakefile
If you find it intimidating or time-consuming to type these lengthy commands, here are some things you can do:
- Set up command
aliases
in your.bashrc
file or its equivalent.- Example:
alias dc="docker-compose"
- Example:
- Set up a
Rakefile
with shortcuts to commonly-used commands.
Conclusion
Using Docker for Rails development is cool and it helps keep things organized, especially if you have multiple projects. Also, docker skills are portable and you can use them on projects of any type. Thus, the knowledge of using Docker with Ruby on Rails indirectly contributes to making you a better developer.
Next steps
- See the code from this tutorial in the docker-rails-example repository on GitHub.
- Try dockerizing a new/existing Rails project.
- Post your comments/suggestions/thoughts in the comments area below.