I needed to spin up some local services for development. docker-compose makes it easy to orchestrate multiple containers without the faff of managing individual docker run commands with dozens of flags.
My typical docker-compose.yml structure Link to heading
Here’s what a typical development setup looks like for me:
version: '3.8'
services:
postgres:
image: postgres:16
environment:
POSTGRES_PASSWORD: localdev
POSTGRES_DB: myapp
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
volumes:
postgres_data:
redis_data:
I always include health checks for databases - they prevent your application from trying to connect before the database is ready. Learnt that the hard way after debugging race conditions on startup.
Compose v1 vs v2 Link to heading
If you’ve been using Docker for a while, you might have noticed docker-compose (with a hyphen) is now deprecated in favour of docker compose (no hyphen). The new version is a plugin for the Docker CLI and is significantly faster. I made the switch in 2023 and haven’t looked back.
The good news: most commands work identically. Just drop the hyphen.
Basic commands Link to heading
Start services in the background:
docker compose up -d redis
Start multiple services:
docker compose up -d redis postgres
Start everything defined in the file:
docker compose up -d
Check what’s running:
docker compose ps
View logs:
docker compose logs redis
Follow logs:
docker compose logs -f redis
Stop services:
docker compose down
Stop and remove volumes (careful):
docker compose down -v
Rebuild images:
docker compose build
The -d flag runs in detached mode (background). Without it, logs stream to your terminal, which is useful when you’re first setting things up and want to see what’s happening.
Things I wish I’d known earlier Link to heading
Named volumes are your friend. I used to skip defining volumes and wonder why my database reset every time I ran docker compose down. Named volumes persist data between container lifecycles.
Don’t expose all ports in production. The ports directive publishes them to your host machine. In production with Kubernetes or Swarm, containers can talk to each other over internal networks without exposing ports.
Health checks save debugging time. A container can be “running” but not actually ready to accept connections. Health checks let docker-compose (and orchestrators) know when a service is truly ready.
For more details, the Compose file reference is comprehensive and well-organised. I keep it bookmarked.