Why use Docker

First of all, why should I use Docker in a development environment when it’s better to build it directly on my local system?

Using Docker in a development environment provides the following benefits.

  • Solidify the development environment into a configuration file, which makes it easy for new developers to start.
  • Isolating system dependencies across projects to avoid interactions.
  • Make the development environment as close to the production environment as possible to avoid potential problems due to system inconsistencies.

The more projects you manage, the more you will appreciate the benefits of containerizing your development environment.

Introduction to Docker Compose

For projects like Rails, in addition to dependencies on the operating system, there are usually external services like PostgreSQL and Redis. A single Docker container is not enough to manage all development dependencies, so Docker Compose is needed.

💡 Docker’s best practice is to run one process per container to easily manage container state and logging.

Docker Compose is a tool that comes with Docker Desktop that defines multiple services needed by an application via YAML configuration files, and then builds and starts them together.

Using Docker Compose is divided into about three steps.

  1. Write the project’s Dockerfile for building the project image.
  2. Write the project’s docker-compose.yml configuration, which defines the services needed to start the project.
  3. Start the application with the docker compose up command.

The contents of docker-compose.yml look like this.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
version: "3.9"
services:
  web:
    build: .
    ports:
      - "5000:5000"
    volumes:
      - .:/code
      - logvolume01:/var/log
    links:
      - redis
  redis:
    image: redis
volumes:
  logvolume01: {}

As you can see from the above example, the Compose file can define multiple services, each corresponding to a container. service content includes the image to be used, the open ports and the local directory to be mounted, etc. The detailed syntax of docker-compose.yml can be found in the official documentation.

Build a Rails application development environment

The following shows how to build a Rails development environment with Docker Compose.

💡 Here’s the chicken or the egg question. Our goal is to put all our dependencies in Docker, but how do we create a Rails project if we don’t have Ruby installed on our local system? rails/) provides a bootstrap approach, building an initial image, starting the container to create the project, and then rebuilding the image.

I chose a different approach: first open a temporary Ruby container, create the project inside the container, and later build the image needed for development.

Create a new project

In order to create a Rails project, start a temporary Ruby container first.

1
$ docker run -it -v $(pwd):/app -w /app ruby:3.0 bash

To install the Rails gem inside the container.

1
/app# gem install rails

Then create the project.

1
/app# rails new myapp --database postgresql --skip-bundle

The --skip-bundle argument is used here because this is only a temporary container and the bundle will be executed later in the development container.

Now that the temporary container has completed its mission, press ctrl-d or type exit to exit the container.

Adding a Dockerfile

Add a Dockerfile file to the project directory and enter the following.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
FROM ruby:3.0

# ruby 镜像预设的这个环境变量干扰了后面的操作,将它重置为默认值
ENV BUNDLE_APP_CONFIG=.bundle

# 如果需要安装其他依赖,取消这段注释
# RUN apt-get update && apt-get install -y --no-install-recommends \
#   nodejs \
#   npm \
#   postgresql-client

WORKDIR /app

This is a minimalist image of the Rails development environment, and you can use apt-get to install other system dependencies if needed.

You don’t need to build the image yet, we’ll build it together later with the docker compose command.

Add docker-compose.yml

Add the docker-compose.yml file to the project directory and enter the following.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
version: "3.9"

services:
  web:
    build: .
    command: bin/rails server -b 0.0.0.0
    volumes:
      - .:/app
    ports:
      - 3000:3000
    depends_on:
      - postgres
  postgres:
    image: postgres:13
    environment:
      POSTGRES_PASSWORD: postgres

Here web and postgres services are defined. web service builds an image based on the Dockerfile file in the current directory and mounts the current directory to the /app directory in the container, releasing port 3000 and adding a startup dependency on the postgres servcie. postgres service will use the postgres image and set the initial password via environment variables.

Build the image

Execute the following command.

1
$ docker compose build

Docker Compose will read the configuration in docker-compose.yml and build the image accordingly.

You have to re-execute this command every time you modify Dockerfile.

Go to the command line

Execute the following command.

1
$ docker compose run web bash

This will start the web service container and open a bash shell where we can execute the commands we need to execute for local development, such as bundle install, bin/rails g, and so on.

Any later operations that need to be performed inside the container will be executed through this shell.

Execute bundle

First execute the following command inside the container.

1
/app# bundle config set --local path vendor/bundle

This command sets the bundle installation directory to the vendor/bundle directory under the project. This is because I don’t want to have to rebuild the image every time I update the Gemfile during development.

Then execute bundle .

1
/app# bundle install

❗️ Remember to add vendor/bundle to .gitignore .

Preparing the database

Before creating the database, you need to modify the database settings of the Rails project, because in the environment built by Docker Compose, PostgreSQL and Rails processes run in different containers, similar to different hosts, and the service name is their respective network name.

Modify database.yml and add the following to the development and test paragraphs.

1
2
3
  host: postgres
  username: postgres
  password: postgres

Then execute the following command.

1
/app# bin/setup

After that, the corresponding database will be created.

Start the web service

After the above preparations, it is time to start the web service. Open another terminal and enter the following command.

1
$ docker compose up

Once the launch is complete, request http://localhost:3000/ and you will see the Rails launch page.

Rails launch page

Press ctrl-c to close the servcie.