Containers & orchestration 21 entries 13 run in the sandbox

Docker Compose cheat sheet

Run multi-container apps locally with one declarative file. Each entry has a worked example and the flags worth knowing, and where one is easy to get wrong, the gotcha.

Lifecycle

docker compose up -d

Create or update every service in compose.yaml and run them in the background.

docker compose up -d --build
-d
Detached mode.
--build
Build images before starting.
--force-recreate
Recreate containers even if nothing changed.
--remove-orphans
Remove containers for services no longer in the file.
SERVICE...
Only these services (and their dependencies).

Gotcha up -d is idempotent: it only recreates containers whose configuration or image changed. Run it again after editing the file.

docker compose ps

Status, health and ports of this project's containers.

docker compose ps -a
NAME          IMAGE         SERVICE   STATUS                     PORTS
shop-api-1    shop-api      api       Up 3 minutes (healthy)     0.0.0.0:8080->8080/tcp
shop-db-1     postgres:16   db        Up 3 minutes (healthy)     5432/tcp
-a
Include stopped containers.
--services
Service names only.

docker compose logs -f <service>

Interleaved, colour-coded logs for one or all services.

docker compose logs -f --tail 50 api
-f
Follow.
--tail N
Last N lines per container.
--since 10m
Recent output only.
-t
Timestamps.

docker compose exec <service> <cmd>

Run a command in the running container of a service.

docker compose exec db psql -U app -c 'select 1'
-T
No TTY; required when piping input or running in CI.
-u USER
Run as a specific user.

docker compose run --rm <service> <cmd>

Start a one-off container for a task such as a database migration, with the service's config.

docker compose run --rm api npm run migrate
--rm
Delete the container afterwards.
--no-deps
Do not start linked services.
--service-ports
Publish the service's ports (off by default for run).

docker compose down

Stop and remove the project's containers and networks. Named volumes stay unless you ask.

docker compose down --remove-orphans
-v
Also remove named volumes declared in the file: deletes data.
--rmi local
Remove images built by this project.
--remove-orphans
Clean up containers of deleted services.

Gotcha down -v is the fastest way to lose a local database you cared about. Use stop if you only want to pause.

docker compose up -d <service>

Apply a changed environment variable or image. restart only restarts the existing container with its old config.

docker compose up -d api
restart SERVICE
Same container, same config; picks up only mounted file changes.
up -d SERVICE
Recreates the container if its definition changed.

Gotcha Edited .env and ran restart, but the app still sees the old value? The container was never recreated.

docker compose pull && docker compose up -d

Update services to the newest images for their tags and roll them.

docker compose pull api && docker compose up -d api
pull SERVICE
Fetch newer images.
build --pull
Rebuild local images on top of fresh base images.

docker compose up -d --scale <service>=<n>

Run several replicas of a stateless service, such as queue workers.

docker compose up -d --scale worker=3
--scale SVC=N
Desired number of containers.

Gotcha Scaling fails for services with container_name or a fixed host port. Only one container can bind host port 8080.

docker compose stop / start

Stop containers without removing them, then start the same containers again later.

docker compose stop worker && docker compose start worker
-t N
Seconds to wait after SIGTERM before SIGKILL (default 10).
stop vs down
stop keeps containers, networks and volumes; down removes containers and networks.

Gotcha start reuses the old containers, so edits to compose.yaml are ignored. Only up recreates a container with the new configuration.

docker compose build <service>

Build or rebuild service images from their build: sections.

docker compose build --pull --no-cache api
--pull
Always pull a newer version of the base image.
--no-cache
Ignore the layer cache and rebuild every step.
--build-arg KEY=VAL
Pass a build argument declared with ARG.

Gotcha up does not rebuild an image that already exists. After changing the Dockerfile or the build context, run build or up --build.

Configuration

docker compose config

Print the fully merged, interpolated configuration. The fastest way to find a typo or a missing variable.

docker compose -f compose.yaml -f compose.prod.yaml config
--services
Only list service names.
-q
Validate silently; non-zero exit on errors.
-f FILE
Merge several files, later ones override earlier ones.

compose.yaml

An app, its database with a healthcheck, and a named volume, started in dependency order.

