Docker Compose Reference: compose.yaml, Commands, Volumes, Override Files & Networking
Docker Compose defines and runs multi-container applications using a single compose.yaml file. It’s the standard local development tool for microservices and the first step to understanding Kubernetes concepts — services, networks, and volumes map directly.
1. compose.yaml Structure
Services, images, ports, volumes, and environment variables
# compose.yaml (or docker-compose.yaml — Compose v2 prefers compose.yaml)
name: my-app
services:
web:
image: nginx:1.25-alpine
ports:
- "8080:80" # host:container
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro # bind mount, read-only
depends_on:
api:
condition: service_healthy # wait for health check, not just started
networks: [frontend, backend]
api:
build:
context: ./api
dockerfile: Dockerfile
args:
NODE_ENV: production
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://postgres:pass@db:5432/mydb
- REDIS_URL=redis://redis:6379
env_file: .env # load from .env file (don't commit .env to git)
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 10s
timeout: 5s
retries: 3
start_period: 30s # grace period before health checks start
depends_on: [db, redis]
networks: [backend]
deploy:
resources:
limits: {cpus: "0.5", memory: 512M}
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: mydb
POSTGRES_USER: postgres
POSTGRES_PASSWORD: pass
volumes:
- postgres_data:/var/lib/postgresql/data # named volume (persisted)
networks: [backend]
redis:
image: redis:7-alpine
networks: [backend]
volumes:
postgres_data: # named volume — persisted across container restarts
networks:
frontend:
backend: # services on different networks can't talk to each other
2. Essential Commands
up, down, ps, logs, exec, build, and pull
# Start all services (detached): docker compose up -d # Start and rebuild images (after Dockerfile change): docker compose up -d --build # Start specific services only: docker compose up -d db redis # only start db and redis # Stop all services (containers removed, volumes kept): docker compose down # Stop and remove named volumes (DESTRUCTIVE — wipes database): docker compose down -v # Check status: docker compose ps # Follow logs (all services): docker compose logs -f # Follow specific service logs: docker compose logs -f api # Run command in running container: docker compose exec api sh docker compose exec db psql -U postgres mydb # Run one-off command in a new container (not running service): docker compose run --rm api npm run migrate # Rebuild a specific service image: docker compose build api # Pull latest base images: docker compose pull # Scale a service (run multiple instances): docker compose up -d --scale api=3 # 3 api containers # Requires: remove fixed 'ports' mapping (use load balancer instead) # Restart a single service: docker compose restart api
3. Volumes & Bind Mounts
Named volumes vs bind mounts, hot reload for development
# Named volume (managed by Docker — persists across restarts):
volumes:
postgres_data: # just declare it — Docker manages location
# Bind mount (host directory mapped into container — for dev):
volumes:
- ./src:/app/src # host path : container path
- ./config.json:/app/config.json:ro # :ro = read-only
# Dev hot-reload pattern (Node.js):
services:
api:
image: node:20-alpine
working_dir: /app
command: ["npm", "run", "dev"] # nodemon or ts-node-dev watches for changes
volumes:
- .:/app # whole project synced into container
- /app/node_modules # anonymous volume: prevent host node_modules from overriding container's
ports: ["3000:3000"]
# The /app/node_modules trick: anonymous volume "shadows" the bind mount at that path
# Container's node_modules are used, not host's (different OS/arch)
# Tmpfs mount (in-memory — good for tests):
services:
test-db:
image: postgres:16
tmpfs: /var/lib/postgresql/data # fast, ephemeral, no disk I/O
# Volume inspection:
docker volume ls
docker volume inspect my-app_postgres_data # shows real path on host
docker volume rm my-app_postgres_data # manual cleanup
4. Override Files & Environments
compose.override.yaml, multiple -f files, and environment-specific configs
# Docker Compose automatically merges compose.yaml + compose.override.yaml
# Use compose.override.yaml for dev settings (don't commit if personal):
# compose.yaml (base — production-like, minimal):
services:
api:
image: my-api:latest
env_file: .env.production
# compose.override.yaml (dev overrides — auto-merged):
services:
api:
build: . # override image with local build
volumes:
- .:/app # hot-reload bind mount
environment:
- DEBUG=true
command: npm run dev # override to dev server
# Multiple -f files (explicit):
docker compose -f compose.yaml -f compose.staging.yaml up -d
# Environment-specific:
docker compose --env-file .env.staging up -d # use specific .env file
# COMPOSE_FILE=compose.yaml:compose.staging.yaml # env var alternative
# Profiles (opt-in services):
services:
monitoring:
image: prom/prometheus
profiles: [monitoring] # only starts with --profile monitoring
app:
image: my-app # no profile = always starts
docker compose --profile monitoring up -d # starts app + monitoring
docker compose up -d # starts only app
5. Networking & Service Discovery
How containers find each other, custom networks, and external access
# Service discovery: containers talk via SERVICE NAME (not IP):
# From 'api' container:
curl http://db:5432 # postgres container (by service name)
redis-cli -h redis # redis container
fetch("http://nginx/api") # nginx container
# Default network: all services share a default network if no 'networks' defined
# Custom networks: isolate services from each other
# Expose to host:
ports:
- "8080:80" # accessible at localhost:8080 on host
- "127.0.0.1:5432:5432" # ONLY on loopback — not accessible from network (security)
- "0.0.0.0:5432:5432" # accessible from any host interface (careful!)
# Container-only (no host exposure):
expose:
- "3000" # accessible to OTHER containers, not host
# Connect to external network (e.g., shared DB across projects):
networks:
shared-db:
external: true # must exist: docker network create shared-db
internal: # created by compose
# Debugging networking:
docker compose exec api ping db # test connectivity by service name
docker compose exec api cat /etc/hosts # see container DNS entries
docker network ls # list networks
docker network inspect my-app_backend # inspect a network
Track Docker and Docker Compose releases.
ReleaseRun monitors Kubernetes, Docker, and 13+ DevOps technologies.
Related: Docker Reference | Kubernetes YAML Reference | Traefik Reference | Docker EOL Tracker
🔍 Free tool: Docker Compose Security Checker — paste your docker-compose.yml and check 9 security misconfigurations — exposed ports, privileged containers, plain-text secrets — with an A–F grade.
Founded
2023 in London, UK
Contact
hello@releaserun.com