Multi-Service Setup
#Multi-Service Setup
After this lesson you'll know:
- how to orchestrate app + DB + Redis with Compose
- what depends_on does (and doesn't do)
- Build vs Image — the difference
- how healthchecks work in Compose
#App + DB + Redis — the standard setup
services:
api:
build: .
ports:
- "3000:3000"
environment:
DATABASE_URL: postgresql://user:pass@db:5432/myapp
REDIS_URL: redis://cache:6379
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
db:
image: postgres:16-alpine
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d myapp"]
interval: 5s
timeout: 5s
retries: 5
start_period: 10s
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: ***
POSTGRES_DB: myapp
cache:
image: redis:7-alpine
volumes:
pgdata:
#build: vs image:
image: nginx— uses the image from Docker Hubbuild: .— builds the local Dockerfilebuild: ./my-folder— builds a Dockerfile in a subfolder
#depends_on & Healthchecks — the right way
depends_on starts services in the correct order. But without extra config, it does NOT wait until a service is ready.
Postgres takes 10-20 seconds on first start to initialize database files. Without a healthcheck, your app starts, connects — and gets connection refused.
The solution: A healthcheck periodically tests if the service is really ready. depends_on with condition: service_healthy then waits exactly as long as needed.
db:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d myapp"]
interval: 5s
timeout: 5s
retries: 5
start_period: 10s
test: The command that checks if the service is aliveinterval: How often to check (every 5s)timeout: Max time for one check (5s)retries: How many failures to toleratestart_period: Wait time before the first check — gives the service startup time
#docker compose watch — Hot Reload for Development
Since Docker Compose v2.22, there's docker compose watch — your code in the container updates automatically when you edit a file.
services:
api:
build: .
ports:
- "3000:3000"
develop:
watch:
- action: sync
path: ./src
target: /app/src
- action: rebuild
path: ./package.json
docker compose watch
# Changes in ./src are synced live into the container
# Changes to package.json trigger a full rebuild
No more manual restarting. Compose detects action: sync for file changes and action: rebuild for dependencies.
Actions explained:
sync— copies changed files into the running container (fast)rebuild— rebuilds the image and restarts the container (for dependencies)restart— restarts the container without build (for config changes)
#Environment variables from .env
# .env file (automatically loaded by compose)
POSTGRES_PASSWORD=***
DATABASE_URL=postgresql://user:pass@db:5432/myapp
services:
api:
environment:
DATABASE_URL: ${DATABASE_URL}
# Alternative: env_file
services:
api:
env_file: .env.production
#✋ Try it out
- Create a compose.yml with Postgres + Redis + Healthcheck, start it and see how
docker compose psshowshealthy - Add
develop:
watch:
- action: sync
path: ./src
target: /app/src to your api service. Start with docker compose watch
docker compose logs -f api— see only one service's logs
#📌 Summary
- build: . builds the local Dockerfile, image: ... pulls from Docker Hub
- depends_on with condition: service_healthy waits until the service is ready
- Healthchecks are the robust way to control startup order
- docker compose watch syncs code changes live into the container