Day 16: Defining Multi-Container Apps with Compose

Learning Objectives: By the end of this lesson, you will be able to:

  • Design complex multi-container architectures using Docker Compose
  • Implement advanced service dependency management and orchestration patterns
  • Configure sophisticated networking, scaling, and resource management
  • Build production-ready applications with proper separation of concerns

1. Welcome and Introduction (5 minutes)

Icebreaker Activity

Yesterday we learned to conduct a small orchestra with Docker Compose. Today, we’re going to conduct a full symphony! We’ll design complex applications with multiple services, each playing their part in perfect harmony. Think Netflix’s microservices, Uber’s distributed systems, or any modern web platform - they all started with the principles we’ll master today.

Lesson Overview

  • Duration: 60-75 minutes
  • Prerequisites: Completion of Day 15, solid understanding of Docker Compose basics
  • Materials Needed: Docker Desktop, text editor, understanding of microservices architecture concepts

2. Learning Objectives and Session Outline (5 minutes)

What We’ll Cover Today:

  1. Advanced Architecture Patterns - Microservices, service mesh, and distributed system design
  2. Sophisticated Service Dependencies - Complex startup ordering and health-based dependencies
  3. Advanced Networking - Multi-tier networks, service isolation, and communication patterns
  4. Scaling and Load Balancing - Horizontal scaling and load distribution strategies
  5. Production Patterns - Resource management, monitoring, and deployment strategies

Success Criteria:

By the end of this session, you should be able to design and implement enterprise-grade multi-container applications that are scalable, maintainable, and production-ready.

3. Foundation Knowledge (15-20 minutes)

Multi-Container Application Architecture Patterns

Microservices Architecture

Modern applications are often built as a collection of loosely coupled services, each responsible for a specific business capability.

Real-World Analogy: Think of a modern hospital. Instead of one doctor handling everything, you have specialists: cardiologists, neurologists, radiologists, pharmacists, etc. Each expert focuses on their domain, but they all work together to provide complete patient care.

Key Microservices Principles:

  • Single Responsibility: Each service has one clear purpose
  • Decentralized: Services manage their own data and business logic
  • Fault Isolated: Failure in one service doesn’t bring down the entire system
  • Technology Agnostic: Different services can use different technologies
  • Independently Deployable: Services can be updated without affecting others

Common Architecture Patterns

1. Three-Tier Architecture
# Frontend → API Gateway → Backend Services → Database
services:
  frontend:     # Presentation Tier
  api-gateway:  # Application Tier
  user-service: # Business Logic Tier
  database:     # Data Tier
2. Event-Driven Architecture
# Services communicate through message queues
services:
  producer-service:
  message-queue:
  consumer-service:
  event-store:
3. CQRS (Command Query Responsibility Segregation)
# Separate read and write operations
services:
  command-api:    # Handles writes
  query-api:      # Handles reads
  write-db:       # Optimized for writes
  read-db:        # Optimized for reads
  event-bus:      # Synchronizes data

Service Discovery and Communication Patterns

DNS-Based Discovery (Default in Compose)

services:
  user-service:
    image: user-api:latest
  
  order-service:
    image: order-api:latest
    environment:
      USER_SERVICE_URL: http://user-service:3000

API Gateway Pattern

services:
  api-gateway:
    image: nginx:alpine
    ports:
      - "80:80"
    # Routes requests to appropriate services
  
  user-service:
    image: user-api:latest
    expose: ["3001"]
  
  order-service:
    image: order-api:latest
    expose: ["3002"]

Key Terminology

  • Service Mesh: Infrastructure layer handling service-to-service communication
  • Circuit Breaker: Pattern preventing cascading failures
  • Sidecar Pattern: Helper container deployed alongside main container
  • Blue-Green Deployment: Zero-downtime deployment strategy
  • Canary Deployment: Gradual rollout to subset of users

4. Detailed Explanation and Examples (25-30 minutes)

Advanced Service Dependencies and Health Checks

Complex Dependency Chains

version: '3.8'

