I typically build and install my experimental applications to Heroku as it's a quick and easy way to deploy Ruby on Rails applications to production. This is especially useful when my apps are usually early prototypes of various ideas. However, for one of my side projects, I decided to host this particular prototype on my home server and felt the most logical route was to build a Docker container for my app. I found myself poking around a number of different sites to finally deploy my Rails 6.1 app. Here are the summarized steps

Setting up the Rails app

I had to make a couple of changes to the Rails app.

Change the following in your /config/environment/production.rb file

config.require_master_key = true

Change the following in your config/database.yml file

production:
  <<: *default
  url: <%= ENV['DATABASE_URL'] %>

Create Docker files

I decided to put the Docker file in the root directory of my Rails app and here's the content of the Dockerfile I created

FROM ruby:3.0.1

# replace shell with bash so we can source files
RUN rm /bin/sh && ln -s /bin/bash /bin/sh

RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs ghostscript

RUN mkdir -p /app
RUN mkdir -p /usr/local/nvm
WORKDIR /app

RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -
RUN apt-get install -y nodejs

RUN node -v
RUN npm -v

# Copy the Gemfile as well as the Gemfile.lock and install
# the RubyGems. This is a separate step so the dependencies
# will be cached unless changes to one of those two files
# are made.
COPY Gemfile Gemfile.lock package.json ./
RUN gem install bundler
RUN bundle install --verbose --jobs 20 --retry 5

RUN npm install -g yarn
RUN yarn install --check-files

# Copy the main application.
COPY . ./

# Expose port 3000 to the Docker host, so we can access it
# from the outside.
EXPOSE 3000

# Pre-compile web assets
RUN bundle exec rails webpacker:compile
RUN bundle exec rails assets:precompile

# The main command to run when the container starts. Also
# tell the Rails dev server to bind to all interfaces by
# default.
CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0"]

A couple of things to note.

  1. I'm currently using Node version 14 as that's the latest version at the time I created the app. You might want to update this for a newer version in the future
  2. I first tried to run bundle install with just the production gems but found that there was a whole slew of gems from the development section that it needed to run the app. Found that it was easier to just run the full bundle install instead.

You can use a .dockerignore file to reduce any unnecessary bloat for your Docker image. A .dockerignore file works very similarly to a .gitignore file and most of the contents can be duplicated as the files that you don't need in your repositiory should also not be in your running application.

This is my .dockerignore file configuration

# Ignore bundler config.
/.bundle

# Ignore the default SQLite database.
/db/*.sqlite3
/db/*.sqlite3-journal
/db/*.sqlite3-*

# Ignore all logfiles and tempfiles.
log/*
tmp/*
!log/.keep
!tmp/.keep

# Ignore pidfiles, but keep the directory.
tmp/pids/*
!tmp/pids/
!tmp/pids/.keep

# Ignore uploaded files in development.
storage/*
!storage/.keep

.byebug_history

# Ignore master key for decrypting credentials and more.
config/master.key

# Ignore local javascript files
node_modules
yarn-error.log
yarn-debug.log*
.yarn-integrity

# Ignore macOS specific files
.DS_Store

# Ignore git related files
.git/*
.gitignore

# Ignore Rails non-application files
coverage/*
features/*
test/*
spec/*

# Ignore Docker files
.dockerignore
Dockerfile

Once this is set up, you're ready to create your Docker image. To do this, run the following command from the project root folder as this is also where your Docker file lives

docker build -t [full url of where the image will be hosted]:[tag] .

This should result with a Docker image for your app. Now you're almost ready to run your docker container. Just before you do that, it's probably best to set up an environment variable file for the project as this allows you to minimize the amount of typing to run the container. I called mine docker.env and put it in the config directory of the app. Here's what I put in my file.

RAILS_MASTER_KEY=[from config/master.key file]
RAILS_ENV=production
RAILS_LOG_TO_STDOUT=true
RAILS_SERVE_STATIC_FILES=true

Here are the things that you want to pay attention to

  • The RAILS_MASTER_KEY variable allows your app to decrypt your credentials.yml file at run time.
  • The RAILS_LOG_TO_STDOUT variable allows you to output your log to Docker so you can debug your app as necessary
  • The RAILS_SERVER_STATIC_FILES variable allows you to pre-compile your assets so your app doesn't have to do it at run time.

Now that is done, you're ready to run your app by running the following command:

docker run --env-file config/tems_core.env -p 80:3000 --name [application name] [docker container url]:[tag]

References:

Better Programming

Yi Zeng's Blog

Setting up Rails 6.0 to run with Docker