Skip to main content
Navigation
HomeTechnical ReferenceJournalGitHubGitHub
Sidebar — toggle document categories via the logo
Categories

Docker Compose Reference

Overview

Docker Compose defines and runs multi-container applications. A single YAML file (docker-compose.yml) describes services, networks, and volumes, letting you spin up a full stack with one command.

Quick reference

These are the everyday Compose commands you'll reach for most. up builds and starts services, down tears the stack down, and ps, logs, and exec help you interact with a running stack. Add -d to run detached and --build to force a rebuild before starting.

docker compose up # start all services (foreground)
docker compose up -d # start in detached mode (background)
docker compose up -d --build # rebuild images before starting
docker compose down # stop and remove containers, networks
docker compose down -v # also remove named volumes (destructive)
docker compose down --rmi all # also remove the images

docker compose start # start existing stopped services
docker compose stop # stop running services gracefully
docker compose restart # restart all services
docker compose restart webapp # restart a specific service
docker compose pause # freeze services in place
docker compose unpause # resume paused services

docker compose ps # list services and their status
docker compose ps -a # include stopped services
docker compose top webapp # show processes running in a service

docker compose logs # logs from all services
docker compose logs -f # follow new log lines in real time
docker compose logs webapp db # logs from specific services only
docker compose logs --tail=50 webapp # last 50 lines for one service

docker compose exec webapp sh # run a command in a running service
docker compose exec -T webapp ls # non-interactive (for CI scripts)
docker compose run webapp sh # start a fresh container for a one-off command
docker compose run --rm webapp npm test # one-off command, cleaned up on exit

docker compose build # build or rebuild service images
docker compose build --no-cache # force a full rebuild
docker compose build webapp # build a specific service

docker compose pull # pull the latest images from the registry
docker compose push # push built service images to the registry

docker compose config # validate and view the resolved configuration
docker compose config --services # list just the service names
docker compose config --volumes # list just the volume names

Compose file reference

Top-level keys

A Compose file is organized around a handful of top-level keys. services is the only required one — it defines your containers — while networks, volumes, configs, and secrets declare shared resources those services reference. version is optional with modern Compose and is mainly kept for compatibility.

version: "3.9" # Compose file version (optional in modern compose)
name: myapp # project name (prefixes container, network, and volume names)
services: # container definitions (required)
networks: # custom network definitions
volumes: # named volume definitions
configs: # config objects (swarm)
secrets: # secret objects (swarm)

Service definition

A service is the Compose analog of a docker run invocation: it names an image or build context plus the runtime settings — ports, environment, volumes, healthchecks, and more. This example shows a typical webapp service with every commonly used key annotated.

services:
webapp:
image: nginx:alpine # image from registry
build: # build from Dockerfile
context: ./www # build context path
dockerfile: Dockerfile.prod # Dockerfile name
args: # build arguments
NODE_ENV: production
target: builder # multi-stage target
container_name: my-webapp # custom container name
hostname: webapp # container hostname
restart: unless-stopped # restart policy
ports: # port mappings
- "8080:80"
- "443:443"
expose: # expose ports to linked services (not host)
- "3000"
environment: # environment variables
NODE_ENV: production
DB_HOST: db
env_file: # load from env file
- ./config/webapp.env
volumes: # mount volumes
- ./www:/usr/share/nginx/html:ro
- app_data:/var/lib/data
networks: # attach to networks
- frontend
- backend
depends_on: # dependency ordering
db:
condition: service_healthy
healthcheck: # health check
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 5s
retries: 3
command: ["nginx", "-g", "daemon off;"] # override default command
entrypoint: ["/custom-entrypoint.sh"] # override entrypoint
user: "1000:1000" # run as user
working_dir: /app # working directory
profiles: # conditional activation
- debug
deploy: # swarm deployment config
replicas: 3
resources:
limits:
cpus: "0.5"
memory: 512M
init: true # use init process
stop_grace_period: 30s # grace period before SIGKILL
extra_hosts: # add hosts entries
- "api.internal:10.0.0.5"
logging: # logging driver
driver: json-file
options:
max-size: "10m"
max-file: "3"

Common service patterns

