Tutorial: Using docker and docker-compose for Ruby on Rails development

May 01, 2021
Tutorial: Using docker and docker-compose for Ruby on Rails development

In this tutorial we'll see how to setup docker and docker-compose for local Ruby on Rails development.

TL;DR: if you don't want to read the whole story, we've created several gists for you. You just have to follow the instructions in their README:

This tutorial won't cover how to install Docker on your computer. You can refer to the official docker documentation.

Goal

We want to have a standard and homogeneous Ruby on Rails development environment isolated from all our other development projects.

This development environment must include webpack-dev-server as a test environment as well.

It should be possible to extend it with a database.

We want to run our local Ruby on Rails source code inside a docker container, whatever the version of Ruby or Rails.

We want a docker-compose setup to run our app container, webpack-dev-server, our tests suite and databases.

Benefits

There are many benefits to use docker and docker-compose for local development environment setup.

For each of our projects we can specify in a very easy way:

  • an exact version of Ruby
  • an exact version of Rails
  • an exact version of database engine

Furthermore each of our projects will be completely isolated:

  • chanding some minor or major version of the language, the framework, the libs or the database engine won't mess up our other dev projects
  • we can have specific software dependencies tight to a specific project inside a container without consequences on our other projects

And we because we can cleary specify each versions of our stack we will be closer to dev/prod parity.

Finally we can completely streamline local dev environment setup: it's easier to onboard new developers on our projects or simply go back to a projects we haven't touched for months or years.