services:
  # Database must start first
  database:
    image: postgres:13-alpine
    environment:
      POSTGRES_DB: ecommerce
      POSTGRES_USER: app
      POSTGRES_PASSWORD: secret
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d ecommerce"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 60s
    volumes:
      - postgres_data:/var/lib/postgresql/data

  # Redis for caching and sessions
  redis:
    image: redis:alpine
    command: redis-server --appendonly yes
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5
    volumes:
      - redis_data:/data

  # Message queue for async processing
  rabbitmq:
    image: rabbitmq:3-management-alpine
    environment:
      RABBITMQ_DEFAULT_USER: app
      RABBITMQ_DEFAULT_PASS: secret
    healthcheck:
      test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
      interval: 30s
      timeout: 30s
      retries: 3
    volumes:
      - rabbitmq_data:/var/lib/rabbitmq

  # User management service
  user-service:
    build: ./services/user-service
    environment:
      DATABASE_URL: postgresql://app:secret@database:5432/ecommerce
      REDIS_URL: redis://redis:6379
    depends_on:
      database:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3001/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s

  # Product catalog service
  product-service:
    build: ./services/product-service
    environment:
      DATABASE_URL: postgresql://app:secret@database:5432/ecommerce
      REDIS_URL: redis://redis:6379
    depends_on:
      database:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3002/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  # Order processing service
  order-service:
    build: ./services/order-service
    environment:
      DATABASE_URL: postgresql://app:secret@database:5432/ecommerce
      REDIS_URL: redis://redis:6379
      RABBITMQ_URL: amqp://app:secret@rabbitmq:5672
      USER_SERVICE_URL: http://user-service:3001
      PRODUCT_SERVICE_URL: http://product-service:3002
    depends_on:
      database:
        condition: service_healthy
      redis:
        condition: service_healthy
      rabbitmq:
        condition: service_healthy
      user-service:
        condition: service_healthy
      product-service:
        condition: service_healthy

  # Background job processor
  worker:
    build: ./services/worker
    environment:
      DATABASE_URL: postgresql://app:secret@database:5432/ecommerce
      RABBITMQ_URL: amqp://app:secret@rabbitmq:5672
    depends_on:
      database:
        condition: service_healthy
      rabbitmq:
        condition: service_healthy
    deploy:
      replicas: 2  # Multiple workers for parallel processing

  # API Gateway
  api-gateway:
    build: ./gateway
    ports:
      - "80:80"
      - "443:443"
    environment:
      USER_SERVICE_URL: http://user-service:3001
      PRODUCT_SERVICE_URL: http://product-service:3002
      ORDER_SERVICE_URL: http://order-service:3003
    depends_on:
      user-service:
        condition: service_healthy
      product-service:
        condition: service_healthy
      order-service:
        condition: service_healthy

  # Frontend application
  frontend:
    build: ./frontend
    environment:
      API_GATEWAY_URL: http://api-gateway
    depends_on:
      - api-gateway

volumes:
  postgres_data:
  redis_data:
  rabbitmq_data:

Advanced Networking Patterns

Multi-Tier Network Isolation

version: '3.8'

services:
  # Frontend tier (public-facing)
  frontend:
    build: ./frontend
    networks:
      - frontend-tier
    ports:
      - "80:80"

  # API Gateway (frontend + backend access)
  api-gateway:
    build: ./gateway
    networks:
      - frontend-tier
      - backend-tier
    depends_on:
      - user-service
      - product-service

  # Business logic services (backend only)
  user-service:
    build: ./services/user-service
    networks:
      - backend-tier
      - database-tier
    expose:
      - "3001"

  product-service:
    build: ./services/product-service
    networks:
      - backend-tier
      - database-tier
    expose:
      - "3002"

  # Data tier (most restricted)
  database:
    image: postgres:13-alpine
    networks:
      - database-tier
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: app
      POSTGRES_PASSWORD: secret
    volumes:
      - db_data:/var/lib/postgresql/data

  # External service connector (special network)
  external-connector:
    build: ./connectors/external-api
    networks:
      - backend-tier
      - external-net

