Dockerize an Existing Ruby on Rails Application

In this article, I am going to explain how to dockerize an existing Ruby on Rails application, and start & stop the application in development / production environment.

What is Docker?

Using docker, we can build and package an application or service with all the dependencies into single unit. Such a unit is called the “Docker Image”. It contains the entire source code of the application, runtime libraries and dependency software/services. Therefore it eliminates the necessity to have particular version of software packages or services pre-installed in a system for a particular application to run.

Docker enables an application to run in a system irrespective of the version and services requisites of the application deployed.

In this example, assume the application having built uses Ruby, Rails, PostgreSQL, Nginx and Unicorn.

Why we need to dockerize the application?

One main use of dockerizing is that on sharing the code of the application with other developers, they can easily setup the application in their machine / environment using simple commands instead of installing all requisite software and dependencies. We can avoid version mismatch from developer to developer.

If we have different rails applications with different versions of Ruby, Postgres, we can setup these applications without affecting other projects’ versions. The initial setup may take few minutes due to images, etc. But after that, we can get the environment running within a matter of seconds.

Also we can deploy the applications using simple steps in any kind of server and Linux distributions.

Prerequisites:

In order to dockerize, first of all, we have to install DockerEngine & DockerCompose in the laptop / instance based on the OS.

Install Docker Engine – Ref: https://docs.docker.com/engine/installation/

Install Docker Compose – Ref: https://docs.docker.com/v1.8/compose/install/

Clone your application source code from the github: (This is done in order to include the docker code in to it and then compile it as a single source code.)

$ git clone git@github.com:agiratech/your-application.git

Creating the Dockerfile

Create the “Dockerfile” file in application root path and copy the following code content into it. This file is for creating the docker mentioned in the beginning. Commit this file into your git repository.

# Base our image on an official, minimal image of our preferred Ruby
FROM ruby:2.2.0# Install essential Linux packages
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev postgresql-client# Define where our application will live inside the image
ENV RAILS_ROOT /var/www/docker_example# Create application home. App server will need the pids dir so just create everything in one shot
RUN mkdir -p $RAILS_ROOT/tmp/pids# Set our working directory inside the image
WORKDIR $RAILS_ROOT# Use the Gemfiles as Docker cache markers. Always bundle before copying app src.
# (the src likely changed and we don't want to invalidate Docker's cache too early)
# http://ilikestuffblog.com/2014/01/06/how-to-skip-bundle-install-when-deploying-a-rails-app-to-docker/
COPY Gemfile GemfileCOPY Gemfile.lock Gemfile.lock# Copy the Rails application into place
COPY . .# Prevent bundler warnings; ensure that the bundler version executed is >= that which created Gemfile.lock
RUN gem install bundler# Finish establishing our Ruby environment
RUN bundle install# Define the script we want run once the container boots
# Use the "exec" form of CMD so our script shuts down gracefully on SIGTERM (i.e. `docker stop`)
CMD [ "bundle exec unicorn -c config/containers/unicorn.rb" ]

Step 2: Creating the Docker Compose Configuration File

Next in our mission to dockerize, is to create the “docker-compose.yml” file in application root path and copy the following code content into it. Docker Compose will support to run one or more Docker containers. We can define everything in the configuration file and push it in to git so that other developers can use that. If you use other databases like MySql, you have to mention the mysql docker images name and its version. All the mentioned images in this article are public images hosted in dockerhub, so if you wish, you can configure with your own docker private images.

#
db:
image: postgres:9.4.5
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: root
ports:
- '5432:5432'
volumes:
- my-app-local-postgres:/var/lib/postgresql/data
app:
# use the Dockerfile next to this file
build: .# sources environment variable configuration for our app
env_file: .env# rely on the RAILS_ENV value of the host machine
environment:
RAILS_ENV: development# makes the app container aware of the DB container
links:
- db:db.local# expose the port we configured Unicorn to bind to
expose:
- "3000"# service configuration for our web server
web:# set the build context to the root of the Rails app
build: .# build with a different Dockerfile
dockerfile: config/containers/dockerfile-nginx# makes the web container aware of the app container
links:
- app# expose the port we configured Nginx to bind to
ports:
- "80:80"