Step 0: Creating the files structure {#step-0}

First things first we create the directory that will host our source code and create threee blank files Dockerfile, docker-compose.yml and entrypoint.sh.

mkdir my-new-app
cd my-new-app
touch Dockerfile
touch docker-compose.yml
touch entrypoint.sh

The Dockerfile is here to instruct Docker how to build a container from our source code and the docker-compose.yml is here to instruct how to run multiple containers including our app container. We'll get to the file entrypoint.sh later.

Step 1: Copy Dockerfile {#step-1}

There are many possibilities to build a docker container image. The easiest and most flexible way is to write a Dockerfile.

A Dockerfile is a very simple text file which describes the different steps to follow to build a Docker image.

Each line of the Dockerfile is a step of the recipe beginning with a special keyword called command.

In our Dockerfile we'll use only 6 commands:

  • FROM: specify an external docker image that we'll use as a base layer
  • RUN: run a command as part of our container
  • ENV: build an environment variable
  • WORKDIR: specify the base directory where all other commands depend on
  • COPY: will copy a file from our local filesystem into the container
  • ENTRYPOINT: will tell how to start our Docker container

Here is the Dockerfile we'll use for all our Ruby on Rails projects. Don't be afraid we'll a do line by line analysis below!

FROM ruby:3.0.1

ARG USER_ID
ARG GROUP_ID

RUN addgroup --gid $GROUP_ID user
RUN adduser --disabled-password --gecos '' --uid $USER_ID --gid $GROUP_ID user

RUN apt-get update -qq

ENV GEM_HOME="/usr/src/app/bundle"
ENV PATH $GEM_HOME/bin:$GEM_HOME/gems/bin:$PATH

RUN apt-get update -qq

# Install NodeJS
RUN apt-get install -y nodejs

# Install yarn
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
RUN apt-get update -qq
RUN apt-get install -y yarn

WORKDIR /usr/src/app

# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh

USER $USER_ID

ENTRYPOINT ["entrypoint.sh"]

Analysis of our Dockerfile

We'll take the official Ruby docker image. As we want to use ruby 3.0.1 we'll base our docker image on the 3.0.1 docker version. Docker image tag version is the same as the ruby language version.

FROM ruby:3.0.1

As our goal is to install gems, we'll tell our docker container where to store our gems source code. The GEM_HOME will instruct bundle where to store gems. Here we went gems source code to be installed in the bundle directory. We'll see below where this bundle dir will be really stored on our local filesystem.

ENV GEM_HOME="/usr/src/app/bundle"

And of course we want that gem binstubs are available in the standard PATH inside our container:

ENV PATH $GEM_HOME/bin:$GEM_HOME/gems/bin:$PATH

Official ruby docker images are based on Ubuntu. We can use Ubuntu standard tooling. First we'd like to update the package information database. The -qq option is here to output only error messages.

RUN apt-get update -qq

As Rails needs node to be installed, we can install it with it's Ubuntu package.

RUN apt-get install -y nodejs

Recent rails versions install the webpacker gem which needs yarn to be installed. Here is a method to install yarn from the official Debian/Ubuntu repo provided by yarn.

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
RUN apt-get update
RUN apt-get install -y yarn

We want to specify that our base working directory will be /usr/src/app.

WORKDIR /usr/src/app

Finally we add a script to be executed every time the container starts.

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]

Step 2: Copy docker-compose.yml {#step-2}

docker-compose is a tool to run multiple Docker containers in parallel.

A simple text file in YAML format called docker-compose.yml is needed to instruct docker-compose how to run muliple docker containers called services.

Here is the docker-compose.yml file we'll use. We'll begin with a very simple setup.

version: "2"
services:
  web:
    build: .
    ports:
      - 3000:3000
    volumes:
      - ./:/usr/src/app
    command: bundle exec rails server -b 0.0.0.0 -p 3000 -e development

Analysis of our docker-compose.yml

TBD

Step 3: Copy entrypoint.sh {#step-3}

TBD

Step 4: Bootstrap our new Rails app {#step-4}

Now that we have setup our Docker, using the exact Ruby version we need for this project, it's time to run them!

First we'd like to run a bash command inside our app container to install Ruby on Rails.

docker-compose run web bash

We're now running a bash terminal inside our running app container.

We can now proceed with the installation of Rails.

gem install rails

The gem and all its dependencies will be installed in our local bundle directory.

Once done we can create the structure of our app:

rails new .

Whenever you'd need to get out of a running docker container you can type exit and type ENTER or type CTRL+D.

Launching the command docker-compose run web command is our go to solution to run any command inside our running container. You can use it to run a bash terminal as described above, run a bundle command like docker-compose run web bundle install devise or a yarn command like docker-compose run web yarn install @hotwired/turbo-rails.

Step 5: Start our local stack {#step-5}

You can launch docker-compose up and visit http//localhost:3000 in your browser.

Launching the command docker-compose up is our go to solution to start any Rails projects using the docker and docker-compose setup used in this tutorial.

Voilà, we now have a running local Rails development environment using docker!

Step 6: Setup webpack-dev-server {#step-6}

Recent version of Rails need webpack. The live asset compilation is done with a daemon called webpack-dev-server.

We'll add it as a new service in our docker-compose.yml file which looks like the following:

version: "2"
services:
  web:
    build: .
    ports:
      - 3000:3000
    volumes:
      - ./:/usr/src/app
    links:
      - webpack      
    command: bundle exec rails server -b 0.0.0.0 -p 3000 -e development
  webpack:
    build: .
    ports:
      - 3035:3035
    volumes:
      - ./:/usr/src/app
    command: ./bin/webpack-dev-server
    environment:
      WEBPACK_HOST: "172.17.0.1:3035"

Furthermore as webpack-dev-server will run inside a container separated from our regular app container, we'll have to bind webpack-dev-server on 0.0.0.0 meaning on all network interfaces. That way it will be accessible from outside its container.

In config/webpacker.yml replace the key development/dev_server/host with the value 0.0.0.0 (localhost is the default value and we don't want it).

Now stpo the stack, rebuild it and restart it.

docker-compose down
docker-compose build
docker-compose up

We now have a fleet of 2 containers running: one container running our Rails source code and one container running webpack-dev-server.

Step 7: Add a PostgreSQL Database to Our Local Stack {#step-7-alt1}

FROM ruby:3.0.1

RUN apt-get update

ENV GEM_HOME="/usr/src/app/bundle"
ENV PATH $GEM_HOME/bin:$GEM_HOME/gems/bin:$PATH

RUN apt-get update -qq

RUN apt-get install -y postgresql-client

# Install NodeJS
# If you don't neet any specific NodeJS version use the Ubuntu package
RUN apt-get install -y nodejs
# If you'd like to install a specific NodeJS version, comment the line above and
# uncomment the lines below
# RUN apt-get install apt-transport-https
# ENV NODE_VERSION 14.16.1
# RUN cd /opt && \
#     curl -L "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" | tar -xJf - && \
#     mv -v node-v$NODE_VERSION-linux-x64 node
# ENV PATH /node_modules/.bin:/opt/node/bin:$PATH

# Install yarn
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
RUN apt-get update -qq
RUN apt-get install -y yarn

WORKDIR /usr/src/app

# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]

The first thing will be to add a new entry in the services section of our docker-compose.yml file which will now look lihe following:

version: "2"
services:
  web:
    build: .
    volumes:
      - .:/usr/src/app:rw
    environment:
      DATABASE_URL: postgresql://admin:admin-secret@postgresql:5432/my_new_app_development
      WEBPACKER_DEV_SERVER_HOST: 172.17.0.1
    links:
      - postgresql
      - webpack
    ports:
      - 3000:3000
    command: bundle exec rails server -b 0.0.0.0 -p 3000 -e development
  webpack:
    build: .
    ports:
      - 3035:3035
    volumes:
      - ./:/usr/src/app
    command: ./bin/webpack-dev-server
    environment:
      WEBPACK_HOST: "172.17.0.1:3035"
  specs:
    build: .
    volumes:
      - ./:/usr/src/app/
    environment:      
      DATABASE_URL_TEST: postgresql://admin:admin-secret@postgresql:5432/my_new_app_test
      RAILS_ENV: test
      SMTP_URL: smtp://hello%40localhost@localhost
    links:
      - postgresql
    command: "tail -F /dev/null"
  postgresql:
    image: scalingo/postgresql:12.2.0-6
    ports:
      - 5432:5432
    environment:
      DB_UID: 1000
    volumes:
      - ./docker/postgresql-data:/var/lib/postgresql:rw
    command: /postgresql

Step 7 alt: Add a MySQL Database to Our Local Stack {#step-7-alt2}

Step 7 alt: Add a Redis Database to Our Local Stack {#step-7-alt3}

FAQ

How to Start my Local Stack?

How to stop my Local Stack?

How to run a command against my local stack?

How to install a gem?

How to install a yarn package?

How to install mimemagick?

How to install external binaries like wkhtmltopdf?

Share the article
Yann Klis
Yann Klis
Yann Klis co-founded Scalingo. Scalingo's vision is to offer a european sovereign cloud housting platform for developers. Today Scalingo hosts thousands of web applications from customers located all over the world. Before that Yann founded Novelys a web studio specialised in Ruby on Rails technology.

Try Scalingo for free

30-day free trial / No credit card required / Hosted in Europe