networks:
  frontend-tier:
    driver: bridge
  backend-tier:
    driver: bridge
    internal: true  # No external internet access
  database-tier:
    driver: bridge
    internal: true  # Most secure - no external access
  external-net:
    driver: bridge  # Allows external API calls

volumes:
  db_data:

Service Mesh Pattern with Envoy Proxy

version: '3.8'

services:
  # User service with sidecar proxy
  user-service:
    build: ./services/user-service
    networks:
      - service-mesh

  user-service-proxy:
    image: envoyproxy/envoy:v1.24-latest
    networks:
      - service-mesh
    volumes:
      - ./envoy/user-service-envoy.yaml:/etc/envoy/envoy.yaml
    depends_on:
      - user-service

  # Product service with sidecar proxy
  product-service:
    build: ./services/product-service
    networks:
      - service-mesh

  product-service-proxy:
    image: envoyproxy/envoy:v1.24-latest
    networks:
      - service-mesh
    volumes:
      - ./envoy/product-service-envoy.yaml:/etc/envoy/envoy.yaml
    depends_on:
      - product-service

  # Service discovery and configuration
  consul:
    image: consul:latest
    networks:
      - service-mesh
    ports:
      - "8500:8500"
    command: consul agent -dev -client=0.0.0.0

networks:
  service-mesh:
    driver: bridge

Scaling and Load Balancing

Horizontal Scaling with Load Balancer

version: '3.8'

services:
  # Load balancer
  load-balancer:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx/load-balancer.conf:/etc/nginx/nginx.conf
    depends_on:
      - api-service

  # Scalable API service
  api-service:
    build: ./api
    environment:
      DATABASE_URL: postgresql://app:secret@database:5432/myapp
      REDIS_URL: redis://redis:6379
    depends_on:
      database:
        condition: service_healthy
      redis:
        condition: service_healthy
    deploy:
      replicas: 3  # Start with 3 instances

  # Shared database
  database:
    image: postgres:13-alpine
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: app
      POSTGRES_PASSWORD: secret
    volumes:
      - db_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d myapp"]
      interval: 10s
      timeout: 5s
      retries: 5

  # Shared cache
  redis:
    image: redis:alpine
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5

volumes:
  db_data:
  redis_data:

Auto-scaling with Resource Limits

version: '3.8'

services:
  web-service:
    build: ./web
    deploy:
      replicas: 2
      resources:
        limits:
          memory: 512M
          cpus: '0.5'
        reservations:
          memory: 256M
          cpus: '0.25'
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
        window: 120s
      update_config:
        parallelism: 1
        delay: 10s
        failure_action: rollback
        monitor: 60s

Configuration Management Patterns

Environment-Specific Configurations

# docker-compose.yml (base)
version: '3.8'

services:
  api:
    build: ./api
    environment:
      NODE_ENV: ${NODE_ENV:-development}
    env_file:
      - .env
      - .env.${NODE_ENV:-development}

  database:
    image: postgres:13-alpine
    environment:
      POSTGRES_DB: ${DB_NAME:-myapp}
      POSTGRES_USER: ${DB_USER:-app}
      POSTGRES_PASSWORD: ${DB_PASSWORD:-secret}
# .env.development
NODE_ENV=development
DB_NAME=myapp_dev
LOG_LEVEL=debug
ENABLE_DEBUGGING=true

# .env.staging
NODE_ENV=staging
DB_NAME=myapp_staging
LOG_LEVEL=info
ENABLE_DEBUGGING=false

# .env.production
NODE_ENV=production
DB_NAME=myapp_prod
LOG_LEVEL=warn
ENABLE_DEBUGGING=false

5. Live Coding Demonstration (30-35 minutes)

Example 1: E-commerce Microservices Platform

Let’s build a comprehensive e-commerce platform with multiple services:

# Create project structure
mkdir ecommerce-platform
cd ecommerce-platform

# Create service directories
mkdir -p services/{user-service,product-service,order-service,payment-service}
mkdir -p infrastructure/{gateway,monitoring}
mkdir -p shared/{database,message-queue}

Core Infrastructure Services

# docker-compose.infrastructure.yml
version: '3.8'

