Dockerfile for Rails app

A Dockerfile is made up of various instructions such as FROM, RUN, COPY, and WORKDIR, each capitalized by convention. Let’s look at a specific example.

Here is a basic Dockerfile for running our Rails app.

FROM ruby:2.7 
RUN apt-get update -yqq 
RUN apt-get install -yqq --no-install-recommends nodejs 
COPY myapp /usr/src/app/ 
WORKDIR /usr/src/app 
RUN bundle install 

Every image has to start from another, preexisting image. For that reason, every Dockerfile begins with a FROM instruction, which specifies the image to be used as its starting point. Typically, we will look for a starting image that is close to what we need but more general. That way we can extend and customize it to our needs.

Dockerfile explanation

FROM instruction

The first line of our Dockerfile is:

FROM ruby:2.7.2 

This is saying that our image will be based on the ruby:2.7.2 image, which, as you have probably guessed, has Ruby 2.7.2 preinstalled. We have chosen to start from this image because having Ruby installed is our biggest requirement and this image gets us most of the way there.

RUN instruction

The next two lines of our Dockerfile are RUN instructions, which tell Docker to execute a command:

RUN apt-get update -yqq 
RUN apt-get install -yqq --no-install-recommends nodejs 
  • The command apt-get is used to install software on Debian (and some other) Linux distributions. We are using it in our Dockerfile because the official Ruby image that our image builds on is based on Debian, and more specifically, on a version called Stretch.

  • The apt-get update command tells the package manager to download the latest package information. Many Dockerfiles will have a similar line because without it apt has no package information at all, and therefore won’t be able to install anything.

  • The -yqq option is a combination of the -y option, which says to answer “yes” to any prompts, and the -qq option, which enables the “quiet” mode to reduce the printed output.

  • The apt-get install command installs Node.js, a prerequisite for running Rails. The --no-install-recommends says not to install other recommended but non-essential packages because we do not need them, and we want to keep our image size as small as possible by not installing unnecessary files.

Mount the volume

Let’s shift gears briefly before we look at the next line of our Dockerfile.

Remember that images, and the containers they spawn, are separate from our local machine. They are isolated, sandboxed environments. Therefore, we need a way to include some of our local files inside the containers we run.

A mounted volume acts as a shared directory between the container and the host and is one way we can make local files accessible inside the container.

However, mounting a volume has a serious downside if it is the only way you get files into a container. Files in a volume are not part of the image itself; they are overlaid onto the image at runtime (when you start a container). If the mounted files were essential, the image would not function without them, but the whole point of images is to package everything they need in order to run. Therefore, it is good practice to bake any needed files into the image itself.

COPY instruction

The next line in our Dockerfile serves exactly this purpose:

COPY myapp /usr/src/app/ 

This tells Docker to copy all the files from our directory myapp into /usr/src/app on the filesystem of the new image. Effectively we are saying, “Copy our Rails app into the container at /usr/src/app”. The source path on our local machine is always relative to where the Dockerfile is located.

WORKDIR instruction

By default, a container’s working directory is /, which does not contain our Rails app files since we copied those into /usr/src/app.

However, the WORKDIR instruction can help us fix the situation. It performs a change directory cd command and changes what the image considers its current directory. The next line in our Dockerfile uses it to set /usr/src/app as the working directory:

WORKDIR /usr/src/app 

You can use multiple WORKDIR instructions in your Dockerfile, each one remaining in effect until another one is issued. The final WORKDIR will be the initial working directory for containers created from the image.

RUN instruction

Finally, we come to the last line of our Dockerfile:

RUN bundle install 

The command is executed from the container’s current working directory, which in the previous command was set to be /usr/src/app. This will install the gems defined in our Rails project’s Gemfile, which are needed in order to start the application.

Get hands-on with 1200+ tech skills courses.