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.