services:
  # PostgreSQL Database
  postgres:
    image: postgres:13-alpine
    environment:
      POSTGRES_DB: ecommerce
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${DB_PASSWORD:-secret123}
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./shared/database/init:/docker-entrypoint-initdb.d
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d ecommerce"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - database-tier

  # Redis for caching and sessions
  redis:
    image: redis:alpine
    command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD:-redis123}
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "--no-auth-warning", "auth", "${REDIS_PASSWORD:-redis123}", "ping"]
      interval: 10s
      timeout: 5s
      retries: 3
    networks:
      - cache-tier

  # RabbitMQ for message queuing
  rabbitmq:
    image: rabbitmq:3-management-alpine
    environment:
      RABBITMQ_DEFAULT_USER: ${RABBITMQ_USER:-app}
      RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASSWORD:-rabbit123}
    volumes:
      - rabbitmq_data:/var/lib/rabbitmq
    healthcheck:
      test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
      interval: 30s
      timeout: 30s
      retries: 3
    networks:
      - message-tier
    ports:
      - "15672:15672"  # Management UI

  # Elasticsearch for search and analytics
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.17.0
    environment:
      - discovery.type=single-node
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    volumes:
      - elasticsearch_data:/usr/share/elasticsearch/data
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:9200/_cluster/health || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 5
    networks:
      - search-tier

volumes:
  postgres_data:
  redis_data:
  rabbitmq_data:
  elasticsearch_data:

networks:
  database-tier:
    driver: bridge
    internal: true
  cache-tier:
    driver: bridge
    internal: true
  message-tier:
    driver: bridge
    internal: true
  search-tier:
    driver: bridge
    internal: true

Business Logic Services

# docker-compose.services.yml
version: '3.8'

services:
  # User Management Service
  user-service:
    build:
      context: ./services/user-service
      dockerfile: Dockerfile
    environment:
      NODE_ENV: ${NODE_ENV:-development}
      DATABASE_URL: postgresql://app:${DB_PASSWORD:-secret123}@postgres:5432/ecommerce
      REDIS_URL: redis://:${REDIS_PASSWORD:-redis123}@redis:6379
      JWT_SECRET: ${JWT_SECRET:-supersecret}
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3001/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s
    networks:
      - database-tier
      - cache-tier
      - service-tier
    deploy:
      replicas: 2
      resources:
        limits:
          memory: 256M
          cpus: '0.3'

  # Product Catalog Service
  product-service:
    build:
      context: ./services/product-service
      dockerfile: Dockerfile
    environment:
      NODE_ENV: ${NODE_ENV:-development}
      DATABASE_URL: postgresql://app:${DB_PASSWORD:-secret123}@postgres:5432/ecommerce
      REDIS_URL: redis://:${REDIS_PASSWORD:-redis123}@redis:6379
      ELASTICSEARCH_URL: http://elasticsearch:9200
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
      elasticsearch:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3002/health"]
      interval: 30s
      timeout: 10s
      retries: 3
    networks:
      - database-tier
      - cache-tier
      - search-tier
      - service-tier
    deploy:
      replicas: 2

  # Order Management Service
  order-service:
    build:
      context: ./services/order-service
      dockerfile: Dockerfile
    environment:
      NODE_ENV: ${NODE_ENV:-development}
      DATABASE_URL: postgresql://app:${DB_PASSWORD:-secret123}@postgres:5432/ecommerce
      REDIS_URL: redis://:${REDIS_PASSWORD:-redis123}@redis:6379
      RABBITMQ_URL: amqp://${RABBITMQ_USER:-app}:${RABBITMQ_PASSWORD:-rabbit123}@rabbitmq:5672
      USER_SERVICE_URL: http://user-service:3001
      PRODUCT_SERVICE_URL: http://product-service:3002
      PAYMENT_SERVICE_URL: http://payment-service:3004
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
      rabbitmq:
        condition: service_healthy
      user-service:
        condition: service_healthy
      product-service:
        condition: service_healthy
    networks:
      - database-tier
      - cache-tier
      - message-tier
      - service-tier

  # Payment Processing Service
  payment-service:
    build:
      context: ./services/payment-service
      dockerfile: Dockerfile
    environment:
      NODE_ENV: ${NODE_ENV:-development}
      DATABASE_URL: postgresql://app:${DB_PASSWORD:-secret123}@postgres:5432/ecommerce
      REDIS_URL: redis://:${REDIS_PASSWORD:-redis123}@redis:6379
      STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY}
      PAYPAL_CLIENT_ID: ${PAYPAL_CLIENT_ID}
      PAYPAL_CLIENT_SECRET: ${PAYPAL_CLIENT_SECRET}
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - database-tier
      - cache-tier
      - service-tier
      - external-tier  # Needs external access for payment providers

