Dockerfilebackendsoftware developmentrails 7 apiAPI

9 min read

|

August 13, 2024

How to Containerize an Existing Rails 7 API with Docker

Step-by-step tutorial on containerizing Rails 7 API apps using Docker. Discover setup tips, best practices, and more.



Chapters

Introduction

What is a Dockerfile?

Docker Compose

Setting the Scene

The Redis Service

Volumes

Installation guide

Next up

Introduction

Today, I will show you how to containerize your existing Rails 7 API application with Docker. Docker is a platform that simplifies the process of developing, deploying, and managing applications using containerization technology. It helps you build and deploy software super quickly, making the whole process smoother. It's like having a magic box that keeps everything organized and running smoothly. 

From Rails 7.1, Docker configuration is included by default when generating a new Rails app. I have written this article for those who upgraded their Rails application to Rails 7 from an older version. I will walk you through the whole process of containerization for Rails 7.0.3 and Ruby 3.2.2 and demonstrate how to do it for the local environment. If you’re looking to deploy and set up a Rails 7 application on a remote server, check out how to do it here.

What is a Dockerfile?

A Dockerfile is a script-like file that specifies the configuration and dependencies needed to build a Docker image. It acts as a blueprint for creating lightweight, portable containers, ensuring consistent application deployment across different environments. It goes into the project root directory.

Type this in your project root directory and name it Dockerfile:

FROM ruby:3.2.2-bullseyeRUN apt-get clean all && apt-get update -qq && apt-get install -y build-essential \                libpq-dev \    curl gnupg2 apt-utils  postgresql-client postgresql-server-dev-all git \                 libcurl3-dev cmake \    libssl-dev pkg-config openssl imagemagick fileWORKDIR /usr/src/appENV RAILS_SERVE_STATIC_FILES trueENV RAILS_LOG_TO_STDOUT trueCOPY Gemfile Gemfile.lock /usr/src/app/RUN bundle -vRUN bundle config --global frozen 1 && \      bundle install && \      bundle clean --force && \      rm -rf /usr/local/bundle/cache/*.gem  && \      find /usr/local/bundle/gems/ -name "*.c" -delete  && \      find /usr/local/bundle/gems/ -name "*.o" -deleteCOPY . /usr/src/appRUN bundle exec rake DATABASE_URL=postgresql:does_not_exist assets:precompileEXPOSE 3000CMD ["bundle", "exec", "rails", "s", "-b", "0.0.0.0"]

This Dockerfile sets up a Ruby environment based on version 3.2.2-bullseye. It installs necessary system dependencies, including PostgreSQL, Git, and image processing libraries. It then configures the working directory and environment variables for a Rails application. The Gemfile and Gemfile.lock are copied into the container, and Ruby gems are installed and cleaned up.The line  COPY . /usr/src/app copies the entire local directory (where the Dockerfile is located) into the container's /usr/src/app directory. This includes all application code and files necessary for the Rails application to run.Finally, the application code is copied, and database assets are precompiled using rake.

Docker Compose

Docker Compose is a tool for defining and managing multi-container Docker applications. It allows developers to describe the services, networks, and volumes for an application using a simple YAML file (docker-compose.yml), making it easier to orchestrate the deployment of complex applications composed of multiple interconnected containers. With Docker Compose, developers can define configurations for their development, testing, and production environments, streamlining the process of container-based application deployment and management.

Setting the Scene

First, we declare the version of Docker Compose we're using. It's like saying, "Hey, Docker, I'm speaking your language, version 3.6!" Now, let's meet our main characters: app and DB.

The App Service

The app service is our star player here. This is where our Rails application lives and breathes. Since it depends on our database (DB), make sure the database is up and running first.We're telling Docker to start the Rails server (bundle exec rails s) and bind it to port 3000 on all network interfaces (-b '0.0.0.0'). This allows us to access our Rails app from outside the Docker container.We've also got some ports mapped here. Port 3000 on our host machine connects to port 3000 inside the container. This is how we'll access our Rails app in the browser.Oh, and don't forget the environment variables! We're loading them from a .env file, which is handy for keeping sensitive information out of our code.

The DB Service

Next up, we have the DB service, starring PostgreSQL as our trusty database. We're using the postgres:16.2-bullseye image for this. We want our database to be resilient, so we've set it to restart always if it stops unexpectedly. We're also mounting a volume named data to persist our Postgres data. This ensures that even if the container goes down, our data stays safe. In order to properly configure your Postgres database with docker-compose you need to add a database.yml file in the config folder.

In the context of using Docker Compose to manage multi-container applications, it's essential to ensure seamless communication between the different services within the containers. Each service defined in Docker Compose runs within its own isolated container. 

When containers need to communicate with each other, they use the service names defined in the docker-compose.yml file as host names. This allows them to find and connect to each other over the Docker network. That’s why the hostname in the database.yml file needs to match the name of the Postgres service defined in your docker-compose.yml file.

So, the reason you need to set POSTGRES_USER: postgres in the Docker Compose environment and specify username: postgres in the database.yml file is to ensure that your Rails application knows how to connect to the Postgres database running in the Docker container. By keeping these configurations consistent, your Rails application will be able to successfully connect to the Postgres database using the specified username and other connection parameters.

Your ./config/database.yml should look like this:

default: &default adapter: postgresql   encoding: unicode   pool: 5   host: db # Same as the name of your postgres service in docker-compose.yml   port: 5432   username: postgres # This needs to be the same as the one in docker-compose.ymldevelopment:   <<: *default   database: app_name_development # Change this to your application name _developmenttest:   <<: *default   database: app_name_test # Change this to your application name _test   # Remaining part is optional, only in case you are going to deploy your application   production:    <<: *default    database: app_name_production    username: <%= ENV['POSTGRES_PRODUCTION_USER'] %>    password: <%= ENV['POSTGRES_PRODUCTION_PASSWORD'] %>  staging:    <<: *default    database: app_name_staging    username: <%= ENV['POSTGRES_STAGING_USER'] %>    password: <%= ENV['POSTGRES_STAGING_PASSWORD'] %>

The Redis Service

If you are using Redis for whatever reason (caching, WebSockets, etc.), you need to add the Redis service to docker-compose.yml. In the volumes section, I provided you with two options. The first one is if you don’t need the data that you are storing in Redis after the container reboot. The second one is if you want to persist data from Redis no matter what happens to the Redis container.

Volumes

Finally, we've got this volumes section where we define our named volume data. This is where our Postgres data will be stored. The second one, “redis_data”, is optional, you need to have it only in case you have redis in Docker Compose services, and you have chosen the second option in redis/volumes (redis_data:/var/lib/redis/data)Type this in your project root directory and name it docker-compose.yml

version: '3.6'services: app:   depends_on:       - db       - redis # Only if you have the “redis” as service   command: bundle exec rails s -p 3000 -b '0.0.0.0'   ports:   - 3000:3000   build: .     tty: true     stdin_open: true     env_file:       - ".env" db: # This is needs to match the “host” variable in the database.yml  image: postgres:16.2-bullseye  restart: always  environment:     POSTGRES_USER: postgres # This need to match the “username” in the database.yml      POSTGRES_HOST_AUTH_METHOD: trust   volumes:     - data:/var/lib/postgresql/data/ redis: # Optional   image: "redis:7-alpine"   volumes:    - ./tmp/redis_data:/var/lib/redis/data #1 In case you don’t need to save data    - redis_data:/var/lib/redis/data #2 Use this one if you need to persist data   volumes:   data:   redis_data: # You need this only if you have chosen option #2 in redis/volumes

Installation guide

Follow one of the installation guides below for your operating system:Linux: https://docs.docker.com/get-started/Windows and Mac: https://www.docker.com/products/docker-desktopBefore proceeding, you should have Docker installed. Update your Gemfile with ruby '3.2.2'. Execute the following commands in the terminal:

$ bundle lock --add-platform aarch64-linux $ bundle update –bundler  $ bundle install

 Before we start our containers, we must first create their images using the command $ docker-compose build. This command is crucial as it prepares our containers with all the required dependencies and configurations, ensuring they're ready to run smoothly within our Docker environment.

Following the image-building process, the subsequent step involves launching our Docker containers. This is accomplished through the command $ docker-compose up -d. Here, -d signifies the containers will be started in detached mode, allowing them to run in the background. This command orchestrates the execution of our containers based on the configurations outlined in the docker-compose.yml file, enabling our application to become accessible and operational within the Docker environment.

That’s it; our containers are running. We just need to do one more thing, and that thing is the database setup!  We can do that simply: $ docker-compose exec app bin/rails db:create db:migrate db:seed      You can also access the Rails console with $ docker-compose exec app bin/rails c.Make sure everything is correct by testing your Rails 7 API endpoints on the localhost:3000.

Next up

This is how you containerize the existing Rails 7 API with Docker. In this blog series, I also covered:

Share


WRITTEN BY

author

Software Engineer

Dario truly cares about the projects he works on and it shows in the quality of his work. He's always picking up new skills and loves to share what he learns with you.

UP NEXT

Leveraging GitHub Actions & DockerHub for a Seamless Rails 7 API Setup

Master seamless deployment of Rails 7 API applications with GitHub Actions and DockerHub. Discover tips and tricks for fast, effective deployment.