CD2: Running many containers together
Compose: the whole app in one file
🎯You'll be able to
Describe several containers, their network, volumes, health checks and start order in a Compose file, and start or stop them together.
🏠The everyday picture
Instead of telling each cook what to make on the day, a wedding caterer writes one menu card listing every dish, who makes it and what must be ready first. Anyone can pick up the card and run the kitchen the same way.
📘What it really is
- Docker Compose reads a file (
compose.yaml) describing services, and creates their containers, a shared network and volumes: docker compose up -dstarts everything;docker compose pslists it;docker compose logs -f appfollows a service's output;-
docker compose downstops and removes the containers and network, keeping volumes. -
Each service lists its
image(or abuildfolder),environment,ports,volumesandhealthcheck. -
Start order:
depends_onwithcondition: service_healthywaits until the database's health check passes before starting the API. Plaindepends_ononly waits for the container to start, which is before the database accepts connections. -
Copies:
deploy: replicas: 2(ordocker compose up -d --scale app=3) runs several copies of one service, all sharing its name. - Settings:
${DB_PASSWORD}in the file is filled from your shell or from a.envfile next to it. Keep.envout of Git. - Compose or Terraform? Compose is great for running an app on one machine, especially for development. Terraform (CD3) manages infrastructure of any kind, across many providers, and keeps a record (state) of what it created. The RouteRani projects use Terraform so the same skills carry over to cloud accounts.
The kit includes this compose.yaml (shortened). It runs the image tagged routerani:1.0.0, so build that tag first:
services:
db:
image: postgres:16.15-alpine
environment:
POSTGRES_USER: routerani
POSTGRES_DB: routerani
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets: [db_password]
volumes: [db-data:/var/lib/postgresql/data]
healthcheck:
test: ["CMD-SHELL", "pg_isready -U routerani -d routerani"]
interval: 2s
retries: 15
app:
image: routerani:1.0.0
environment:
APP_VERSION: 1.0.0
DATABASE_URL: postgres://routerani:${DB_PASSWORD}@db:5432/routerani
depends_on:
db: { condition: service_healthy }
deploy: { replicas: 2 }
lb:
image: nginx:1.30.6-alpine
volumes: [./infra/nginx.conf:/etc/nginx/nginx.conf:ro]
ports: ["127.0.0.1:8081:80"]
depends_on: [app]
volumes:
db-data:
secrets:
db_password:
environment: DB_PASSWORD
📝A worked example
Input: in the kit, docker build -t routerani:1.0.0 app, export DB_PASSWORD=$(openssl rand -hex 12), then docker compose up -d, then docker compose ps and six
requests to http://localhost:8081/version.
Expected output: db, two app copies (both healthy once your image has the health check from CD1) and lb running; the x-served-by header alternates between two container IDs.
After a booking, docker compose down then up -d keeps the booking; docker compose down -v deletes it. Tested on 1 November 2026 with
Docker 29.9.2 and Compose 5.6.1 on Linux.
🌍Where it's used
- Developer setups: "clone the repo, run
docker compose up" gets a new team member running in minutes. - Small production sites on a single server.
- CI test jobs that need a real database and cache.
📝Case scenario
A new developer at a logistics company spent three days installing the right PostgreSQL, Redis and Elasticsearch versions from a wiki page that was out of date. The team wrote a Compose file with pinned versions and health checks. The next new joiner was running tests within an hour.
🖱️Try it
Start the stack, then run docker compose stop db and request /ready through the load balancer. Start it again with
docker compose start db. Success: /ready returns 503 while the database is stopped and 200 within a few seconds of it returning.
⚠️Common mistakes
- Plain
depends_onfor a database. Sign: the API crashes on first start, then works on a restart. Fix:condition: service_healthy, and retry in the app too. - Committing
.env. Sign: passwords in the Git history. Fix: add.envto.gitignore; commit a.env.examplewith placeholders. - Building production on
docker composehabits. Sign: one server with no record of changes. Fix: for anything beyond one machine, use infrastructure as code.
🛠️For your project
The projects use Terraform rather than Compose, but every idea here (network, aliases, volume, health checks) appears again in infra/main.tf.
✅Quick check
What does condition: service_healthy add to depends_on?
Show the answer
It waits until the dependency's health check passes, not just until its container starts.
What does docker compose down keep?
Show the answer
The volumes, unless you add -v.
