Working on a Ruby on Rails project? Chances are that you’ll need a specific version of Ruby, Bundler, and project-specific gems (packages). Instead of installing these things directly on your dev machine (laptop/desktop), you can use Docker to keep things organized. This article aims to help you get started with a basic Ruby on Rails setup with Docker.

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.

Screenshot of the Rails welcome screen
This article would’ve been incomplete without this graphic, so, voila!

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.

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 the docker-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 a Dockerfile.
    • The context specifies the directory from which those commands should be executed (sort of).
  • ports: This ensures that the port 3000 of the container is accessible to the outside world, say, your browser.
  • volumes: Tells Docker to synchronize the contents of the ror 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 like RAILS_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 as mkdir /ror && cd /ror.
  • COPY ./ror /ror: Copies the contents of the ror 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’s Gemfile, 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.

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"
  • 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.

On this page

Never miss an article

Follow me on LinkedIn or Twitter and enable notifications.