Step 2
Step 2 — docker-compose patterns
30 min
Step 2 — docker-compose patterns
Real services aren't single containers. App + DB + Redis + queue — docker-compose brings them up together.
First docker-compose.yml
services:
postgres:
image: postgres:17-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: user
POSTGRES_PASSWORD: secret
volumes:
- pg-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d myapp"]
interval: 5s
redis:
image: redis:7-alpine
command: redis-server --appendonly yes
volumes:
- redis-data:/data
app:
build: .
depends_on:
postgres: { condition: service_healthy }
redis: { condition: service_started }
environment:
DB_URL: postgresql://user:secret@postgres:5432/myapp
REDIS_URL: redis://redis:6379
ports:
- "127.0.0.1:3000:3000"
restart: unless-stopped
volumes:
pg-data:
redis-data:
docker compose up -d
docker compose ps
docker compose logs -f app
docker compose down
Six patterns
- healthcheck — gate
depends_onwithcondition: service_healthy - 127.0.0.1 binding — only Caddy reaches the app port
- named volumes —
pg-datafor production - env_file — secrets out of YAML
- depends_on — startup order
- restart: unless-stopped — survive reboots
.env split
services:
app:
env_file: [.env.prod]
DEV vs PROD split
infra/
├── dev/docker-compose.yml
└── prod/docker-compose.yml
DEV: hot-reload + direct exposure. PROD: behind Caddy + loopback only.
Try it
Bring up Postgres + Redis + app from the YAML above. docker compose ps should show all healthy.
Going deeper
Next
Step 3 — Caddy.