Docker Compose¶
What Is It?¶
Docker Compose is a tool for defining and running multi-container Docker applications declaratively. Instead of issuing a chain of docker run commands with carefully-crafted flags, you describe the entire stack — services, networks, volumes, and their relationships — in a single docker-compose.yml file. One command then brings the whole stack up or down.
Compose is the simplest form of container orchestration. It runs on a single host (no clustering, no scheduling across machines), but it removes nearly all the manual bookkeeping of running multiple cooperating containers: dependency ordering, named networks, named volumes, environment variables, restart policies — all expressed in YAML.
For larger production setups you would graduate to Kubernetes (covered in week 11). Compose is the right tool when:
- A single host is enough.
- The number of services is small (3–20).
- You want a checked-in description of "the production deployment" that someone can read without
docker inspect-ing every container.
Installation¶
Modern Docker installations ship Docker Compose as a CLI plugin — invoked as docker compose (with a space, not a hyphen). On the lab VM, where Docker was installed in week 9, no extra packages are needed:
sudo dnf install -y docker-compose-plugin
sudo docker compose version
If docker-compose-plugin is already pulled in by the docker-ce install, dnf will say nothing changed.
docker-compose (v1, hyphenated) is deprecated
The standalone Python tool docker-compose (with a hyphen) was the original implementation. It is end-of-life — use docker compose (the plugin) instead. Most v1 commands still work in v2 with the same flags.
Key Files and Directories¶
| Path | Purpose |
|---|---|
docker-compose.yml | The Compose project definition. Lives at the project root. |
compose.yml, compose.yaml | Alternative names accepted by docker compose v2. |
.env | Optional file for environment variables interpolated into docker-compose.yml. |
/var/lib/docker/volumes/ | Where named volumes Compose manages live on disk. |
A directory containing a docker-compose.yml is called a project. The project's name defaults to the directory name (e.g. lab10) and is used to namespace networks, volumes, and container labels Compose creates.
Configuration¶
docker-compose.yml is a YAML file with three top-level keys you'll use most: services, networks, and volumes. Compose creates and destroys all three together.
Minimal Working Configuration¶
services:
app:
image: registry.hpc.ut.ee/mirror/library/nginx:latest
ports:
- "8080:80"
restart: unless-stopped
Bring up:
sudo docker compose up -d
Take down (stops and removes containers, but keeps named volumes):
sudo docker compose down
Important Directives¶
services:- The list of containers in the stack. Each key under
services:is a service name (it becomes the container's hostname on the Compose network). image:- The image to run, exactly as you'd pass to
docker run. Either pulled from a registry or built locally if you also specifybuild:. build:- Path to a directory with a
Dockerfile. If bothimageandbuildare set, the built image gets tagged with theimagename. ports:- Port mappings, in
"host:container"form. Bind to localhost ("*********:5000:5000") when the service should only be reachable through a reverse proxy on the same host. volumes:(per-service)- Mounts. Bind-mount with
/host/path:/container/path, named volume withvolumename:/container/path. Named volumes must also be declared at the top-levelvolumes:block. environment:- Environment variables passed into the container. Use a list (
KEY=value) or a dict (KEY: value). restart:- Same semantics as
docker run --restart. Useunless-stoppedfor production-like behaviour: restart on crash and on host reboot, but stay stopped if you explicitlydocker compose stop. depends_on:- A service waits for others to start before starting itself. Note: by default this only waits for the dependency's container to start, not for its application to be healthy. Add a
healthcheck:and usedepends_on: { service: { condition: service_healthy } }for true readiness gating. labels:- Arbitrary metadata. Reverse-proxies like Traefik consume specific label keys to discover services automatically.
networks:(top-level)- Define networks the stack uses. By default, Compose creates a single user-defined bridge network named
<project>_defaultand attaches every service to it. volumes:(top-level)- Define named volumes the stack uses. Without further configuration, Compose manages the lifecycle and storage location.
Reusing an Existing Named Volume or Network¶
A common upgrade path is migrating containers that were previously started with docker run and a manually-created named volume. To make Compose reuse the existing volume rather than create a new (empty) one, declare it as external: true:
volumes:
minio-data:
external: true # use the pre-existing 'minio-data' volume
services:
minio:
image: registry.hpc.ut.ee/mirror/minio/minio:latest
volumes:
- minio-data:/data # same name as in the top-level block
Networks work the same way:
networks:
mynetwork:
external: true
This is essential when migrating a stateful service (database, object store, …) so you don't lose its data.
Common Commands¶
# Start the entire stack in the background
sudo docker compose up -d
# View the logs of every service (follow mode)
sudo docker compose logs -f
# Logs of one service only
sudo docker compose logs -f minio
# List the running containers in this project
sudo docker compose ps
# Stop everything but keep containers around
sudo docker compose stop
# Stop and remove containers (named volumes survive by default)
sudo docker compose down
# Stop, remove containers, AND remove named volumes (DESTRUCTIVE)
sudo docker compose down -v
# Rebuild images (after editing Dockerfiles or build context)
sudo docker compose build
# Restart one service after a config change
sudo docker compose up -d --force-recreate inventory
# Check the effective merged config (interpolates env vars)
sudo docker compose config
All commands operate on the project rooted at the current directory. Use -f /path/to/docker-compose.yml to point at a project elsewhere.
Logging and Debugging¶
docker compose logs <service>aggregates the logs of every container in that service.docker compose psshows the status of each service container — use this to confirm everything actually started.docker compose configparses, validates, and prints the merged configuration. Run this after any edit; it's the fastest way to catch YAML errors and broken variable references.- Containers managed by Compose carry these labels (visible in
docker inspect): com.docker.compose.project=<project name>com.docker.compose.service=<service name>- If a container fails to start,
docker compose up(without-d) attaches to the foreground and shows you the start-up output directly.
Security Considerations¶
- The same security caveats apply as for raw Docker: containers run as root by default, the Docker socket is root-equivalent, and image provenance still matters. Compose does nothing to mitigate these.
- Don't store secrets directly in
docker-compose.ymlif it's checked into git. Use a.envfile kept out of version control, or Docker secrets. restart: alwayswill respawn a container indefinitely, including one that crashes immediately. Useunless-stoppedfor normal services, oron-failure: <count>to bound retries.docker compose down -vdeletes named volumes. If your data is in a named volume, this is a foot-gun — re-read the prompt before confirming.
Further Reading¶
Related Documentation¶
- Concepts: Containers, Container Orchestration
- Technologies: Docker
- SOPs: Container Operations