networks:
  database-tier:
    external: true
    name: ecommerce_database-tier
  cache-tier:
    external: true
    name: ecommerce_cache-tier
  message-tier:
    external: true
    name: ecommerce_message-tier
  search-tier:
    external: true
    name: ecommerce_search-tier
  service-tier:
    driver: bridge
  external-tier:
    driver: bridge

API Gateway and Frontend

# docker-compose.gateway.yml
version: '3.8'

services:
  # API Gateway with Kong
  api-gateway:
    image: kong:alpine
    environment:
      KONG_DATABASE: "off"
      KONG_DECLARATIVE_CONFIG: /kong/declarative/kong.yml
      KONG_PROXY_ACCESS_LOG: /dev/stdout
      KONG_ADMIN_ACCESS_LOG: /dev/stdout
      KONG_PROXY_ERROR_LOG: /dev/stderr
      KONG_ADMIN_ERROR_LOG: /dev/stderr
      KONG_ADMIN_LISTEN: 0.0.0.0:8001
    volumes:
      - ./infrastructure/gateway/kong.yml:/kong/declarative/kong.yml
    ports:
      - "80:8000"      # Proxy port
      - "8001:8001"    # Admin API
    depends_on:
      - user-service
      - product-service
      - order-service
      - payment-service
    networks:
      - service-tier
      - frontend-tier

  # Frontend Application
  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    environment:
      REACT_APP_API_GATEWAY_URL: http://api-gateway:8000
      REACT_APP_ENVIRONMENT: ${NODE_ENV:-development}
    depends_on:
      - api-gateway
    networks:
      - frontend-tier
    ports:
      - "3000:80"

  # Admin Dashboard
  admin-dashboard:
    build:
      context: ./admin-dashboard
      dockerfile: Dockerfile
    environment:
      REACT_APP_API_GATEWAY_URL: http://api-gateway:8000
      REACT_APP_ADMIN_MODE: "true"
    depends_on:
      - api-gateway
    networks:
      - frontend-tier
    ports:
      - "3001:80"

networks:
  service-tier:
    external: true
    name: ecommerce_service-tier
  frontend-tier:
    driver: bridge

Background Workers and Jobs

# docker-compose.workers.yml
version: '3.8'

services:
  # Email notification worker
  email-worker:
    build:
      context: ./workers/email-worker
      dockerfile: Dockerfile
    environment:
      NODE_ENV: ${NODE_ENV:-development}
      RABBITMQ_URL: amqp://${RABBITMQ_USER:-app}:${RABBITMQ_PASSWORD:-rabbit123}@rabbitmq:5672
      SENDGRID_API_KEY: ${SENDGRID_API_KEY}
      SMTP_HOST: ${SMTP_HOST}
      SMTP_PORT: ${SMTP_PORT}
      SMTP_USER: ${SMTP_USER}
      SMTP_PASS: ${SMTP_PASS}
    depends_on:
      rabbitmq:
        condition: service_healthy
    networks:
      - message-tier
      - external-tier
    deploy:
      replicas: 2

  # Image processing worker
  image-worker:
    build:
      context: ./workers/image-worker
      dockerfile: Dockerfile
    environment:
      NODE_ENV: ${NODE_ENV:-development}
      RABBITMQ_URL: amqp://${RABBITMQ_USER:-app}:${RABBITMQ_PASSWORD:-rabbit123}@rabbitmq:5672
      AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
      AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
      AWS_S3_BUCKET: ${AWS_S3_BUCKET}
    depends_on:
      rabbitmq:
        condition: service_healthy
    networks:
      - message-tier
      - external-tier
    volumes:
      - image_processing_temp:/tmp/image-processing
    deploy:
      replicas: 1
      resources:
        limits:
          memory: 1G
          cpus: '1.0'

  # Analytics and reporting worker
  analytics-worker:
    build:
      context: ./workers/analytics-worker
      dockerfile: Dockerfile
    environment:
      NODE_ENV: ${NODE_ENV:-development}
      DATABASE_URL: postgresql://app:${DB_PASSWORD:-secret123}@postgres:5432/ecommerce
      ELASTICSEARCH_URL: http://elasticsearch:9200
      RABBITMQ_URL: amqp://${RABBITMQ_USER:-app}:${RABBITMQ_PASSWORD:-rabbit123}@rabbitmq:5672
    depends_on:
      postgres:
        condition: service_healthy
      elasticsearch:
        condition: service_healthy
      rabbitmq:
        condition: service_healthy
    networks:
      - database-tier
      - search-tier
      - message-tier

