Dockerizing Rails Applications Part 2: Automation

Simplify the Docker build for your Rails applications by using Makefiles

Photo by Austin Neill on Unsplash

In my previous article, I discussed some of the best practices for writing Docker files, such as using an entrypoint and reducing the number of RUN instructions per Dockerfile — as well as using custom Docker base images.

Building and pushing the Docker images was briefly mentioned at the end of the article by providing the commands needed for doing the job. These commands are presented again below.

$> docker build -t ${IMG}:${IMG_TAG} Dockerfile

$> docker push ${IMG}:${IMG_TAG}

Docker commands for building the images are simple and easy to use; however, it can be very long and complex, especially if building the Docker image requires extra steps, such as:

Passing build args to the docker build command

command Installing application dependencies before building the Docker image

Generating labels to be attached to the Docker images

There’s a need to compile the source code before building the Docker image

The above points can make building the Docker image much more complex, and, as a result, the docker build command will be more complex and long. In addition, in the case of managing several applications, this means each of the applications will have a different unique set of commands for building its Docker image, especially if these applications are built using different programming languages.

One idea to solve this problem is to hide the complexity of building Docker images behind a unified interface across all of the Docker services. The requirements for this tool or interface is as follows:

Building and pushing Docker images should be done with simple commands

Building and pushing Docker images should be done in the same way across different services

We can implement this interface simply by using a tool from the old days called make. This command is usually installed on macOS and on Linux systems, and it’s used for managing and compiling the source code process and for defining the commands needed to compile and install software applications. Makefiles are the files that define the instructions for compiling and building the source code that’ll be used by the make command.

To solve our issues, we’re going to write a Makefile that defines how to build and push Docker images, and then we’re going to use the make command to do the actual process and call the needed docker commands under the hood.

The first section in the Makefile file should be the list of all the supported parameter-configurations items. This is a good idea to make the Makefile more readable and flexible.

These configuration items can be overwritten using environment variables. For instance, we can set up a default namespace and still build Docker images and push them to aother namespace using the same make command with an extra environment variable — e.g., make build and NAMESPACE=overwritten make build .

Below is a list of some of these configurations items, along with a description for each of them.

NAMESPACE : The project namespace will be used as the namespace for the Docker image

: The project namespace will be used as the namespace for the Docker image REGISTRY : The URL of the Docker registry used to host the Docker image

: The URL of the Docker registry used to host the Docker image BRANCH : The Git branch used to build the Docker image. The Docker image will be tagged with the branch name. For instance, Docker images built from the master branch will be tagged with the master tag, while images built from the develop branch will have the develop tag.

Below is a list of some config items that should be defined and used by the make commands. The values of these variables either have static values, such as the image_name , or are generated automatically using a Git command like commit_hash .

IMAGE_NAME : This should define the Docker image name, and it can be the same as the application name usually

: This should define the Docker image name, and it can be the same as the application name usually IMAGE_FULL_NAME : This is the full Docker image name included in the registry and the namespace

: This is the full Docker image name included in the registry and the namespace BRANCH_TAG : This is the branch tag that’ll be used to tag the Docker image. This item relays on the branch, but it makes sure the value can be used as a tag — i.e., the tag doesn’t include any special characters like / .

: This is the branch tag that’ll be used to tag the Docker image. This item relays on the branch, but it makes sure the value can be used as a tag — i.e., the tag doesn’t include any special characters like . COMMIT_HASH : The last commit hash in the branch used to build the Docker image. We can use this item to tag the Docker image and to add a label to the Docker image so it’s easier to find out what code version is running inside the Docker image.

: The last commit hash in the branch used to build the Docker image. We can use this item to tag the Docker image and to add a label to the Docker image so it’s easier to find out what code version is running inside the Docker image. COMMAND : The default command supported by the Docker image. For a Rails application, this could be the rails server command.

: The default command supported by the Docker image. For a Rails application, this could be the command. PORT : The ports needed for the service. These ports are going to be exposed to the host system.

: The ports needed for the service. These ports are going to be exposed to the host system. CONTAINER_ENV : The application default environment variables will be linked to the containers created by the make command

: The application default environment variables will be linked to the containers created by the command BUILD_PARAMS : The options list of the docker build command

: The options list of the command BUILD_ARGS : The list of the supported Docker --build-arg

The next step is to start defining the supported commands of the Makefile . Below is a list of the proposed set of make commands that can make building, pushing, and testing Docker images much easier:

make config : This command will be used to perform all the actions needed to be ready for building the Docker images. These actions could be installing necessary packages for the build process, compiling the source code of the packages, or generating config files. Below is a proposed config implementation for a Rails applications that packages all of the gems into the local vendor directory.

make build : This command will be used to build and tag the Docker image on the host node. Below is a proposed implementation for Rails applications that build the Docker image and tags it with both the COMMIT_HASH and the BRANCH_TAG .

make shell : This command will create a new container from the generated Docker image and open a shell session inside the container. Below is a proposed implementation to start a sh shell inside a temporary container.

make run : This command will be used to create a container from the generated Docker image in the foreground and to execute the application default command. The below snippet shows an implementation of a run command that’ll start a temporary container and execute the command defined in the variable COMMAND inside the container.

run:

docker run --rm --name ${NAME}-${BRANCH_TAG} ${CONTAINER_ENV} -it ${PORT} ${IMAGE_FULL_NAME}:${COMMIT_HASH} ${COMMAND}

make start : This command will be used to create a container from the generated Docker image in the background and execute the default command for the application. The below snippet shows an implementation of a start command that’ll start a container in the background using the command defined in the variable COMMAND .

make stop : This command will stop the container created by the command make start . This can be done simply by using the docker rm -f command.

stop:

docker rm -f ${NAME}-${BRANCH_TAG}

make push : This command will be used to push the tags of the generated Docker image from the host node to the Docker registry. The below implementations will push the branch tag and commit hashtags to the Docker registry. In addition, if the used branch is the master branch, the push command will also push the latest tag.

make release : This command will be used to automate the config , build, and push commands. Basically, it’ll call tasks defined in these subcommands.

release: config build push

make clean : This command will be used to remove all of the generated files or Docker images using the config and build commands. The below implementations of the clean command remove Docker images generated by the build command — as well as the Ruby gemset created by the config command.

A complete Makefile implementation is presented below. This implementation can be used as a template for Docker Makefiles and for generating Docker images.