Create unicorn.rb file:

Next create the “unicorn.rb” file in config/containers folder and copy the following code content into it. For this rails application, we will use unicorn as our appserver.

# Where our application lives. $RAILS_ROOT is defined in our Dockerfile.
app_path = ENV['RAILS_ROOT']# Set the server's working directory
working_directory app_path# Define where Unicorn should write its PID file
pid "#{app_path}/tmp/pids/unicorn.pid"# Bind Unicorn to the container's default route, at port 3000
listen "0.0.0.0:3000"# Define where Unicorn should write its log files
stderr_path "#{app_path}/log/unicorn.stderr.log"
stdout_path "#{app_path}/log/unicorn.stdout.log"# Define the number of workers Unicorn should spin up.
# A new Rails app just needs one. You would scale this
# higher in the future once your app starts getting traffic.
# See https://unicorn.bogomips.org/TUNING.html
worker_processes 1# Make sure we use the correct Gemfile on restarts
before_exec do |server|
ENV['BUNDLE_GEMFILE'] = "#{app_path}/Gemfile"
end# Speeds up your workers.
# See https://unicorn.bogomips.org/TUNING.html
preload_app true

Create Docker file for Nginx:

Next create the “dockerfile-nginx” file in config/containers folder and copy the following code content into it. For this rails application, we will use nginx as our web server. (For development environment this will not be required)

# build from the official Nginx image
FROM nginx# install essential Linux packages
RUN apt-get update -qq && apt-get -y install apache2-utils# establish where Nginx should look for files
ENV RAILS_ROOT /var/www/docker_example# Set our working directory inside the image
WORKDIR $RAILS_ROOT# create log directory
RUN mkdir log# copy over static assets
COPY public public/# copy our Nginx config template. Nginx.conf file is nothing but a virtual host file.
COPY config/containers/nginx.conf /tmp/docker_example.nginx# substitute variable references in the Nginx config template for real values from the environment
# put the final config in its place
RUN envsubst '$RAILS_ROOT' < /tmp/docker_example.nginx > /etc/nginx/conf.d/default.conf# Use the "exec" form of CMD so Nginx shuts down gracefully on SIGTERM (i.e. `docker stop`)
CMD [ "nginx", "-g", "daemon off;" ]

Update Database Host Name:

Modify your database host name as “host: db.local” in the database.yml file. This is configured in the compose configuration file. This will connect to the postgres which is running as the container.

Up & Running the Application:

You can run the below commands in the application root path.$ sudo docker-compose build
# this will install docker images ubuntu, postgres, nginx, ruby, etc..$ sudo docker-compose run app rake db:create db:migrate
# this will setup db and default data.#you can run any rake command with the above format.$ sudo docker-compose up
#this will start the containers up and running.

The first time, this build command will take a while because it require to download all of the Docker images that we configured in the compose file for this rails application.

After that, all you require is to run docker-compose up command and the application up will be done in a few seconds. This completes our aim to dockerize the application. You can access your application in the browser with the url mentioned in your nginx conf file like http://your-domain.com/

Other developers can also just clone your recent code from github and they can run the above up & running command to setup the app for development.

Stop, Start, Down the containers:
You can stop / start the application using the below commands:

$ sudo docker-compose stop
$ sudo docker-compose start
$ sudo docker-compose down

Stop & start will just stop & start the services, but the containers will not be down / removed. Down will stop the containers and will remove them. If we run the down command, we have to run the up command to create all containers and start it again.

We have now seen the process to successfully dockerize an existing Ruby on Rails application.

Do you want to know more about Docker? Or would you like to Dockerize your existing application or new Rails / Golang application? Please get in touch with us. Contact Us Now