< RETROBOX
DEVOPS GUIDE 10 MIN READ
ENGINEERING MASTERCLASS

How to Build Multi-Container Docker Compose Stacks (Production Architecture)

Complete DevOps guide to writing production-grade docker-compose.yml stacks with Node.js, PostgreSQL 16, Redis 7, Nginx reverse proxy, healthchecks, and internal bridge networks.

🛠️ Interactive Tool Available:

Want to generate a custom production-ready Docker Compose file visually without writing YAML from scratch? Use our Docker Compose Generator.

1. Core Principles of Multi-Container Architecture

Modern cloud applications rarely run as single monolithic binaries. A robust production stack typically decouples application logic from stateful databases, ephemeral caches, message brokers, and public ingress proxies.

When organizing services with Docker Compose, adhere to the three foundational pillars of container engineering:

2. The Production-Ready 3-Tier Compose Template

Below is a full-featured, battle-tested docker-compose.yml file implementing healthchecks, isolated private networking, automatic restart policies, and named persistent storage.

version: '3.8'

services:
  # -------------------------------------------------------------
  # 1. Public Ingress / Reverse Proxy Layer
  # -------------------------------------------------------------
  reverse-proxy:
    image: nginx:alpine
    container_name: production_proxy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./certs:/etc/ssl/certs:ro
    depends_on:
      api-server:
        condition: service_healthy
    networks:
      - public-network
      - private-backend

  # -------------------------------------------------------------
  # 2. Application Backend (Node.js / Express / Fastify)
  # -------------------------------------------------------------
  api-server:
    build:
      context: .
      dockerfile: Dockerfile
      target: production
    container_name: production_api
    restart: unless-stopped
    environment:
      - NODE_ENV=production
      - PORT=3000
      - DATABASE_URL=postgres://app_user:${POSTGRES_PASSWORD}@postgres-db:5432/production_db
      - REDIS_URL=redis://redis-cache:6379
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health"]
      interval: 15s
      timeout: 5s
      retries: 3
      start_period: 10s
    depends_on:
      postgres-db:
        condition: service_healthy
      redis-cache:
        condition: service_healthy
    networks:
      - private-backend

  # -------------------------------------------------------------
  # 3. Database Layer (PostgreSQL 16)
  # -------------------------------------------------------------
  postgres-db:
    image: postgres:16-alpine
    container_name: production_postgres
    restart: unless-stopped
    environment:
      POSTGRES_DB: production_db
      POSTGRES_USER: app_user
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app_user -d production_db"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - private-backend

  # -------------------------------------------------------------
  # 4. In-Memory Cache & Message Broker (Redis 7)
  # -------------------------------------------------------------
  redis-cache:
    image: redis:7-alpine
    container_name: production_redis
    restart: unless-stopped
    command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3
    networks:
      - private-backend

# ---------------------------------------------------------------
# Persistent Storage Volumes
# ---------------------------------------------------------------
volumes:
  postgres_data:
    driver: local
  redis_data:
    driver: local

# ---------------------------------------------------------------
# Isolated Bridge Networks
# ---------------------------------------------------------------
networks:
  public-network:
    driver: bridge
  private-backend:
    driver: bridge
    internal: true

3. Advanced Healthcheck Orchestration (Avoid Race Conditions)

One of the most frequent mistakes in Docker Compose is relying on basic depends_on: [postgres-db]. By default, Docker only checks if the container has started, not if PostgreSQL is ready to accept TCP socket connections.

To eliminate startup race conditions permanently, define explicit healthchecks using pg_isready and bind your dependent service using condition: service_healthy.

4. Security & Environment Variable Isolation

Never commit database passwords or JWT signing keys into your docker-compose.yml file. Always declare variables inside a separate .env file placed in your root directory, and add .env to your .gitignore.

★ RELATED GUIDES: 🐳 Docker Stacks ⚡ Postgres + Redis 🔐 JWT Security 📐 SQL to TS {} All Tools
YOUR AD HERE

Want to promote your app, SaaS, dev tool, or business to thousands of engineers? Affordable dedicated spots available.

GET THIS SPOT ($) →