Dockerizing a Rails App

Gavin Carew
4 min readApr 17, 2023

--

The app I’m going to be using is called Viewing Party, and it’s a rails monolith I worked on at The Turing School. If you want to clone my repo so you can work along, you can find it here. I encourage you to use your own repo, though. Part of deploying is working through all the bugs, dependencies, and other nonsense.

Note: Sections with this formattingare either file names, or are meant to be run in the terminal.

The first step is to install Docker Desktop. You may want to try out their onboarding workflow as well.

Let’s get the basics set up first: a Dockerfile and an entrypoint. The Dockerfile specifies how to set up the environment to run your application, the entrypoint is a script, or a set of commands, that will run after your basic environment is built.

In the root directory of your app, create these two files with touch Dockerfile entrypoint.sh. We’re going to follow Docker’s official documentation fairly closely to get our app running locally in a container.

Add the following to Dockerfile:

FROM ruby:2.7.4
RUN apt-get update -qq && apt-get install -y nodejs postgresql-client
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install

# Add a script to be executed every time the container starts. Fixes a glitch with the pids directory by removing the server.pid file on execute.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

# Runs a rails server command to start the rails server, pointing it to local host.
CMD ["rails", "server", "-b", "0.0.0.0"]

If you’re using a different version of Ruby, change the version at the top of your Dockerfile.

Add your entrypoint script in entrypoint.sh:

#!/bin/bash
set -e
rm -f /myapp/tmp/pids/server.pid
bundle exec rake db:{drop,create,migrate,seed}
exec "$@"

The first line looks like a comment, but the kernel reads it. Do not skip it or you will get an exec format error. The third line helps head off an error you might have seen before, where Rails won’t start a server because it thinks a server has already started.

The database commands will reset the database. If you wanted a database to persist, you could remove the rake task, then run them manually only the first time you set up your database by runningdocker compose run web rake db:{create,migrate,seed} in the command line.

Finally, we need to make a Docker Compose file. From the docs:

This file describes the services that comprise your app (a database and a web app), how to get each one’s Docker image (the database just runs on a pre-made PostgreSQL image, and the web app is built from the current directory), and the configuration needed to link them together and expose the web app’s port.

We aren’t going to use Docker Compose to build our image when we want to deploy to cloud environments, but Docker Compose allows us to run our app locally. We can use the principles learned here when we start looking at cloud deployment and infrastructure as code tools later on.

Create a Docker Compose file with touch docker-compose.yml then add the following:

version: "3.9"
services:
db:
image: postgres
volumes:
- ./tmp/db:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: password
web:
environment:
MOVIES_API_KEY: ${MOVIES_API_KEY}
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

Here, you need to add your secrets from an environmental variable. Add gem 'dotenv-rails' to your Gemfile and bundle. In the root directory, touch .env. This .env file will be where you put all your secret keys. Mine is pretty simple, it just looks like this:

MOVIES_API_KEY=[YOUR_API_KEY]

If you’re following along with my repo, this API key is free from The Movie Database.

Any API keys can go in this file and they will be inserted into docker-compose when the image is built. Make sure to add .env to your .dockerignore and .gitignorefile. Now you’re safe to check in docker-compose to version control.

Finally, you need to configure where your app is going to look for a database by changing the default database connection. Development and test environments will inherit from this default configuration. In config/database.yml, adjust your default database like so:

default: &default
adapter: postgresql
encoding: unicode
host: db
username: postgres
password: password
pool: 5

If you haven’t already, start the Docker desktop app.

Run a one-time command to start a service:

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

Next, build a Docker image by running docker compose build. An image is a read-only, unchangeable template for creating containers. Any time you want to change the contents of a container, you will need to run this command. This might take a few minutes.

Finally, run docker compose up to start a container with the image you just built. Once your migrations have run, you should be able to visit localhost:3000 to see your app running from the container.

When you’re done with your container, run docker compose down to stop all services.

Review

Hopefully, you successfully got a containerized rails app running in your development environment. You may be wondering what the point is, but this is the middle ground between “hello world” and deploying an app like Airbnb (yes, originally built with rails!) to billions of users.

The same components of a Dockerfile, an entrypoint, and a build/compose file will be used whether you’re deploying to Cloud Run or Google Kubernetes Engine or Fargate, or any number of other massively scaling cloud environments.

Deploying to cloud environments can be a bit of a nightmare. The errors are less useful, the builds take longer, and everything is just a bit more opaque. Hopefully, you can come back to this local deployment and check all your components. Do I have a DB set up? Is it networked to my app? Are my secrets making it into the production environment correctly?

See you in part three… deploying to Google Cloud Run.

--

--