Dockerize Existing Ruby on Rails API with docker-compose.yml

Docker is a great tool to develop and manage your Ruby on Rails applications locally. It allows you to easily isolate your ruby environment, database and other services like cronjob etc. Where as docker-compose file brings together different services to be used by each other. In this guide we’re going to cover:

  • Creating a new Rails 5 App (Optional)
  • Setting up the App for dockerizing
  • Adding Dockerfile and configure docker-compose.yml to run rails server

Pre-reqs

You should have docker for mac installed. You can get it here. Basic knowledge of Docker and Rails is also helpful.

If you are looking to add delayed_job and cron to your dockerized app you can find it here (Add background jobs and cron to your dockerized ruby on rails app).

Step 1 — Create a Rails 5 App

First, we are going to create a Rails 5 API app with Postgres. If you already have an app you can skip this section.

To create a new rails project, type the command below in your terminal:

$ rails -v
Rails 5.2.1
$ rails new rails-docker -d postgresql --api

This should create an app with the folder name rails-docker. Go into the folder and open it in your favorite text editor. I am using sublime text:

$ cd rails-docker
$ subl .

At this point, you can create the database by running rails db:create and do rails s to start the server but you don’t have to do this, as we will be doing it using docker-compose.

Step 2 — Setting up the App for dockerizing

We will make some changes to our app before we dive into creating Dockerfile.

First, we will add dotenv-rails gem in our project. We are using this to maintain environment variables for the App. So add the following line in your Gemfile under development and test group:

gem 'dotenv-rails'

Now open config/database.yml and replace all the content with the following:

development:
url: <%= ENV['DATABASE_URL'].gsub('?', '_development?') %>
test:
url: <%= ENV['DATABASE_URL'].gsub('?', '_test?') %>
production:
url: <%= ENV['DATABASE_URL'].gsub('?', '_production?') %>

and also create .env file in your project root folder and add the below line in it:

DATABASE_URL="postgres://root:mysecretpassword@db:5432/rails_docker_db_name?encoding=utf8&pool=5&timeout=5000"

Here we are using the same DATABASE_URL env variable and appending environment name to the database name. So for the development environment, database name will be rails_docker_db_name_development.

That's all for the project setup. Let’s move onto the interesting part.

Step 3 — Adding Dockerfile and configure docker-compose.yml

Let's add theDockerfile to the project root folder with the following content:

FROM ruby:2.4.3
ENV APP_HOME /rails-docker
# Installation of dependencies
RUN apt-get update -qq \
&& apt-get install -y \
# Needed for certain gems
build-essential \
# Needed for postgres gem
libpq-dev \
# The following are used to trim down the size of the image by removing unneeded data
&& apt-get clean autoclean \
&& apt-get autoremove -y \
&& rm -rf \
/var/lib/apt \
/var/lib/dpkg \
/var/lib/cache \
/var/lib/log
# Create a directory for our application
# and set it as the working directory
RUN mkdir $APP_HOME
WORKDIR $APP_HOME
# Add our Gemfile and install gems
ADD Gemfile* $APP_HOME/
RUN bundle install
# Copy over our application code
ADD . $APP_HOME
# Run our app
CMD RAILS_ENV=${RAILS_ENV} bundle exec rails db:create db:migrate db:seed && bundle exec rails s -p ${PORT} -b '0.0.0.0'

Command breakdown:

  • FROM ruby:2.4.3 tells Docker to derive our image off of the Ruby image with version 2.4.3.
  • ENV APP_HOME /rails-docker defines an environment variable that will be available to this instance.
  • Next line we are installing all the dependencies needed for our app to run.
  • RUN mkdir $APP_HOME and WORKDIR $APP_HOME creates a directory and makes it as a working directory. This means we will be in this directory by default.
  • Now we are adding Gemfile and Gemfile.lockto our docker directory that we just created and run bundle installto install all the gems.
  • Next, we are adding the rest of the project to our app directory and specifying our default command using CMD. If you noticed we are RAILS_ENV and PORT env variables which we will now add in .env file.

Add the following in your .env file:

RAILS_ENV="development"
PORT=3000

We will also add .dockerignore file in the root directory with the following content which will tell the docker to not copy these folders or files to the container whenever we build the image.

.dockerignore
.git
logs/
tmp/

Now we will create docker-compose.yml file which will define our web and db service.

version: '3'
services:
db:
image: postgres
environment:
POSTGRES_USER: root
POSTGRES_PASSWORD: mysecretpassword
volumes:
- pgdata:/var/lib/postgresql/data
web:
build: .
command: bundle exec rails s webrick -b '0.0.0.0'
volumes:
- .:/rails-docker
ports:
- "3000:3000"
depends_on:
- db
tty: true
stdin_open: true
volumes:
pgdata:

Here, we are defining 2 services, First is db service which uses Postgres image from docker hub. Notice we used db service as the host in our DATABASE_URL in the .env file. Second is web service which is built through our Dockerfile and we have provided the command to run this service. We also mapped the port of container to our host port i.e. 3000. And we have mentioned that web is dependent on db service. That’s it, now let's try to build it. Type following in your terminal:

$ docker-compose build

Once the build is complete, you should see the following output:

Now run the below commands to create and migrate the database and run the server:

$ docker-compose run web rails db:create db:migrate
$ docker-compose up

Go to your browser and open http://localhost:3000 and you will see the app home page.

Congrats, you have completed the first milestone. Give yourself some treat ;)

You can grab the final code here: https://github.com/ankitsamarthya/dockerized-rails

Conclusion

This is just a start, there's a lot to learn about docker and docker-compose. Don’t worry I will walk you through the process from development to production to set up continuous deployment with AWS ECS.

You can continue to part 2 of this tutorial here (Add background jobs and cron to your dockerized ruby on rails app) where I show you how you can add delayed_job and cron in this dockerized app.

See you soon. Until then, keep coding. :)