Key idea
Most apps are more than one container. Compose describes all of them in one file, compose.yaml: which images to run, how they reach each other and where their data lives. One command starts the whole stack, and one removes it.
The problem it solves
A web app and its database by hand is a lot of typing. You create a network so they can talk, a volume for the data, then two long docker run commands with the right ports, variables and names, in the right order. A teammate has to repeat all of it exactly.
Compose turns that into a file you commit next to your code. Everyone who clones the repository runs docker compose up and gets the same stack.
A two-service stack
This is the file you'll run in the next lesson, from the compose-stack sample:
services:
web:
build: ./app
ports:
- "3000:3000"
environment:
DATABASE_URL: postgres://app:demo-password@db:5432/app
depends_on:
db:
condition: service_healthy
db:
image: postgres:16
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: demo-password # demo only
POSTGRES_DB: app
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d app"]
interval: 5s
timeout: 3s
retries: 10
volumes:
db-data:
your machine
┌──────────────────────── compose-stack network ────────────────────────┐
│ │
│ ┌───────────┐ postgres://…@db:5432 ┌───────────┐ ┌──────────┐ │
│ │ web │ ───────────────────────▶ │ db │──▶│ db-data │ │
│ │ port 3000 │ │ port 5432 │ │ (volume) │ │
│ └───────────┘ └───────────┘ └──────────┘ │
└───────▲───────────────────────────────────────────────────────────────┘
│ localhost:3000
browser, curl
Services
Each entry under services becomes one container. web is built from the ./app folder's Dockerfile; db runs a ready-made image. Ports, variables and health checks are the same settings you've passed to docker run, written down.
The network
Compose puts every service on one private network and gives each a name on it: its service name. That's why web finds the database at db, in DATABASE_URL. Nothing outside the stack can reach db, because it publishes no port.
Volumes
db-data is a named volume. The database keeps its files there, not in the container's own filesystem, so they survive the container being removed and recreated. Module 7 covers volumes in depth.
Start order
depends_on with condition: service_healthy holds web back until db passes its health check: pg_isready run every 5 seconds, each try given 3 seconds, and up to 10 failures before db counts as unhealthy. Without the condition, Compose only waits for the database container to start, and a database takes a few seconds more before it accepts connections.
Check yourself