10. Docker Compose Deployment

Chapter 10 of 18 · 15 min

Docker Compose orchestrates multi-container deployments for development and staging. Production typically uses Kubernetes, but Docker Compose provides a simpler path for smaller deployments. The compose file defines services, networks, volumes, and dependencies.

# docker-compose.yml
version: '3.8'

services:
  postgres:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: aiapp
      POSTGRES_USER: aiapp
      POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
    volumes:
      - postgres_data:/var/lib/postgresql/data
    secrets:
      - postgres_password
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U aiapp"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
      model_server:
        condition: service_healthy
    environment:
      DATABASE_URL: postgresql://aiapp@postgres/aiapp
      REDIS_URL: redis://redis:6379
      MODEL_SERVER_URL: http://model_server:8080
      SECRET_KEY_FILE: /run/secrets/secret_key
    secrets:
      - postgres_password
      - secret_key
    deploy:
      resources:
        limits:
          memory: 2G
        reservations:
          memory: 512M

  model_server:
    build:
      context: ./model-server
      dockerfile: Dockerfile
    environment:
      MODEL_PATH: /models/mistral-7b.q4_k_m.gguf
      CONTEXT_SIZE: 4096
    volumes:
      - model_cache:/models
    deploy:
      resources:
        limits:
          memory: 8G
        reservations:
          devices:
            - capabilities: [gpu]

  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    depends_on:
      - backend

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/certs:/etc/nginx/certs:ro
    depends_on:
      - backend
      - frontend

volumes:
  postgres_data:
  redis_data:
  model_cache:

secrets:
  postgres_password:
    file: ./secrets/postgres_password.txt
  secret_key:
    file: ./secrets/secret_key.txt

Resource limits prevent any service from consuming all host memory. The memory reservation tells Docker to pre-allocate memory for the container, improving performance. The GPU reservation requires the Docker runtime to have GPU access configured.

Secrets management requires secure file permissions. Secrets files should have 0600 permissions:

# Create secrets directory with restricted access
mkdir -p secrets
chmod 700 secrets
echo "your-secure-password" > secrets/postgres_password.txt
chmod 600 secrets/postgres_password.txt

Common Docker Compose failures include port conflicts when services try to bind to ports already in use. Check with netstat -tulpn | grep LISTEN. Volume permission issues when UIDs mismatch between host and container—use named volumes instead of bind mounts to avoid this.

EXERCISE

Create a Docker Compose file that starts the full stack with health checks and resource limits. Verify all services start successfully and communicate.