Apr 14, 2020 - Salvatore Ferrucci    

Dockerize a Ruby on Rails Application using Compose

Introduction

Docker is an open-source project that can simplify your workflow during development process, everything the application needs to run is included. The Docker image contains the code, runtime, system libraries and anything else which permits to run the applications inside an isolated containers.

There are a dozen reasons to use Docker during the development, the main benefits to dockerize an application are

This tutorial will show you how to set up a development environment for a Ruby on Rails application using Docker Compose.

Prerequisites

To follow this tutorial, you will need to ensure docker and docker-compose are working on your local machine

➜  ~ docker --version
Docker version 19.03.8, build afacb8b
➜  ~ docker-compose --version
docker-compose version 1.25.4, build 8d51620a
➜  ~

Generate a new rails project

The first step will be to generate a new rails project, we can skip webpack and bundle install tasks and delegate it to the container in a short. We will use PostgreSQL as a database and the following command generate a new application which includes pg and configure PostgreSQL instead of SQLite.

➜  ~ rails new rails-docker -d postgresql --skip-webpack-install --skip-bundle

Writing the Dockerfile

Using a Dockerfile allows you to define your container environment and avoid discrepancies with dependencies or runtime versions.

FROM ruby:2.6-alpine

WORKDIR /app

RUN apk add --update --no-cache \
      bash \
      binutils-gold \
      build-base \
      curl \
      file \
      g++ \
      gcc \
      git \
      less \
      libstdc++ \
      libffi-dev \
      libc-dev \
      linux-headers \
      libxml2-dev \
      libxslt-dev \
      libgcrypt-dev \
      make \
      netcat-openbsd \
      nodejs \
      openssl \
      pkgconfig \
      postgresql-dev \
      python \
      tzdata \
      yarn

EXPOSE 3000

Defining Services with Docker Compose

Using Docker Compose, we will be able to run the multiple containers required for our setup. We will define our Compose services in our main docker-compose.yml file.

version: '3.4'
services:
  app:
    build: .
    image: rails-docker
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    environment:
      BUNDLE_PATH: /usr/local/bundle
      DATABASE_HOST: postgres
      RAILS_ENV: development
    depends_on:
      - postgres
    ports:
      - "3000:3000"
    stdin_open: true
    tty: true
    volumes:
      - ${PWD}:/app
      - bundle:/usr/local/bundle
      - node_modules:/app/node_modules

  postgres:
    image: postgres:12.2-alpine
    environment:
      POSTGRES_USER: root
      POSTGRES_HOST_AUTH_METHOD: trust
    volumes:
      - postgresql:/var/lib/postgresql/data

volumes:
  postgresql:
  bundle:
  node_modules:

Configure the Ruby on Rails Application

Before to run a rails application we need to launch several tasks in order to have the environment ready. The first one is to install all ruby dependencies running bundle install

docker-compose run app bundle install

Starting with Rails 6, Webpacker is the default JavaScript compiler. It means that all the JavaScript code will be handled by Webpacker instead of the old assets pipeline aka Sprockets. As a default node package manager rails application uses yarn and required dependencies can be installed with the command

docker-compose run app yarn install

Finally we're almost ready to launch the main application, we just need to setup database and create the schema.rb. Let's do it with well know command db:create and db:migrate

docker-compose run app rake db:create db:migrate db:seed

Testing the Application

First, build the container images and create the services by running docker-compose up with the -d flag, which will run the containers in the background:

docker-compose run -p 3000:3000 app

final

Active Storage Support

Active Storage facilitates uploading files to a cloud storage service like Amazon S3, Google Cloud Storage, or Microsoft Azure Storage and attaching those files to Active Record objects.

If your application needs to support storage and/or images processing you need to add in the Dockerfile additional dependencies

RUN apk add --update --no-cache \
      imagemagick \
      poppler \
      poppler-utils

You can add it with the new RUN directive (if you're using a docker multi stage build feature) or append it in the previous.

Procfile

If your application needs to launch multiple processes during development, you can use a Procfile approach to manage the processes.

You need to install foreman before to create and configure a Procfile, since our image is based on ruby the simple command in the Dockerfile will install all the required libraries.

RUN gem install foreman

The next step is to configure it creating a Procfile.dev with a list of processes, for example:

web: bundle exec puma -C config/puma.rb
worker: bundle exec sidekiq -C config/sidekiq/default.yml 

and change the command in the docker-compose.yml configuration

  command: bash -c "/usr/local/bundle/bin/foreman start -f Procfile.dev"

Conclusion

By following this tutorial, you have created a development setup for your Rails application using Docker containers. You’ve made your project more modular and portable by decoupling your application’s state from your code.

Containerization is a wonderful way to abstract the infrastructure layer of your application, to ship code as efficiently as possible, but now what is next?

Thanks for reading so far! Looking forward to hear your comments, questions, improvements! I am very grateful for your feedback