Ruby on Rails Developer Series: Creating a Docker Container Around Your Application

Written by: Evan Glazer
4 min read

Welcome to the third post in the Ruby on Rails Developer Series. In this part, our goal is to add a Docker container to our application so we can deploy our packaged application on a server. This will enable us to create a continuous integration process while building our environment in a matter of seconds. We'll cycle through learning about the benefits of Docker and how to build a basic Docker image. The series theme is to make you feel confident as an engineer in building a structured project with Ruby on Rails.

You can do this!

What is Docker?

Docker allows you to package up an application or service with all of its dependencies in an image. The Docker image contains code, system libraries and whatever else you may need to install on a server.

What makes Docker different from a virtual machine?

Regarding a virtual machine (VM), you tend to have an entire guest operating system for each application you want to isolate. The time to reboot a server is also a longer process than being able to spin up a server in a few seconds. I think that says everything - when you can test your application in minutes vs hours.

What are the benefits?

  1. Encapsulation of your Application

  2. Expand Development Teams

  3. Scalability

Bottom Line

Well, it is safe to assume that we can spin-up another server in seconds .. if it was Pokemon Go .. we wouldn't have to waste time dealing with non-trivial developer workarounds.

Let's begin!

PreRequisites

- Install Docker
- Build our Dockerfile
- Map out our application for our Docker Compose file
- Spinning up our application!

Docker Compose relies on Docker Engine and we will need to set up our machine with docker to begin:

Install Docker below with your given OS:

Mac

Windows

This will install Docker and the tools including Docker Compose and other supporting apps.

Once installed, it's time to set up our Rails/PostgresSQL application in our Dockerfile.

Define the project

We will need to create a file named Dockerfile:

I would reference this page for Linux apt-get command information. And for basic bash command understanding - I would recommend here

FROM ruby:2.5
RUN apt-get update -qq && apt-get install -y nodejs postgresql-client
RUN mkdir /myapp
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
COPY . /myapp
# 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"]
EXPOSE 3000
# Start the main process.
CMD ["rails", "server", "-b", "0.0.0.0"]

Entry point or entrypoint?

An entrypoint file is a script based file ran when a container is started. We will use an entry point to fix a rails-specific issue that prevents the server from restarting when server.pid pre-exists.

entrypoint.sh

#!/bin/bash
set -e
# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid
# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"

What do these commands do?

set -e - allows you to change the values of shell options and set the positional parameters, or to display the names and values of shell variables. And -e is set to exit immediately if something returns a non-zero status.

rm -f - allows you to remove a file path that is set. And -f is to force the removal even if it has dependencies or will cause issues.

exec "$@" - typically used to make the entry point a pass-through that then runs the Docker command. It will replace the currently running shell with the command that $@ is pointed at ($@).

How does the magic happen for development locally?

docker-compose.yml is where we describe the services the application contains. One service we will include is the postgres database and rails application, redis, possibly sidekiq. Although, this file will configure and bind the services together to properly execute expose the correct ports, etc.

version: '3'
services:
  db:
    image: postgres
    volumes:
      - ./tmp/db:/var/lib/postgresql/data
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db

Build the project

docker-compose run web rails new . --force --no-deps --database=postgresql

Context: We're building the image for the web service which utilizes the Dockerfile. Then it runs rails new inside the container and we generate a fresh app.

You should see a list of files like below:

If you add gems in your Gemfile, you will need to rebuild with the following command: docker-compose build.

Modify Database Config

default: &default
  adapter: postgresql
  host: localhost
  port: 5432
  encoding: utf8
  pool: 5
  database: rails-json-api
  username: development
  password: development
development: &default
  adapter: postgresql
  host: localhost
  port: 5432
  encoding: utf8
  pool: 5
  database: rails-json-api-dev
  username: development
  password: development

Create database and run application!

docker-compose run web rake db:create

Thats it!

You should be able to view http://localhost:3000 on the web browser to see the rails application boot up.

Overall, it really was that simple to dockerize our rails application! It could be a scary area, in my opinion, to enter for newer developers, although it really does help scale companies quickly.

In the last part of this series, we're going to explore how to secure your application with best practices and some cool gems that keep the community very strong!

Additional resources

Stay up to date

We'll never share your email address and you can opt out at any time, we promise.