volumes:
  image_processing_temp:

networks:
  database-tier:
    external: true
    name: ecommerce_database-tier
  search-tier:
    external: true
    name: ecommerce_search-tier
  message-tier:
    external: true
    name: ecommerce_message-tier
  external-tier:
    driver: bridge

Deployment Script

#!/bin/bash
# deploy.sh

set -e

echo "🚀 Deploying E-commerce Platform..."

# Create external networks
echo "📡 Creating networks..."
docker network create ecommerce_database-tier --internal || true
docker network create ecommerce_cache-tier --internal || true
docker network create ecommerce_message-tier --internal || true
docker network create ecommerce_search-tier --internal || true
docker network create ecommerce_service-tier || true

# Deploy infrastructure first
echo "🏗️  Starting infrastructure services..."
docker-compose -f docker-compose.infrastructure.yml up -d

# Wait for infrastructure to be healthy
echo "⏳ Waiting for infrastructure to be ready..."
docker-compose -f docker-compose.infrastructure.yml exec postgres pg_isready -U app -d ecommerce
docker-compose -f docker-compose.infrastructure.yml exec redis redis-cli ping

# Deploy business services
echo "🔧 Starting business services..."
docker-compose -f docker-compose.services.yml up -d

# Deploy gateway and frontend
echo "🌐 Starting gateway and frontend..."
docker-compose -f docker-compose.gateway.yml up -d

# Deploy background workers
echo "⚙️  Starting background workers..."
docker-compose -f docker-compose.workers.yml up -d

echo "✅ Deployment complete!"
echo "🌍 Frontend: http://localhost:3000"
echo "👑 Admin: http://localhost:3001"
echo "🐰 RabbitMQ: http://localhost:15672"
echo "🔍 Kong Admin: http://localhost:8001"

# Health check
echo "🔍 Running health checks..."
sleep 30
curl -f http://localhost:3000 && echo "Frontend: ✅"
curl -f http://localhost:8001 && echo "Gateway Admin: ✅"

6. Hands-On Practice (25-30 minutes)

Exercise 1: Build a Blogging Platform (20 minutes)

Objective: Design and implement a multi-service blogging platform

Requirements: Create a blogging platform with the following services:

  • Auth Service: User registration, login, JWT token management
  • Content Service: Article creation, editing, storage
  • Comment Service: Comment management with moderation
  • Search Service: Full-text search using Elasticsearch
  • Media Service: Image/file upload and processing
  • Notification Service: Email notifications for comments, likes

Architecture Guidelines:

  1. Use separate databases for each service where appropriate
  2. Implement proper service-to-service communication
  3. Add health checks and proper dependency management
  4. Include a message queue for async operations
  5. Design proper network isolation

Starter Template:

version: '3.8'

