Demystifying Docker Compose
Tags: rails, docker, postgres
Published: 2019-11-21 23:45:28

---
title: Demystifying Docker Compose
published: true
description: A quick explaination of how to use Docker Compose for local development in a Rails and Postgres project.
tags: rails, docker, postgres
---

My latest project used Rails and Postgres. After fighting my Mac and the Postgres install for an hour, I gave up and used [Docker](https://www.docker.com/) instead.

Docker has always seemed kinda mystical to me and I really wanted to understand my setup this time instead of just copying a quick start. And since there is no better way to learn something than to teach it, I will share it with you!
 
There are three key files:
- Dockerfile
- docker-compose.yml
- database.yml

And two main commands:
- docker-compose build
- docker-compose up

You will need to have Docker installed.

I prefer to use the [Docker extension](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-docker) for [VSCode](https://code.visualstudio.com/) to interact with my running containers, so there are more commands you may need if you are not using that extension.

## Dockerfile

The Dockerfile is where the dependencies for your container are defined. I'll go through mine line by line.

`FROM ruby:2.6.3`
This is from what [image](https://hub.docker.com/search/?type=image) it is starting from, in this case the Ruby 2.6.3 image.

`RUN apt-get update -qq && apt-get install -y nodejs postgresql-client 
RUN gem install bundler`
Docker containers are VERY bare bones and the next lines were installing Nodejs, a Postgres client, and Bundler.

`WORKDIR /my-api`
This should be the folder where all your application code is in. It is setting the working directory so the next commands are easier.

`COPY Gemfile /my-api/Gemfile
COPY Gemfile.lock /my-api/Gemfile.lock`
Copying the Gemfiles to the working space in the container.

`RUN bundle install`
This will run bundler in the container.

`COPY . /my-api`
Copy all the other files into the working space.

`COPY Docker/database.yml config/database.yml`
This line can be skipped. The project was shared with others who had local installations of Postgres and I didn't want to override the config/database.yml in the repository. I created a Docker folder for my specific database.yml file. This command copies my database.yml file to the standard location for the container.
 
`EXPOSE 3000`
Only open the ports you need to have open.

`# Start the main process.
CMD ["rails", "server", "-b", "0.0.0.0"]`
Start rails.

So for this Rails and Postgres project, my full Dockerfile looked like this:
```text
FROM ruby:2.6.3
RUN apt-get update -qq && apt-get install -y nodejs postgresql-client 
RUN gem install bundler
WORKDIR /my-api
COPY Gemfile /my-api/Gemfile
COPY Gemfile.lock /my-api/Gemfile.lock
RUN bundle install
COPY . /my-api
COPY Docker/database.yml config/database.yml 
EXPOSE 3000

# Start the main process.
CMD ["rails", "server", "-b", "0.0.0.0"]
```

## docker-compose.yml

This file connects our newly defined Rails container to a Postgres database.

`version: '3'`
All docker-compose files start with this. Don't change the version, just leave it.

`services:`
This defines the services (aka containers) to be built and how they are connect.

`  db:
    image: postgres`
The first service is a Postgres database. This is pulling the base Postgres image. I do not need any add-ons or customizations for my database so a Dockerfile for this service isn't necessary.
If you want data to persist, you will have to add a volume.
`volumes:
      - ./tmp/db:/var/lib/postgresql/data`
I didn't need persistent storage for this project so I didn't use a volume.

`  web:`
The next service is the Rails web server.

    `build: .`
Build the image from our newly defined Dockerfile.

   ` command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"`
Run this bash command in our new service container. This command is removing the pid file and telling rails what ports to use.

    `ports:
      - "3000:3000"`
Mapping the Rails service port 3000 to my local machine's 3000.

    `depends_on:
      - db`
This service will not start until the database service has started.

Once again, a `volumes` line could be here but I didn't need it.

All together my docker-compose.yml looks like this.
```yaml
version: '3'
services:
  db:
    image: postgres
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    ports:
      - "3000:3000"
    depends_on:
      - db
```

## database.yml

This is the database connection file. 

```yaml
default: &default
  adapter: postgresql
  encoding: unicode
  host: db
  username: postgres
  password:
  pool: 5

development:
  <<: *default
  database: my-api_development


test:
  <<: *default
  database: my-api_test
```

## docker-compose build

This command builds the containers. With my set up, anytime I update the Gemfile I have to rebuild.

Building can take some time. Each line in the docker-compose.yml is a step in the building process that can be cached so each build afterwards is faster.

Example output from a quick rebuild.
```text
$ docker-compose build
db uses an image, skipping
Building web
Step 1/11 : FROM ruby:2.6.3
 ---> d529acb9f124
Step 2/11 : RUN apt-get update -qq && apt-get install -y nodejs postgresql-client
 ---> Using cache
 ---> 32e807d05964
Step 3/11 : RUN gem install bundler
 ---> Using cache
 ---> 0c1c757c6905
Step 4/11 : WORKDIR /my-api
 ---> Using cache
 ---> b305131f9c6b
Step 5/11 : COPY Gemfile /my-api/Gemfile
 ---> Using cache
 ---> 657fc337d0c4
Step 6/11 : COPY Gemfile.lock /my-api/Gemfile.lock
 ---> Using cache
 ---> 09154df316d8
Step 7/11 : RUN bundle install
 ---> Using cache
 ---> 7915826ca085
Step 8/11 : COPY . /my-api
 ---> 4ff482944c8e
Step 9/11 : COPY Docker/database.yml config/database.yml
 ---> 101f814ba0bc
Step 10/11 : EXPOSE 3000
 ---> Running in 45f91676a8f3
Removing intermediate container 45f91676a8f3
 ---> 9426b9097b78
Step 11/11 : CMD ["rails", "server", "-b", "0.0.0.0"]
 ---> Running in 3f262f9679a2
Removing intermediate container 3f262f9679a2
 ---> a056b93d4bc8
Successfully built a056b93d4bc8
```
## docker-compose up

This command starts the containers.

```text
$ docker-compose up
Starting my-api_db_1 ... done
Recreating my-api_web_1 ... done
Attaching to my-api_db_1, my-api_web_1
```

Then visit `localhost:3000` in a browser to confirm it is up and running.

For more information, check out the official [quickstart](https://docs.docker.com/compose/rails/) guide for Rails.