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:
- Advanced Architecture Patterns - Microservices, service mesh, and distributed system design
- Sophisticated Service Dependencies - Complex startup ordering and health-based dependencies
- Advanced Networking - Multi-tier networks, service isolation, and communication patterns
- Scaling and Load Balancing - Horizontal scaling and load distribution strategies
- 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:
- Use separate databases for each service where appropriate
- Implement proper service-to-service communication
- Add health checks and proper dependency management
- Include a message queue for async operations
- 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:
- Prometheus: Metrics collection from all services
- Grafana: Dashboards and visualization
- Jaeger: Distributed tracing
- 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
- 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
- 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
- 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'
- 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
- Architecture matters at scale - Proper service separation and communication patterns are crucial
- Health checks enable reliability - Services should fail fast and recover gracefully
- 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
- What’s the difference between
depends_onanddepends_onwithcondition: service_healthy? - Why should you use internal networks for database services?
- How do you scale a specific service in a multi-service application?
Sample Answers:
- Simple
depends_ononly waits for container start;condition: service_healthywaits for the service to be actually ready - Internal networks prevent external access to sensitive services, improving security posture
- Use
docker-compose up -d --scale service-name=Nwhere 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