Multi-Container Applications
Real-world applications often require multiple services working together. This lesson covers patterns for building and managing multi-container applications with Docker Compose.
Common Application Patterns
Three-Tier Architecture
# docker-compose.yml
services:
# Presentation tier
frontend:
build: ./frontend
ports:
- "3000:3000"
depends_on:
- backend
# Application tier
backend:
build: ./backend
environment:
DATABASE_URL: postgresql://postgres:secret@database:5432/app
depends_on:
- database
# Data tier
database:
image: postgres:15
environment:
POSTGRES_PASSWORD: secret
POSTGRES_DB: app
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
Microservices Pattern
services:
api-gateway:
build: ./gateway
ports:
- "80:80"
depends_on:
- users-service
- orders-service
- products-service
users-service:
build: ./services/users
environment:
DB_HOST: users-db
orders-service:
build: ./services/orders
environment:
DB_HOST: orders-db
USERS_SERVICE: http://users-service:3000
products-service:
build: ./services/products
environment:
DB_HOST: products-db
users-db:
image: postgres:15
volumes:
- users-data:/var/lib/postgresql/data
orders-db:
image: postgres:15
volumes:
- orders-data:/var/lib/postgresql/data
products-db:
image: mongo:6
volumes:
- products-data:/data/db
volumes:
users-data:
orders-data:
products-data:
Full-Stack Web Application
Project Structure
myapp/
├── docker-compose.yml
├── docker-compose.override.yml
├── .env
├── frontend/
│ ├── Dockerfile
│ ├── package.json
│ └── src/
├── backend/
│ ├── Dockerfile
│ ├── package.json
│ └── src/
└── nginx/
└── nginx.conf
Production Compose File
# docker-compose.yml
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./certs:/etc/nginx/certs:ro
depends_on:
- frontend
- backend
frontend:
build:
context: ./frontend
target: production
expose:
- "3000"
backend:
build:
context: ./backend
target: production
environment:
NODE_ENV: production
DATABASE_URL: postgresql://postgres:${DB_PASSWORD}@db:5432/myapp
REDIS_URL: redis://redis:6379
expose:
- "4000"
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
db:
image: postgres:15
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: myapp
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:alpine
volumes:
- redis-data:/data
volumes:
postgres-data:
redis-data:
Development Override
# docker-compose.override.yml
services:
frontend:
build:
target: development
ports:
- "3000:3000"
volumes:
- ./frontend/src:/app/src
environment:
- CHOKIDAR_USEPOLLING=true
backend:
build:
target: development
ports:
- "4000:4000"
volumes:
- ./backend/src:/app/src
environment:
NODE_ENV: development
db:
ports:
- "5432:5432"
redis:
ports:
- "6379:6379"
Nginx Configuration
# nginx/nginx.conf
upstream frontend {
server frontend:3000;
}
upstream backend {
server backend:4000;
}
server {
listen 80;
location / {
proxy_pass http://frontend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
}
location /api {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Background Workers
Add background job processing:
services:
api:
build: ./backend
command: npm run start:api
environment:
REDIS_URL: redis://redis:6379
worker:
build: ./backend
command: npm run start:worker
environment:
REDIS_URL: redis://redis:6379
deploy:
replicas: 2 # Run 2 worker instances
scheduler:
build: ./backend
command: npm run start:scheduler
environment:
REDIS_URL: redis://redis:6379
redis:
image: redis:alpine
Service Scaling
Scale specific services:
# Scale workers
docker compose up -d --scale worker=5
# Scale multiple services
docker compose up -d --scale worker=3 --scale api=2
# Or in compose file
services:
worker:
build: .
deploy:
replicas: 3
Shared Networks
Connect services across compose files:
# infrastructure/docker-compose.yml
services:
postgres:
image: postgres:15
networks:
- infrastructure
redis:
image: redis:alpine
networks:
- infrastructure
networks:
infrastructure:
name: shared-infrastructure
# app/docker-compose.yml
services:
api:
build: .
networks:
- default
- infrastructure
environment:
DATABASE_URL: postgresql://postgres:secret@postgres:5432/app
networks:
infrastructure:
external: true
name: shared-infrastructure
Init Containers
Run initialization before main services:
services:
db-init:
image: postgres:15
command: >
sh -c "
until pg_isready -h db -U postgres; do sleep 1; done
psql -h db -U postgres -d app -f /init/schema.sql
"
volumes:
- ./init:/init
depends_on:
db:
condition: service_healthy
api:
build: .
depends_on:
db-init:
condition: service_completed_successfully
Service Dependencies Graph
┌─────────────────────────────────────────────────────────────────┐
│ Application Services │
│ │
│ ┌──────────┐ │
│ │ nginx │ │
│ └────┬─────┘ │
│ │ │
│ ├─────────────────┐ │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ │
│ │ frontend │ │ backend │ │
│ └──────────┘ └────┬─────┘ │
│ │ │
│ ┌───────────┼───────────┐ │
│ ▼ ▼ ▼ │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ db │ │ redis │ │ worker │ │
│ └────────┘ └────────┘ └────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
depends_on: ensures start order
healthcheck: ensures service readiness
networks: controls communication paths
Debugging Multi-Container Apps
View All Logs
# All services
docker compose logs
# Specific services
docker compose logs api db
# Follow with timestamps
docker compose logs -f -t
# Last 100 lines
docker compose logs --tail 100
Execute Commands
# Shell into service
docker compose exec api bash
# Run one-off command
docker compose run --rm api npm run migrate
# View running processes
docker compose top
Inspect State
# Service status
docker compose ps
# Network connections
docker compose exec api netstat -tlnp
# Environment variables
docker compose exec api env
Common Patterns
Sidecar Pattern
services:
app:
build: .
volumes:
- logs:/app/logs
log-shipper:
image: fluent/fluentd
volumes:
- logs:/logs:ro
depends_on:
- app
volumes:
logs:
Ambassador Pattern
services:
app:
build: .
environment:
DB_HOST: db-ambassador
db-ambassador:
image: haproxy
volumes:
- ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
depends_on:
- db-primary
- db-replica
Key Takeaways
- Multi-container apps separate concerns into focused services
- Use depends_on with health checks for reliable startup
- Override files customize base configuration for environments
- Nginx reverse proxy unifies frontend and backend under one port
- Scale workers independently of the main application
- External networks connect services across compose projects
- Use docker compose logs and exec for debugging
- Follow patterns like three-tier, microservices, and sidecars

