Stage 4 · Provision
Docker Compose
Multi-Container Apps
Connect an API, PostgreSQL, Redis, and worker services using Docker Compose DNS and networks.
Real-World Architecture
Most applications consist of multiple services: an API server, a database, a cache, a message queue, and background workers. Docker Compose lets you define all of these in one file, start them together, and manage them as a single unit.
Full Compose Stack
services:
api:
build: ./api
ports:
- "3000:3000"
environment:
DATABASE_URL: postgres://postgres:secret@db:5432/app
REDIS_URL: redis://cache:6379
depends_on:
- db
- cache
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: app
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql/data
cache:
image: redis:7-alpine
volumes:
- redisdata:/data
worker:
build: ./worker
environment:
DATABASE_URL: postgres://postgres:secret@db:5432/app
REDIS_URL: redis://cache:6379
depends_on:
- db
- cache
volumes:
pgdata:
redisdata:This stack has four services: api, db, cache, and worker. They communicate using DNS names (db, cache) on the default Compose network. Data persists in named volumes pgdata and redisdata.
Service Communication
Services communicate using their service name as the hostname. The api service connects to the database at postgres://postgres:secret@db:5432/app — db resolves to the database container's IP address through Docker's embedded DNS server.
services:
api:
environment:
# Use service names, not localhost or 127.0.0.1
DATABASE_URL: postgres://user:pass@db:5432/mydb
REDIS_URL: redis://cache:6379
MQ_URL: amqp://queue:5672
# Wrong — these will not work
# DATABASE_URL: postgres://user:pass@localhost:5432/mydb
# DATABASE_URL: postgres://user:pass@127.0.0.1:5432/mydbEach service gets its own network namespace. localhost inside one container refers to that container, not another service. Use the service name as the hostname — Docker resolves it to the correct container.
localhost inside a container refers to that container itself, not other services. Use the service name as the hostname. Docker Compose sets up DNS resolution automatically.
Database Services
Database containers need persistent storage, initialization scripts, and proper configuration. Compose supports all of these through volumes, environment variables, and entrypoint scripts.
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: app
POSTGRES_USER: postgres
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
ports:
- "127.0.0.1:5432:5432" # Localhost only
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
volumes:
pgdata:The init.sql file runs when the database is first created. pgdata persists the database across restarts. The healthcheck ensures dependent services wait until PostgreSQL is ready. Ports are bound to localhost for security.
Background Workers
Background workers consume messages, process jobs, or run scheduled tasks. They typically share the same image or codebase as the API but run a different command.
services:
worker:
build: .
command: python worker.py
environment:
DATABASE_URL: postgres://postgres:secret@db:5432/app
REDIS_URL: redis://cache:6379
depends_on:
db:
condition: service_healthy
restart: unless-stoppedThe worker shares the same build context as the api but runs a different command. depends_on with service_healthy ensures the database is ready before the worker starts. restart: unless-stopped keeps it running.
Admin Tools
Admin tools like pgAdmin, Redis Commander, and Adminer provide web interfaces for databases. These are optional services that should not run in production but are invaluable during development.
services:
adminer:
image: adminer:latest
ports:
- "8081:8080"
depends_on:
- db
redis-commander:
image: rediscommander/redis-commander:latest
environment:
REDIS_HOSTS: local:cache:6379
ports:
- "8082:8081"
depends_on:
- cacheAdminer provides a database management UI at localhost:8081. Redis Commander gives a web interface for Redis at localhost:8082. These tools connect to services using Compose DNS names.
Wrap admin tools in a Compose profile so they do not start by default: docker compose --profile dev up starts everything including admin tools. docker compose up starts only production services.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.