An Intro to Docker Compose

What is Docker Compose?

Docker Compose is a tool that allows you to configure, build and run multiple containers. It's incredibly useful for building and deploying apps that use multiple services, each with their own container.

For example, let's say you have an app that uses 3 services:
- site: An ecommerce site that lets users browse and shop for items.
- database: A database that stores user, product, and order information.
- notifier: A script that monitors your database and sends email notifications to users when their orders have shipped.

Your app directory structure would look like this:

app
├── database
│   └── Dockerfile
│   └── ...
├── notifier
│   └── Dockerfile
│   └── ...
└── site
    └── Dockerfile
    └── ...

Normally, you'd have to start 3 containers yourself, either by running docker run for each image or by writing a custom script to launch them all. But with Docker Compose, you can launch all 3 containers with a single file AND configure them to boot.

How do I use Docker Compose?

Basic Overview

Let's expand on our example above. To use Docker Compose with our app, we add a docker-compose.yml file to the top level of our project directory:

app
├── docker-compose.yml
├── database
│   └── Dockerfile
│   └── ...
├── notifier
│   └── Dockerfile
│   └── ...
└── site
    └── Dockerfile
    └── ...

The contents of our docker docker-compose.yml file would be:

# line below tells docker-compose which version of the docker-compose file format to use
version: "3.8"
services:
    database:
        build: ./database
    notifier
        build: ./notifier
    site:
        build: ./site

The lines above define each service and tell Docker compose where to find their build files. To build the images for services, we'd run the command docker-compose build from the top level of the project directory. And to run them, we'd use the command docker-compose up.

Configuration Options

The example docker-compose.yml file above is very basic. But Docker compose allows you to configure your services in all sorts of ways. For example: we could add a few lines to tell docker-compose that the site and notifier services depend on the database service, and that the database service needs to build and run successfully before the others:

version: "3.8"
services:
    site:
        build: ./app
        depends_on:
            - database
    notifier
        build: ./notifier
        depends_on:
            - database
    db:
        build: ./database

Note that each of our services still has their own Dockerfile. This is useful for long, service specific configurations, when a service might have a long and/or complex Dockerfile. However, you could include all of the configuration options for a service in your docker-compose.yml file if you wanted.

For example - let's assume the database service has 2 files: database/Dockerfile and database/init.sql.

app
├── docker-compose.yml
├── database
│   └── Dockerfile
│   └── init.sql
├── notifier
│   └── Dockerfile
│   └── ...
└── site
    └── Dockerfile
    └── ...

Let's also assume the contents of database/Dockerfile are as follows:

FROM postgres:13
COPY ./init.sql /docker-entrypoint-initdb.d/init.sql

This simply tells Docker which image to use for database, and copies a single file (init.sql) to the container on build. Note that /docker-entrypoint-initdb.d is a special directory on postgres images, and that .sql files in this directory are run when postgres starts.

We could remove our database/Dockerfile entirely by changing our docker-compose.yml as below:

version: "3.8"
services:
    site:
        build: ./app
        depends_on:
            - database
    notifier
        build: ./notifier
        depends_on:
            - database
    database:
        image: "postgres:13"
        volumes:
            "./init.sql:/docker-entrypoint-initdb.d/init.sql"

This achieves the same effect - Docker Compose now knows which image to use for database, and will copy our init.sql file to the appropriate place in the container (volumes is slightly different than COPY, but I won't get into that here).

Closing Remarks

Hope that's enough to get you started using Docker compose on your own! If you want to learn more,  I highly recommend reading the Docker compose docs.