Docker Compose is the standard way to run multiple containers with a single command and describe their networks, volumes and depends-on relationships in one file. It is indispensable at every stage from development to production. This guide covers setting up a typical web application stack.
Basic compose.yml
# compose.yml (docker-compose.yml also works)
services:
app:
build: .
ports: ['3000:3000']
environment:
NODE_ENV: production
DATABASE_URL: postgres://app:secret@db:5432/appdb
REDIS_URL: redis://cache:6379
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
restart: unless-stopped
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
POSTGRES_DB: appdb
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ['CMD', 'pg_isready', '-U', 'app']
interval: 5s
retries: 10
restart: unless-stopped
cache:
image: redis:7-alpine
volumes:
- redisdata:/data
restart: unless-stopped
nginx:
image: nginx:alpine
ports: ['80:80', '443:443']
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./certs:/etc/letsencrypt:ro
depends_on: [app]
restart: unless-stopped
volumes:
pgdata:
redisdata:
Essential Commands
# Build + start (detached)
docker compose up -d --build
# Tail logs
docker compose logs -f app
docker compose logs --since 10m
# Restart a single service
docker compose restart app
# Rebuild a service and start it
docker compose up -d --build app
# Status
docker compose ps
# Shut everything down
docker compose down
# Remove volumes too (data is gone!)
docker compose down -v
Environment Files
Do not leave sensitive data in compose.yml. Use a .env file — Compose reads it automatically.
# compose.yml
services:
app:
environment:
DATABASE_URL: ${DATABASE_URL}
JWT_SECRET: ${JWT_SECRET}
# .env (git-ignore!)
DATABASE_URL=postgres://...
JWT_SECRET=supersecret
# Use a different env file
docker compose --env-file .env.production up -d
Network Management
By default, Compose creates a project-specific bridge network and services find each other by service name (postgres://db:5432). You can define custom networks for isolation.
services:
frontend:
networks: [public]
backend:
networks: [public, internal]
db:
networks: [internal] # isolated from the outside world
networks:
public:
internal:
internal: true # no internet access
Volume Types
- Named volume:
pgdata:/var/lib/postgresql/data— managed by Docker, portable - Bind mount:
./config:/etc/app— host directory, ideal for development - tmpfs:
type: tmpfs— RAM only, good for sensitive temp data
services:
app:
volumes:
- ./src:/app/src # bind (hot reload)
- node_modules:/app/node_modules # named
- type: tmpfs
target: /tmp
Production Best Practices
services:
app:
image: ghcr.io/user/app:v1.2.3 # pinned tag, not :latest
restart: unless-stopped
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
memory: 128M
logging:
driver: json-file
options:
max-size: '10m'
max-file: '5'
read_only: true
tmpfs: ['/tmp', '/var/run']
security_opt:
- no-new-privileges:true
cap_drop: [ALL]
Override Files (Dev vs Prod)
# compose.yml (base)
services:
app:
image: myapp:latest
restart: unless-stopped
# compose.override.yml (loaded by default, for dev)
services:
app:
build: .
volumes: ['./src:/app/src']
command: npm run dev
# compose.prod.yml
services:
app:
image: ghcr.io/user/app:v1.2.3
environment:
NODE_ENV: production
# Usage
docker compose up # loads the override automatically
docker compose -f compose.yml -f compose.prod.yml up -d
Healthcheck and depends_on
depends_on only guarantees startup order, not that the service is ready. For actual readiness, use condition: service_healthy combined with a healthcheck.
services:
app:
depends_on:
db:
condition: service_healthy
db:
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U app']
interval: 5s
timeout: 3s
retries: 10
start_period: 10s
Profiles
services:
app: {}
db: {}
monitoring:
image: prom/prometheus
profiles: [monitoring] # does not start by default
# Usage
docker compose up # app, db
docker compose --profile monitoring up # + monitoring
Conclusion
Docker Compose is the sweet spot for small and medium production where Kubernetes is overkill. You bring up the whole stack with one command and do migrations/deploys in minutes. Perfect for running 5-10 services on a single VPS.
Reach out to KEYDAL for containerizing existing applications and production deployment with Compose. Contact us