services:
  api:
    build: .
    ports:
      - "8080:8080"
    env_file: .env
    environment:
      DATABASE_URL: postgres://app:${DB_PASSWORD}@db:5432/app
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped
  db:
    image: postgres:16
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app"]
      interval: 5s
      retries: 10
volumes:
  pgdata:
db:5432
Services reach each other by service name on the project network.
condition: service_healthy
Start api only after db passes its healthcheck.
${DB_PASSWORD}
Interpolated from the shell or the project .env file.
volumes: pgdata
Named volume that survives down (without -v).

Gotcha Inside a container, localhost is the container itself. Connect to db:5432, not localhost:5432.

.env vs env_file:

Two different mechanisms that are easy to confuse.

docker compose --env-file prod.env config
.env (project dir)
Used to interpolate ${VARS} inside compose.yaml itself.
env_file:
Injects variables into the container's environment.
--env-file FILE
Use a different interpolation file.

Gotcha A variable in .env is not automatically visible inside the container unless a service references it or loads it with env_file.

docker compose -p <name>

Set the project name that prefixes containers, networks and volumes. Two checkouts with the same folder name collide otherwise.

docker compose -p shop-staging up -d
-p NAME
Project name.
COMPOSE_PROJECT_NAME
Same, as an environment variable.
name:
Top-level key in compose.yaml.

docker compose --profile <name> up

Keep optional services, such as debug tools or seeders, out of the default up.

docker compose --profile debug up -d
profiles: [debug]
On a service, it only starts when that profile is active.

docker compose watch

Sync source changes into running containers or rebuild on change during local development.

develop.watch
Per-service rules in compose.yaml.
action: sync / rebuild
Copy files in, or rebuild the image.

healthcheck:

Tell Compose when a service is actually ready, not merely started, so depends_on can wait for it.

healthcheck:
  test: ["CMD", "curl", "-fsS", "http://localhost:8080/healthz"]
  interval: 10s
  timeout: 3s
  retries: 5
  start_period: 30s
CMD / CMD-SHELL
CMD runs the program directly; CMD-SHELL runs the string through /bin/sh.
retries
Consecutive failures before the container is marked unhealthy.
start_period
Grace period at start-up during which failures do not count.

Gotcha The check runs inside the container, so the tool it calls (curl, wget, pg_isready) must exist in the image. Slim images often lack curl.

restart: unless-stopped

Restart a service automatically when its process exits or the Docker daemon restarts.

services:
  worker:
    image: ghcr.io/acme/worker:1.4.2
    restart: unless-stopped
no
Never restart (the default).
on-failure[:N]
Restart only after a non-zero exit, optionally at most N times.
always
Restart on any exit, and after the daemon restarts.
unless-stopped
Like always, except after you stopped it yourself.

Gotcha A policy reacts to the process exiting. A hung process that keeps running is never restarted, and plain Compose does not restart unhealthy containers.

ports: vs expose:

ports publishes a container port on the host. Services on the same Compose network reach each other on the container port without either.

services:
  api:
    ports:
      - "127.0.0.1:8080:8080"
  db:
    expose:
      - "5432"
HOST:CONTAINER
Publish the container port on the host port.
127.0.0.1:HOST:CONTAINER
Publish on loopback only, reachable from this machine.
expose
Documents a port for other services; publishes nothing.

Gotcha A bare "8080:8080" binds 0.0.0.0, and Docker writes its own iptables rules that bypass ufw. The port is open to the network unless you bind 127.0.0.1.

deploy.resources.limits

Cap a service's memory and CPU so one leak or runaway loop cannot starve the whole host.

services:
  worker:
    deploy:
      resources:
        limits:
          cpus: "1.5"
          memory: 512M
memory
Hard limit. At the limit the kernel OOM-kills the process (exit code 137).
cpus
CPU time quota, in cores; the service is throttled, not killed.
reservations.memory
A soft limit the kernel only enforces when the host runs short of memory.

Gotcha Check the limits took effect with docker stats: the MEM USAGE / LIMIT column shows the cap instead of the host's total memory.

Your pager is ready.

Free, instant, and it works on your phone. No signup: start as a guest and save your progress later.