services:
  # Infrastructure services
  postgres-auth:
    # TODO: Auth service database
  
  postgres-content:
    # TODO: Content service database
  
  elasticsearch:
    # TODO: Search engine
  
  redis:
    # TODO: Caching and sessions
  
  rabbitmq:
    # TODO: Message queue
  
  # Business services
  auth-service:
    # TODO: Authentication and authorization
  
  content-service:
    # TODO: Blog post management
  
  comment-service:
    # TODO: Comment system
  
  search-service:
    # TODO: Search functionality
  
  media-service:
    # TODO: File upload and processing
  
  notification-service:
    # TODO: Email notifications
  
  # Frontend and gateway
  api-gateway:
    # TODO: Route requests to services
  
  frontend:
    # TODO: React/Vue blog interface

# TODO: Define appropriate networks and volumes

Exercise 2: Add Monitoring and Observability (10 minutes)

Objective: Extend the application with comprehensive monitoring

Requirements: Add the following monitoring services to your blogging platform:

  1. Prometheus: Metrics collection from all services
  2. Grafana: Dashboards and visualization
  3. Jaeger: Distributed tracing
  4. ELK Stack: Centralized logging (Elasticsearch, Logstash, Kibana)

Implementation Task:

# Add to your docker-compose.yml
services:
  # Monitoring stack
  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"

  grafana:
    image: grafana/grafana:latest
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    volumes:
      - grafana_data:/var/lib/grafana
      - ./monitoring/grafana/dashboards:/var/lib/grafana/dashboards
    ports:
      - "3030:3000"

  jaeger:
    image: jaegertracing/all-in-one:latest
    environment:
      - COLLECTOR_ZIPKIN_HTTP_PORT=9411
    ports:
      - "16686:16686"
      - "14268:14268"

  # Update your services to expose metrics
  auth-service:
    # Add prometheus metrics endpoint
    labels:
      - "prometheus.scrape=true"
      - "prometheus.port=3001"

7. Common Pitfalls and Troubleshooting (10 minutes)

Frequent Issues in Multi-Container Applications

  1. Problem: Services starting in wrong order causing failures Cause: Inadequate dependency management and health checks Solution: Implement proper health checks and use condition-based dependencies
# ❌ Simple depends_on (only waits for container start)
services:
  api:
    depends_on:
      - database

# ✅ Health check based dependency
services:
  api:
    depends_on:
      database:
        condition: service_healthy
  
  database:
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "user"]
      interval: 10s
      timeout: 5s
      retries: 5
  1. Problem: Services can’t communicate across different Compose files Cause: Network isolation between different Compose projects Solution: Use external networks or single Compose file
# Create shared external network
docker network create shared-network

# In each compose file
networks:
  default:
    external: true
    name: shared-network
  1. Problem: Resource exhaustion with many services Cause: No resource limits set for containers Solution: Implement proper resource management
services:
  api:
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: '0.5'
        reservations:
          memory: 256M
          cpus: '0.25'
  1. Problem: Configuration management becomes complex Cause: Hard-coded values and inconsistent environment handling Solution: Use environment files and configuration templates
# Use environment-specific files
docker-compose --env-file .env.production up -d

# Or use profiles for different environments
docker-compose --profile production up -d

Debugging Multi-Container Applications

# Check service health across all compose files
docker-compose -f docker-compose.infrastructure.yml ps
docker-compose -f docker-compose.services.yml ps

# View logs from specific service
docker-compose logs -f user-service

# Check network connectivity between services
docker-compose exec user-service ping product-service

# Monitor resource usage
docker stats $(docker-compose ps -q)

# Check service discovery
docker-compose exec api-gateway nslookup user-service

Performance Optimization

# Scale specific services based on load
docker-compose up -d --scale api-service=5

# Monitor and adjust resource limits
docker-compose exec api-service top
docker-compose exec api-service free -h

# Use build cache effectively
docker-compose build --parallel

8. Best Practices and Professional Tips (10 minutes)

Production-Ready Multi-Container Architecture

Service Design Principles

# Follow single responsibility principle
services:
  user-service:
    # Only handles user management
    build: ./services/user-service
    
  auth-service:
    # Only handles authentication
    build: ./services/auth-service
    
  email-service:
    # Only handles email sending
    build: ./services/email-service

Health Check Strategy

