Multi-Service Architecture

๐Ÿ”ฅ Vibe Prompt

"Extend compose to 4 services: API Gateway, Auth, Worker, and Frontend. Use network isolation."

services:
  gateway:
    image: nginx:alpine
    ports: ["80:80"]
    networks: ["frontend", "backend"]
    volumes: ["./nginx.conf:/etc/nginx/conf.d/default.conf"]

  frontend:
    build: ./frontend
    networks: ["frontend"]

  auth:
    build: ./auth
    networks: ["backend"]
    depends_on: [db, redis]
    environment:
      JWT_SECRET: ${JWT_SECRET}

  worker:
    build: ./worker
    networks: ["backend"]
    depends_on: [redis, db]
    deploy:
      replicas: 3

networks:
  frontend:
  backend:
    internal: true  # No external access

Network Isolation

Internet โ†’ Gateway (nginx)
                โ†“
          Frontend Network
                โ†“
          Backend Network (internal!)
              โ†™    โ†“    โ†˜
            Auth  Worker  DB

Scaling with Compose

services:
  worker:
    deploy:
      replicas: 5
      resources:
        limits:
          cpus: "0.5"
          memory: "256M"

Production Tips

  • Use .env for secrets (never commit)
  • Pin image versions (not :latest)
  • Add restart: always to critical services
  • Use read-only volumes where possible

Chapter Summary

  • Understand core concepts and principles
  • Master implementation methods and techniques
  • Familiar with common issues and solutions
  • Able to apply in real projects

Further Reading

  • Official documentation and API references
  • Open source examples on GitHub
  • Technical books and online courses
  • Community discussions and tech blogs

Implementation Example

Basic Example

# This section provides a complete implementation example

Steps

  1. Setup: Configure development environment
  2. Data: Prepare required data
  3. Implementation: Build core functionality
  4. Testing: Verify correctness
  5. Optimization: Improve performance

Common Errors

| Error Type | Cause | Solution | |------------|-------|----------| | Compilation | Syntax | Check code syntax | | Runtime | Environment | Verify dependencies installed | | Logic | Algorithm | Step-by-step debugging | | Performance | Efficiency | Use profilers |

Code Example

import sys

def main():
    print("Hello, World!")

if __name__ == "__main__":
    main()

References

  • Official documentation
  • API reference
  • Open source examples
  • Community discussions

Service Dependency Management

depends_on with Health Checks

services:
  app:
    depends_on:
      db:
        condition: service_healthy  # Waits for health check to pass
      redis:
        condition: service_started  # Waits for container start only

  db:
    image: postgres:16-alpine
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U myapp"]
      interval: 5s
      timeout: 5s
      retries: 10
      start_period: 30s

  redis:
    image: redis:7-alpine
    # No health check โ€” app only needs redis to start

Sharing Data Between Services

Named Volumes

services:
  api:
    volumes:
      - uploads:/app/uploads

  worker:
    volumes:
      - uploads:/app/uploads  # Same volume shared between services

volumes:
  uploads:

Bind Mount for Development

services:
  app:
    volumes:
      - .:/app           # Mount source code for hot-reload
      - /app/node_modules  # Exclude node_modules (from image)

Networking Between Services

services:
  app:
    networks:
      - frontend
      - backend

  api:
    networks:
      - backend
      - database

  db:
    networks:
      - database

networks:
  frontend:
  backend:
  database:

This isolates services:

  • App โ†’ API (via backend network)
  • API โ†’ DB (via database network)
  • App cannot reach DB directly (security)

Complete Multi-Service Example

version: "3.9"

services:
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./static:/usr/share/nginx/html:ro
    networks:
      - frontend
    depends_on:
      - app

  app:
    build:
      context: ./app
      dockerfile: Dockerfile
    expose:
      - "3000"
    environment:
      - DB_HOST=db
      - DB_USER=${DB_USER}
      - DB_PASSWORD=${DB_PASSWORD}
      - REDIS_HOST=redis
    networks:
      - frontend
      - backend
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started

  worker:
    build:
      context: ./worker
      dockerfile: Dockerfile
    environment:
      - DB_HOST=db
      - DB_USER=${DB_USER}
      - DB_PASSWORD=${DB_PASSWORD}
    networks:
      - backend
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:16-alpine
    volumes:
      - pgdata:/var/lib/postgresql/data
    networks:
      - backend
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
      interval: 5s
      timeout: 5s
      retries: 10
      start_period: 30s

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

volumes:
  pgdata:
  redis_data:

networks:
  frontend:
  backend:

Managing Multiple Compose Files

# Development: override with dev-specific config
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d

# Production: override with production config
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

# Run specific services only
docker compose up -d db redis  # Start only database and cache

Best Practices for Multi-Service

| Practice | Reason | |----------|--------| | Use expose (not ports) for internal services | Internal services should not be accessible from the host | | Isolate networks by security level | Frontend, backend, database โ€” each with different access | | Add health checks to all stateful services | Depends_on waits for real readiness, not just start | | Use named volumes for persistent data | Data survives container restarts | | Use .env file for shared variables | One source of truth for environment config | | Separate compose files per environment | Base + dev + prod overrides | | One service = one responsibility | Don't bundle multiple concerns into one container | | Set restart: always for critical services | Self-healing after crashes | | Configure log rotation | Prevent log files from filling the disk |

Summary

Multi-service Docker Compose setups coordinate web servers, APIs, databases, caches, and workers. Use depends_on with health checks for startup ordering, named volumes for data sharing, and network isolation for security. Multiple compose files handle environment differences.

Key takeaways:

  • depends_on with condition: service_healthy waits for real readiness
  • Named volumes share data between services (e.g., uploads between API and worker)
  • Network isolation improves security (frontend cannot reach database)
  • Use expose instead of ports for internal services
  • Use .env files for shared environment configuration
  • Multiple compose files (base + dev + prod) handle environment differences
  • Health checks prevent the app from connecting to an unready database

What's Next: Dev vs. Prod Configurations

The next chapter covers development vs. production Docker Compose configurations โ€” hot-reload, debug tools, security hardening, and performance optimization.

Member Exclusive Free Tutorial

This chapter is free exclusive content for registered members! Please login or register to unlock immediately.

Login / Register Now