Building Enterprise Multi-Container Stacks
Enterprise systems require separating concerns into distinct, cooperating microservices. In this chapter, learn how to architect, network, and orchestrate a production 3-tier stack comprising anNginx reverse proxy, a scalable REST API backend, a persistentPostgreSQL database, and an in-memory Redis cache.
1. Architectural Topology & Traffic Routing
In a secure multi-tier deployment, external client requests enter exclusively through the reverse proxy. The proxy terminates SSL, serves static assets, and forwards dynamic HTTP traffic to internal backend APIs. Databases and caches reside in a private backend network with zero published host ports:
Port 80 / 443
Nginx Proxy
Reverse ProxyNode/Python API
Business LogicPostgres & Redis
Persistence & Cache2. Complete Production compose.yaml
The following Compose specification implements dual-tier network isolation, health probes, restart recovery, and volume persistence:
services:
# 1. Reverse Proxy & Static Edge Gateway
gateway:
image: nginx:1.25-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./certbot/conf:/etc/letsencrypt:ro
networks:
- public-network
depends_on:
api:
condition: service_healthy
# 2. Scalable Application API Service
api:
build:
context: ./backend
dockerfile: Dockerfile
restart: unless-stopped
environment:
NODE_ENV: production
PORT: 5000
DATABASE_URL: postgresql://app_user:${DB_PASSWORD}@postgres-db:5432/app_production
REDIS_URL: redis://redis-cache:6379
networks:
- public-network
- private-network
expose:
- "5000"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/api/health"]
interval: 10s
timeout: 3s
retries: 3
depends_on:
postgres-db:
condition: service_healthy
redis-cache:
condition: service_healthy
# 3. Persistent PostgreSQL Database
postgres-db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: app_user
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: app_production
volumes:
- postgres_storage:/var/lib/postgresql/data
networks:
- private-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app_user -d app_production"]
interval: 5s
timeout: 3s
retries: 5
# 4. In-Memory Redis Session & Queue Cache
redis-cache:
image: redis:7-alpine
restart: unless-stopped
command: ["redis-server", "--appendonly", "yes"]
volumes:
- redis_storage:/data
networks:
- private-network
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 2s
retries: 3
networks:
public-network:
driver: bridge
private-network:
driver: bridge
volumes:
postgres_storage:
redis_storage:3. Nginx Upstream Configuration (nginx/conf.d/default.conf)
Configure Nginx to reverse-proxy HTTP requests to the api service name. Docker's embedded DNS automatically resolves the hostname api to the container's private IP address:
upstream backend_cluster {
server api:5000;
}
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://backend_cluster;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}Next Up: Docker Compose Commands
In Chapter 13, master everyday stack operations: starting in background, tailing logs, rebuilding caches, and scaling services.
Chapter 13: Docker Compose Commands →