services:
  api:
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s      # Check every 30 seconds
      timeout: 10s       # Timeout after 10 seconds
      retries: 3         # Retry 3 times before marking unhealthy
      start_period: 60s  # Grace period during startup

Configuration Management

# Use external configuration
services:
  api:
    environment:
      DATABASE_URL: ${DATABASE_URL}
      REDIS_URL: ${REDIS_URL}
      LOG_LEVEL: ${LOG_LEVEL:-info}
    env_file:
      - .env
      - .env.${ENVIRONMENT:-development}
    configs:
      - source: api_config
        target: /app/config/app.json

Security Best Practices

services:
  database:
    # Use internal networks for sensitive services
    networks:
      - database-tier
    # No ports exposed to host
    
  api:
    # Run as non-root user
    user: "1001:1001"
    # Read-only root filesystem
    read_only: true
    # Temporary filesystem for writable areas
    tmpfs:
      - /tmp:rw,noexec,nosuid,size=100m

Monitoring and Observability

services:
  api:
    # Structured logging
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
        labels: "service,environment"
    
    # Metrics labels for Prometheus
    labels:
      - "prometheus.scrape=true"
      - "prometheus.port=9090"
      - "prometheus.path=/metrics"

Deployment Strategies

Blue-Green Deployment

# Deploy new version alongside old
docker-compose -p myapp-green up -d

# Test new version
curl http://green-version/health

# Switch traffic (update load balancer)
# Then remove old version
docker-compose -p myapp-blue down

Rolling Updates

services:
  api:
    deploy:
      update_config:
        parallelism: 1      # Update one container at a time
        delay: 10s          # Wait 10s between updates
        failure_action: rollback
        monitor: 60s        # Monitor for 60s after update

Next Steps for Advanced Orchestration

  • Learn Kubernetes for larger-scale orchestration
  • Explore service mesh technologies (Istio, Linkerd)
  • Study infrastructure as code (Terraform, Pulumi)
  • Practice with CI/CD pipelines for containerized applications

9. Summary and Review (5 minutes)

Key Takeaways

  1. Architecture matters at scale - Proper service separation and communication patterns are crucial
  2. Health checks enable reliability - Services should fail fast and recover gracefully
  3. Resource management prevents conflicts - Set appropriate limits and reservations for production

Multi-Container Application Checklist

✅ Service Separation: Each service has a single, well-defined responsibility
✅ Health Monitoring: All services have proper health checks implemented
✅ Network Isolation: Services communicate through defined network boundaries
✅ Resource Management: CPU and memory limits configured appropriately
✅ Configuration Management: Environment-specific configurations externalized
✅ Dependency Management: Services start in correct order with proper dependencies

Quick Review Questions

  1. What’s the difference between depends_on and depends_on with condition: service_healthy?
  2. Why should you use internal networks for database services?
  3. How do you scale a specific service in a multi-service application?

Sample Answers:

  1. Simple depends_on only waits for container start; condition: service_healthy waits for the service to be actually ready
  2. Internal networks prevent external access to sensitive services, improving security posture
  3. Use docker-compose up -d --scale service-name=N where N is the desired number of instances

For Instructors

Teaching Tips

  • Use diagrams to visualize complex service interactions
  • Start with simple architectures and gradually add complexity
  • Emphasize real-world scenarios and trade-offs
  • Show both successful and failed deployments to illustrate common issues

Assessment Ideas

  • Architecture design challenges for different application types
  • Troubleshooting exercises with intentionally broken service dependencies
  • Performance optimization projects with resource constraints
  • Security audit assignments for multi-service applications

Adaptation Notes

  • For beginners: Focus more on basic multi-service patterns and simple dependencies
  • For advanced students: Introduce service mesh concepts and advanced deployment patterns
  • Online delivery: Use screen sharing for complex YAML editing and network debugging
  • Time management: Multi-service applications take longer to start up - plan accordingly

Common Student Challenges

  • Service communication complexity - Help students understand DNS resolution and network boundaries
  • Startup ordering issues - Emphasize the importance of health checks vs simple dependencies
  • Resource management - Teach students to monitor and optimize resource usage
  • Configuration complexity - Show how to organize and manage environment-specific configurations effectively