Skip to content

Docker Compose

Docker Compose is the standard way to manage services using Docker. While it is focused on defining and running multi-container applications it also works well for managing the configuration of single-container applications. See the official documentation for a more in-depth explanation.

Why Would You Want To Use Docker Compose?

When you first start out you may wonder why you want to go to this extra work. You can just launch containers directly and they stay running, and all my services are simple. What benefit does this provide?

The answer to that question will come in a few days, weeks, or months, when you are ready to change your system. When it's time to docker pull a new upstream container you will have to destroy and recreate the container. Will you remember every argument you passed the first time? What happens if you have a drive failure and you have to rebuild everything?

With a proper Docker Compose setup you don't have to worry about any of that, because your exact configuration will be documented in docker-compose.yml files.

Structure For Your Docker Compose Infrastructure

While it is possible to work with multiple docker-compose.yml files within the same directory, most people find it easier to build a directory tree to store their services and configuration. This allows for a lot of flexibility when it comes to building and managing your services. There are many ways you manage these files, but this is what we recommend for maximum flexibility and ease of use.

Basic setup

First, pick a home for your directory tree. This can be anywhere, but in most situations we recommend /srv/docker. For the rest of this guide we will assume you have placed your directory tree there.

sudo mkdir -p /srv/docker-compose
sudo chown `whoami` /srv/docker-compose  # Optional, but makes
                                         # it easier to manage

Within this directory you will create directories for every service you want to run.

Note

The term "service" here is used in a slightly different way than you may be used to. In this context a service is any collection of one or more containers. Each container runs a single daemon process, which is what you may be used to thinking of as a service.

Create Your First Service

Let's start by setting up a service for the Mosquitto MQTT broker. This will demonstrate setting up a simple service which someone else has published a container for.

First we'll create directories for it. Notice that we have created a directory in /srv/docker-compose and directories for volume mounts in /srv/mosquitto. If you are setting up a service that doesn't need volume mounts you can skip that.

mkdir -p /srv/docker-compose/mosquitto /srv/mosquitto/conf /srv/mosquitto/data /srv/mosquitto/log

Create the /srv/docker-compose/mosquitto/docker-compose.yml file:

/srv/docker-compose/mosquitto/docker-compose.yml

version: "3"
services:
  mosquitto:
    image: eclipse-mosquitto
    volumes:
      - ./conf:/mosquitto/conf
      - ./data:/mosquitto/data
      - ./log:/mosquitto/log
    ports:
      - 1883:1883
      - 9001:9001

Create the mosquitto.conf configuration file:

/srv/mosquitto/conf/mosquitto.conf

persistence true
persistence_location /mosquitto/data/
log_dest file /mosquitto/log/mosquitto.log

listener 1883

Once this is in place you can use docker-compose to bring it up:

cd /srv/docker-compose/mosquitto
docker-compose up --detach

If you want to watch the logs you can attach to them like this:

cd /srv/docker-compose/mosquitto
docker-compose logs

To bring the service down:

cd /srv/docker-compose/mosquitto
docker-compose down

Custom Docker Containers

Sometimes you need to customize a container before it gets deployed. Docker provides a mechanism for doing so using Dockerfile. We'll demonstrate how to do that by creating a custom nginx service.

As before we need to create the docker-compose directory:

mkdir -p /srv/docker-compose/nginx/nginx

Notice that we did not include a data directory in /srv, and we added an extra /nginx to the end of our path? The first nginx path component refers to the service, while the second nginx path component refers to the container.

Now we create the docker-compose.yml file:

/srv/docker-compose/nginx/docker-compose.yml

version: '3'
services:
  nginx:
    build: ./nginx
    ports:
      - "80:80"

This is where the second nginx directory comes into play- we are going to create a Dockerfile and an index.html inside of there.

/srv/docker-compose/nginx/nginx/Dockerfile

FROM nginx:mainline
COPY index.html /usr/share/nginx/html

/srv/docker-compose/nginx/nginx/index.html

<h1>Hello, World!</h1>

With these two files in place you are ready to start nginx:

cd /srv/docker-compose/nginx
docker-compose up --detach

Writing Your Own Service

Now we'll look at a more complex example- we'll implement our own service along with the infrastructure it needs. In this example we'll stub out an application that uses a redis backend. As before, we start by creating the directories we need:

mkdir -p /srv/docker-compose/my_cool_app/my_cool_app

Create some files:

/srv/docker-compose/my_cool_app/docker-compose.yml

version: '3'
services:
  my_cool_app:
    build: ./nginx
    ports:
      - "80:80"
  redis:
    image: redis:latest
    ports:
      - "6379:6379"
    restart: always

/srv/docker-compose/my_cool_app/my_cool_app/Dockerfile

FROM debian:stable
COPY my_cool_app /
ENTRYPOINT ["/my_cool_app"]

/srv/docker-compose/my_cool_app/my_cool_app/my_cool_app

#!/usr/bin/env python3
import time
print('Hello, World!')
while True:
    sleep(0.1)

With these files in place you're ready to run your service. There are two ways you can run it:

Development mode:

Use this while you are actively developing the app. Logs for all services will display on STDOUT and pressing ^C will stop all the containers.

cd /srv/docker-compose/my_cool_app
docker-compose down  # This ensures that you have a fresh start
docker-compose up

Production mode:

Use this when you want your service to run normally, even after you logout.

cd /srv/docker-compose/my_cool_app
docker-compose down  # This ensures that you have a fresh start
docker-compose up --detach