A typical stack couples a web frontend, an API backend, a database, and a cache. This example shows the standard wiring: the web service reaches the API over the internal network, the API waits for a healthy database before starting, and data lives in named volumes that outlive the containers.

# Web frontend
services:
web:
build: ./www
ports:
- "80:3000"
environment:
- API_URL=http://api:4000
depends_on:
- api

# API backend
api:
build: ./api
expose:
- "4000"
environment:
- DATABASE_URL=postgresql://user:pass@db:5432/mydb
depends_on:
db:
condition: service_healthy

# PostgreSQL database
db:
image: postgres:16-alpine
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: mydb
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d mydb"]
interval: 5s
timeout: 5s
retries: 5

# Redis cache
cache:
image: redis:7-alpine
volumes:
- redisdata:/data

volumes:
pgdata:
redisdata:

Networks

Declare custom networks here so services can reach each other by container name. Every network uses the bridge driver by default; override driver or tune IPAM (subnets) only when you need explicit addressing or cross-host connectivity.

networks:
frontend: # default: bridge driver
backend:
driver: bridge
driver_opts:
com.docker.network.bridge.name: br-backend
ipam:
config:
- subnet: 172.28.0.0/16

Volumes

Named volumes declared at the top level persist data independently of any container. Setting external: true tells Compose that the volume already exists — on the host or managed by another stack — so it will use it instead of creating a new one.

volumes:
pgdata:
driver: local
driver_opts:
type: none
o: bind
device: /mnt/storage/pgdata
redisdata:
external: true # volume is managed outside of compose

Profiles (conditional services)

Profiles mark services as optional: they are skipped by a normal docker compose up and are only started when their profile is enabled with --profile. This is ideal for debug tooling, one-off jobs, or dev-only services without maintaining a second compose file.

services:
app:
build: .
debug-tools:
image: nicolaka/netshoot
profiles: # only started with --profile debug
- debug
command: sleep infinity

Start the stack with the profile enabled to include debug-tools:

docker compose up -d # only starts app
docker compose --profile debug up -d # also starts debug-tools

Extends and fragments (YAML anchors)

YAML anchors (&name / *name) and extension fields (x-...) let you define shared configuration once and merge it into multiple services with <<:. This is the modern replacement for the deprecated extends key — keep repetitive settings like restart policies and logging options in a single place.

x-common: &common
restart: unless-stopped
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"

services:
webapp:
<<: *common
image: nginx:alpine

api:
<<: *common
image: myapp:latest

Using multiple compose files

Compose merges several files together, with later files overriding earlier ones. Pass them explicitly with -f, and note that docker-compose.override.yml is picked up automatically when present — reserve it for local development and use a named file such as docker-compose.prod.yml for environments.

# Base config + override
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

# docker-compose.override.yml is applied automatically
# docker-compose.prod.yml only when explicitly specified

The base file holds the shared config; the prod file overrides ports, restart policy, and environment for production:

# docker-compose.yml
services:
web:
build: .
ports:
- "80:3000"

# docker-compose.prod.yml
services:
web:
restart: always
ports:
- "443:3000"
environment:
- NODE_ENV=production

Environment variable interpolation

Compose supports ${VARIABLE} substitution from shell environment or .env files:

services:
web:
image: ${DOCKER_REGISTRY:-docker.io}/myapp:${APP_VERSION:-latest}
ports:
- "${HOST_PORT:-8080}:3000"

Default values: ${VAR:-default} (empty/unset → default), ${VAR-default} (only unset → default).

Error if missing: ${VAR:?error message}.

# .env file in the same directory as docker-compose.yml
DOCKER_REGISTRY=my.registry.com
APP_VERSION=2.1.0
HOST_PORT=9090

Production considerations

Hardening a stack for production is mostly about defaults: restart policies, resource limits, healthchecks, log rotation, and dropping capabilities the app does not need. This example bundles the most important production settings into one service definition — adjust the values to match your workload.

services:
api:
restart: unless-stopped # or 'always' for critical services
init: true # handle signals properly
read_only: true # root FS read-only (except volumes)
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
reservations:
cpus: "0.5"
memory: 256M
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 15s
timeout: 5s
retries: 3
start_period: 30s
logging:
driver: json-file
options:
max-size: "50m"
max-file: "5"

See also