Wether you are running apps on your own infrastructure or deploying to the cloud, there are many reasons to containerize your Rails application. However, the template generated by
rails new doesn’t help you there, as the generated code has to be adapted to run in Docker. You will need to do some modifications to the app and add a carefully designed Dockerfile, which is important for speeding up build times and reducing the image size. That’s what this post is about.
The text is divided in 3 parts. In this first post, we take a look at what you should change in your application code in order to make it suitable to run inside a docker container. Part 2 is about building a base image that contains the prerequisites needed for a typical Ruby on Rails app. Finally, on part 3, we show how to add the application files, create and run your container. A basic understanding of Docker concepts and Dockerfile syntax is required for understanding the content.
One very important decision that will impact the way you build your image is how you want to handle the assets. Particularly, you should answer these two questions:
There are no correct answers here, as this depends on many factors that are application or environment-specific. In our use case, which is described here, we assume a simple scenario where:
- We don’t want developers to care about building assets for production. The CI script should handle it.
- In a small scale environment, the assets can be served by the same container that runs the application.
Now let’s get started. The first thing to notice when containerizing your application is that you should not have any configuration data in your docker image. Instead, the configuration files should reference environment variables that are defined elsewhere, outside the image. This allows you to use the same image in different environments, just by setting the appropriate values for the variables.
To achieve this, you will have to look at the source code searching for files that hold database connection settings, URLs for external services, and any other parameters that might change depending on the environment. Typical files to look are
config/initializers/*. You should replace hard-coded values with environment variables. Here is what an example
database.yml that references environment variables, using postgresql as an example:
Providing values for the environment variables
The values for the environment variables need to be informed when you run your container. If you start your application using Docker Compose, it will automatically load environment variables from a file called
.env in the working directory, if it finds one. You can also supply additional .env files in your
docker-compose.yml, if you like.
For development, you might find it convenient to use the dotenv gem. It will allow you to run your development server in the same way you normally would, while loading environment variables from your
.env file. You can also have multiple files, and
dotenv will combine them to form your configuration.
Here is an example configuration with 3 files:
Should I commit or .gitignore my .env files?
It is a good practice to commit your
.env files to source control. You should avoid, however, including sensitive data like database passwords. For these, you should look for solutions like Docker Secrets or Kubernetes Secrets, depending on your production environment.
Dotenv also supports the use of local env files (.env.*.local). These allow you to make settings local to a specific machine and should be gitignored.
Did you notice the
RAILS_LOG_TO_STDOUT variable that appeared in the example
.env?. It is not a good practice to write data to the container’s filesystem, so this setting is used to send the logs to standard output. The default-generated Rails configuration will look for this variable when running in staging or production environment.
Another important variable to configure are
RAILS_ENV, that defines if you are in development, staging or production and
RAILS_SERVE_STATIC_FILES, to make the static assets available through the same server.
This is how you set up your application to run smoothly inside a Docker Container. In the next post we will start to look at how to transform it in a docker image by creating an appropriate Dockerfile.