DevOps & Cloud

Docker Compose Advanced: Multi-Service, Networks, Volumes & Profiles

TOKEN

Pelajari Docker Compose tingkat lanjut — arsitektur multi-service, custom networks, named volumes, profiles, environment management, health checks, dan production-ready patterns untuk workflow DevOps profesional

1. Pengenalan Docker Compose Advanced

Docker Compose adalah tool yang memungkinkan Anda mendefinisikan dan menjalankan aplikasi multi-container menggunakan satu file YAML. Pada level dasar, Compose digunakan untuk menghubungkan beberapa service sederhana. Namun pada level advanced, Compose menjadi tool yang sangat powerful untuk mengelola arsitektur microservices yang kompleks.

Pada tutorial Docker Dasar sebelumnya, kita telah mempelajari konsep dasar Docker. Kali ini kita akan membahas fitur-fitur advanced Docker Compose yang sering digunakan dalam environment production.

Mengapa Docker Compose Advanced?

Fitur Compose Dasar Compose Advanced
Services2-3 service sederhana10+ service dengan dependencies kompleks
NetworkDefault networkCustom networks dengan isolasi segment
VolumesAnonymous volumesNamed volumes, bind mounts, driver options
EnvironmentInline variables.env files, variable substitution, profiles
SecurityTidak terkelolaSecrets, configs, read-only filesystem
MonitoringTidak adaHealth checks, restart policies, logging
Diagram: Docker Compose Multi-Service Architecture
┌─────────────────────────────────────────────────────────────────┐
│                    DOCKER COMPOSE PROJECT                        │
│                                                                  │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │                 frontend-network (172.20.0.0/16)           │  │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐               │  │
│  │  │  Nginx   │  │  React   │  │  Vue.js  │               │  │
│  │  │ (Proxy)  │  │  (Web)   │  │  (Admin) │               │  │
│  │  │ :80,:443 │  │  :3000   │  │  :3001   │               │  │
│  │  └────┬─────┘  └──────────┘  └──────────┘               │  │
│  └───────┼───────────────────────────────────────────────────┘  │
│          │                                                       │
│  ┌───────┼───────────────────────────────────────────────────┐  │
│  │       ▼        backend-network (172.21.0.0/16)            │  │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐               │  │
│  │  │  Node.js │  │  Python  │  │  Worker  │               │  │
│  │  │  (API)   │  │  (ML)    │  │ (Celery) │               │  │
│  │  │  :4000   │  │  :5000   │  │          │               │  │
│  │  └────┬─────┘  └────┬─────┘  └────┬─────┘               │  │
│  └───────┼─────────────┼─────────────┼──────────────────────┘  │
│          │             │             │                           │
│  ┌───────┼─────────────┼─────────────┼──────────────────────┐  │
│  │       ▼             ▼             ▼   data-network        │  │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐               │  │
│  │  │ Postgres │  │  Redis   │  │  MinIO   │               │  │
│  │  │  :5432   │  │  :6379   │  │  :9000   │               │  │
│  │  │ [vol:pg] │  │ [vol:rd] │  │ [vol:mi] │               │  │
│  │  └──────────┘  └──────────┘  └──────────┘               │  │
│  └───────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘

Arsitektur di atas menunjukkan sebuah aplikasi production-grade dengan beberapa keunggulan:

2. Arsitektur Multi-Service

Arsitektur multi-service memungkinkan kita memecah aplikasi menjadi beberapa service yang saling terhubung. Setiap service berjalan di container terisolasi, memiliki image, konfigurasi, dan lifecycle-nya sendiri.

Contoh: Full-Stack Application

YAML — docker-compose.yml
# docker-compose.yml — Full-stack multi-service example
# BeebaneLabs - Docker Compose Advanced Tutorial

services:
  # ===========================
  # REVERSE PROXY (Nginx)
  # ===========================
  nginx:
    image: nginx:1.25-alpine
    container_name: app-proxy
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
    depends_on:
      api:
        condition: service_healthy
    networks:
      - frontend
      - backend
    restart: unless-stopped

  # ===========================
  # FRONTEND (React)
  # ===========================
  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
      args:
        - VITE_API_URL=/api
    container_name: app-frontend
    environment:
      - NODE_ENV=production
    volumes:
      - frontend-dist:/app/dist
    networks:
      - frontend
    restart: unless-stopped

  # ===========================
  # BACKEND API (Node.js)
  # ===========================
  api:
    build:
      context: ./backend
      dockerfile: Dockerfile
      target: production
    container_name: app-api
    environment:
      - DATABASE_URL=postgresql://user:${DB_PASSWORD}@postgres:5432/appdb
      - REDIS_URL=redis://redis:6379
      - JWT_SECRET=${JWT_SECRET}
      - NODE_ENV=production
    volumes:
      - ./backend/uploads:/app/uploads
      - api-logs:/app/logs
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:4000/health"]
      interval: 15s
      timeout: 5s
      retries: 3
      start_period: 30s
    networks:
      - backend
      - data
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 128M

  # ===========================
  # WORKER (Celery/Python)
  # ===========================
  worker:
    build:
      context: ./worker
      dockerfile: Dockerfile
    container_name: app-worker
    environment:
      - CELERY_BROKER_URL=redis://redis:6379/0
      - DATABASE_URL=postgresql://user:${DB_PASSWORD}@postgres:5432/appdb
    depends_on:
      redis:
        condition: service_healthy
      postgres:
        condition: service_healthy
    networks:
      - backend
      - data
    restart: unless-stopped
    deploy:
      replicas: 2

  # ===========================
  # DATABASE (PostgreSQL)
  # ===========================
  postgres:
    image: postgres:16-alpine
    container_name: app-db
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: appdb
    volumes:
      - postgres-data:/var/lib/postgresql/data
      - ./database/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user -d appdb"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    networks:
      - data
    restart: unless-stopped

  # ===========================
  # CACHE (Redis)
  # ===========================
  redis:
    image: redis:7-alpine
    container_name: app-redis
    command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
    volumes:
      - redis-data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3
    networks:
      - data
    restart: unless-stopped

# ===========================
# NETWORKS
# ===========================
networks:
  frontend:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16
  backend:
    driver: bridge
    ipam:
      config:
        - subnet: 172.21.0.0/16
  data:
    driver: bridge
    internal: true
    ipam:
      config:
        - subnet: 172.22.0.0/16

# ===========================
# VOLUMES
# ===========================
volumes:
  postgres-data:
    driver: local
  redis-data:
    driver: local
  frontend-dist:
    driver: local
  api-logs:
    driver: local

Penjelasan Komponen Kunci

Komponen Fungsi Tips
depends_onMenentukan urutan startup serviceGunakan condition: service_healthy agar benar-benar menunggu
healthcheckMemeriksa kesehatan serviceSelalu tambahkan untuk service kritis seperti database
deploy.resourcesBatasan CPU dan memoryPenting untuk mencegah satu service menghabiskan semua resource
restartKebijakan restart otomatisunless-stopped cocok untuk production
build.targetMulti-stage build targetGunakan target berbeda untuk dev dan production

Perintah Dasar Multi-Service

Bash
# Jalankan semua service di background
docker compose up -d

# Jalankan hanya service tertentu
docker compose up -d api postgres redis

# Lihat status semua service
docker compose ps

# Lihat log gabungan semua service
docker compose logs -f

# Lihat log service tertentu
docker compose logs -f api

# Restart satu service
docker compose restart api

# Rebuild dan restart setelah perubahan Dockerfile
docker compose up -d --build api

# Hentikan semua service (pertahankan data)
docker compose down

# Hentikan dan hapus volumes (HATI-HATI: data hilang!)
docker compose down -v

# Scale worker menjadi 3 instance
docker compose up -d --scale worker=3
💡 Tips

Gunakan docker compose up -d --build setiap kali Anda mengubah Dockerfile. Docker Compose tidak otomatis rebuild image jika Dockerfile berubah — Anda harus trigger secara manual.

3. Custom Networks

Secara default, Docker Compose membuat satu network bridge untuk semua service dalam satu file docker-compose.yml. Namun dalam production, Anda perlu memisahkan network berdasarkan layer arsitektur untuk keamanan dan isolasi yang lebih baik.

Jenis Network Driver

Driver Fungsi Penggunaan
bridgeNetwork default, container bisa saling berkomunikasiService yang perlu berinteraksi
hostContainer menggunakan host network langsungPerformance-critical, hindari NAT overhead
overlayNetwork lintas host (Swarm mode)Multi-node deployment
noneTidak ada networkContainer yang tidak perlu network
macvlanContainer mendapat IP langsung dari LANLegacy app yang butuh IP sendiri

Network Segmentation Strategy

YAML — Custom Networks Configuration
# Network segmentation strategy
networks:
  # Frontend network — exposed to outside world
  # Hanya Nginx dan frontend app
  frontend:
    driver: bridge
    driver_opts:
      com.docker.network.bridge.name: br-frontend
    ipam:
      driver: default
      config:
        - subnet: 172.20.0.0/16
          gateway: 172.20.0.1
    labels:
      - "app.network=frontend"
      - "app.environment=production"

  # Backend network — internal API communication
  # API, Worker, dan proxy
  backend:
    driver: bridge
    internal: false   # bisa akses internet untuk API calls
    ipam:
      config:
        - subnet: 172.21.0.0/16
    labels:
      - "app.network=backend"

  # Data network — TIDAK bisa akses internet
  # Database, cache, dan storage saja
  data:
    driver: bridge
    internal: true    # TIDAK bisa akses internet!
    ipam:
      config:
        - subnet: 172.22.0.0/16
    labels:
      - "app.network=data"

Network Aliases

Network aliases memungkinkan service diakses dengan nama yang mudah diingat, bukan nama container:

YAML — Network Aliases
services:
  postgres-primary:
    image: postgres:16
    networks:
      data:
        aliases:
          - db-primary
          - database
          - postgres

  postgres-replica:
    image: postgres:16
    networks:
      data:
        aliases:
          - db-replica

  api:
    environment:
      # Service lain bisa pakai alias untuk akses
      DATABASE_HOST: db-primary
      REPLICA_HOST: db-replica
    networks:
      - backend
      - data    # API perlu akses ke data network

Fixed IP Addresses

YAML — Static IP
services:
  nginx:
    networks:
      frontend:
        ipv4_address: 172.20.0.10
      backend:
        ipv4_address: 172.21.0.10

  postgres:
    networks:
      data:
        ipv4_address: 172.22.0.10
⚠️ Peringatan

Hindari menggunakan fixed IP addresses kecuali benar-benar diperlukan (misalnya untuk whitelist di firewall). Gunakan network aliases sebagai gantinya karena lebih fleksibel dan tidak akan conflict saat scaling.

4. Named Volumes & Bind Mounts

Docker menyediakan beberapa cara untuk mengelola data persisten. Pemahaman yang benar tentang volume management sangat penting untuk production deployment.

Perbandingan Tipe Volume

Tipe Sintaks Kelebihan Kekurangan
Named Volume postgres-data:/var/lib/pg Dikelola Docker, portable, backup mudah Tidak bisa edit langsung dari host
Bind Mount ./data:/var/lib/pg Bisa edit dari host, cocok untuk development Path absolut, portability terbatas
Tmpfs type: tmpfs, target: /tmp Di RAM saja, sangat cepat Hilang saat container restart

Advanced Volume Configuration

YAML — Volume Configurations
services:
  postgres:
    image: postgres:16
    volumes:
      # Named volume dengan mount options
      - type: volume
        source: postgres-data
        target: /var/lib/postgresql/data
        volume:
          nocopy: true

      # Read-only bind mount untuk config
      - type: bind
        source: ./postgres/postgresql.conf
        target: /etc/postgresql/postgresql.conf
        read_only: true

      # Init scripts (read-only)
      - type: bind
        source: ./database/init
        target: /docker-entrypoint-initdb.d
        read_only: true

  api:
    image: node:20-alpine
    volumes:
      # Bind mount untuk development (hot reload)
      - type: bind
        source: ./backend/src
        target: /app/src

      # Named volume untuk uploads
      - type: volume
        source: api-uploads
        target: /app/uploads

      # tmpfs untuk temporary files (RAM-based)
      - type: tmpfs
        target: /app/tmp
        tmpfs:
          size: 100000000    # 100MB limit
          mode: 1777

  redis:
    image: redis:7-alpine
    volumes:
      - type: volume
        source: redis-data
        target: /data
    # Read-only root filesystem
    read_only: true
    tmpfs:
      - /tmp

# Volume definitions dengan driver options
volumes:
  postgres-data:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: /data/postgres
    labels:
      com.example.description: "PostgreSQL production data"
      com.example.backup: "daily"

  redis-data:
    driver: local
    labels:
      com.example.description: "Redis AOF persistence"

  api-uploads:
    driver: local

Backup & Restore Volumes

Bash
# Backup volume ke file tar
docker run --rm \
  -v myapp_postgres-data:/data:ro \
  -v $(pwd)/backups:/backup \
  alpine tar czf /backup/postgres-data-$(date +%Y%m%d).tar.gz -C /data .

# Restore volume dari file tar
docker run --rm \
  -v myapp_postgres-data:/data \
  -v $(pwd)/backups:/backup \
  alpine tar xzf /backup/postgres-data-20260626.tar.gz -C /data

# List semua volumes
docker volume ls

# Inspect volume
docker volume inspect myapp_postgres-data

# Backup PostgreSQL secara konsisten (lebih aman)
docker compose exec postgres pg_dump -U user appdb > backup.sql

# Restore PostgreSQL
docker compose exec -T postgres psql -U user appdb < backup.sql
💡 Tips

Untuk backup database production, jangan backup volume secara langsung karena data mungkin sedang ditulis. Gunakan tool backup database seperti pg_dump atau mysqldump untuk konsistensi data.

5. Docker Compose Profiles

Profiles memungkinkan Anda mengelompokkan service berdasarkan kebutuhan. Misalnya, service monitoring dan debugging hanya diaktifkan saat development, sementara service production hanya dijalankan di server.

Konsep Profiles

Service tanpa profiles akan selalu dijalankan. Service dengan profiles hanya dijalankan ketika profile-nya diaktifkan secara eksplisit.

YAML — Docker Compose Profiles
services:
  # ===========================
  # SERVICE UTAMA (selalu jalan)
  # ===========================
  api:
    build: ./backend
    profiles: []    # Tidak ada profile = selalu jalan
    depends_on:
      - postgres
      - redis

  postgres:
    image: postgres:16
    volumes:
      - postgres-data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine

  nginx:
    image: nginx:1.25-alpine
    ports:
      - "80:80"

  # ===========================
  # DEVELOPMENT ONLY
  # ===========================
  frontend-dev:
    build:
      context: ./frontend
      dockerfile: Dockerfile.dev
    profiles:
      - development
    ports:
      - "3000:3000"
    volumes:
      - ./frontend/src:/app/src    # Hot reload
    environment:
      - NODE_ENV=development

  mailhog:
    image: mailhog/mailhog
    profiles:
      - development
    ports:
      - "1025:1025"   # SMTP
      - "8025:8025"   # Web UI

  # ===========================
  # DEBUGGING / ADMIN TOOLS
  # ===========================
  pgadmin:
    image: dpage/pgadmin4
    profiles:
      - debug
      - admin
    environment:
      PGADMIN_DEFAULT_EMAIL: admin@beebanelabs.com
      PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_PASSWORD}
    ports:
      - "5050:80"

  redis-commander:
    image: rediscommander/redis-commander
    profiles:
      - debug
      - admin
    environment:
      REDIS_HOSTS: local:redis:6379
    ports:
      - "8081:8081"

  portainer:
    image: portainer/portainer-ce
    profiles:
      - admin
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - portainer-data:/data
    ports:
      - "9000:9000"

  # ===========================
  # MONITORING
  # ===========================
  prometheus:
    image: prom/prometheus
    profiles:
      - monitoring
    volumes:
      - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus-data:/prometheus
    ports:
      - "9090:9090"

  grafana:
    image: grafana/grafana
    profiles:
      - monitoring
    volumes:
      - grafana-data:/var/lib/grafana
      - ./monitoring/dashboards:/etc/grafana/provisioning/dashboards:ro
    ports:
      - "3000:3000"

  # ===========================
  # TESTING
  # ===========================
  test-runner:
    build:
      context: .
      dockerfile: Dockerfile.test
    profiles:
      - test
    environment:
      - DATABASE_URL=postgresql://test:test@postgres-test:5432/testdb
    depends_on:
      postgres-test:
        condition: service_healthy

  postgres-test:
    image: postgres:16
    profiles:
      - test
    environment:
      POSTGRES_USER: test
      POSTGRES_PASSWORD: test
      POSTGRES_DB: testdb
    tmpfs:
      - /var/lib/postgresql/data   # RAM-based untuk test cepat
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U test"]
      interval: 5s
      timeout: 3s
      retries: 3

volumes:
  postgres-data:
  portainer-data:
  prometheus-data:
  grafana-data:

Cara Menggunakan Profiles

Bash
# Jalankan HANYA service default (tanpa profile)
docker compose up -d

# Aktifkan profile development
docker compose --profile development up -d

# Aktifkan multiple profiles
docker compose --profile debug --profile monitoring up -d

# Aktifkan SEMUA profiles
docker compose --profile development --profile debug --profile admin --profile monitoring --profile test up -d

# Atau gunakan environment variable
COMPOSE_PROFILES=development,debug docker compose up -d

# Jalankan test dengan profile test
docker compose --profile test run --rm test-runner npm test

# Lihat service berdasarkan profile
docker compose --profile admin ps
📋 Best Practice Profiles

Gunakan profile naming yang konsisten: development, staging, production, debug, admin, monitoring, test. Simpan profile combinations di script shell agar tim tidak perlu menghafal flag yang panjang.

6. Environment Variables & .env Files

Pengelolaan environment variables yang baik adalah kunci keamanan dan portabilitas Docker Compose. Jangan pernah hardcode secrets di dalam docker-compose.yml.

Strategi Environment Variables

Bash — .env file
# .env — untuk Docker Compose variable substitution
# JANGAN commit file ini ke git!

# Database
DB_PASSWORD=super-secret-password-here
DB_NAME=appdb
DB_USER=appuser

# Application
JWT_SECRET=my-jwt-secret-key
API_KEY=sk-1234567890abcdef

# Docker Compose project name
COMPOSE_PROJECT_NAME=myapp

# Image tags (bisa di-override per environment)
API_IMAGE_TAG=latest
FRONTEND_IMAGE_TAG=latest
Bash — .env.example (aman untuk commit ke git)
# .env.example — Template environment variables
# Copy ke .env dan isi dengan nilai yang sesuai

# Database
DB_PASSWORD=CHANGE_ME
DB_NAME=appdb
DB_USER=appuser

# Application
JWT_SECRET=CHANGE_ME
API_KEY=CHANGE_ME

# Docker
COMPOSE_PROJECT_NAME=myapp
API_IMAGE_TAG=latest
FRONTEND_IMAGE_TAG=latest

Variable Substitution di docker-compose.yml

YAML — Variable Substitution
services:
  api:
    image: myapp/api:${API_IMAGE_TAG:-latest}
    environment:
      # Dari .env file
      - DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@postgres:5432/${DB_NAME}
      - JWT_SECRET=${JWT_SECRET}

      # Default values (jika tidak ada di .env)
      - NODE_ENV=${NODE_ENV:-production}
      - LOG_LEVEL=${LOG_LEVEL:-info}
      - PORT=${API_PORT:-4000}

      # Literal value (tidak dari .env)
      - SERVICE_NAME=api

    # Atau gunakan env_file untuk bulk import
    env_file:
      - path: ./config/common.env
        required: true
      - path: ./config/secrets.env
        required: false    # Tidak error jika file tidak ada

Docker Secrets (Swarm Mode)

YAML — Docker Secrets
# Untuk Docker Swarm, gunakan secrets
# Secrets di-mount sebagai file di /run/secrets/

services:
  api:
    image: myapp/api
    secrets:
      - db_password
      - jwt_secret
      - api_key
    environment:
      # Baca secret dari file
      - DB_PASSWORD_FILE=/run/secrets/db_password
      - JWT_SECRET_FILE=/run/secrets/jwt_secret

  postgres:
    image: postgres:16
    secrets:
      - db_password
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt
  jwt_secret:
    file: ./secrets/jwt_secret.txt
  api_key:
    external: true    # Sudah ada di Docker Swarm
⚠️ Peringatan Keamanan

Selalu tambahkan .env dan secrets/ ke .gitignore. Gunakan .env.example sebagai template. Untuk production, pertimbangkan menggunakan Docker Secrets atau external secrets manager seperti HashiCorp Vault atau AWS Secrets Manager.

7. Health Checks & Dependencies

Health checks memastikan service benar-benar siap menerima request, bukan hanya sekadar container yang sudah running. Ini sangat penting untuk service dependencies seperti database yang perlu waktu untuk inisialisasi.

Health Check Patterns

YAML — Health Checks
services:
  # PostgreSQL — gunakan pg_isready
  postgres:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  # MySQL — gunakan mysqladmin
  mysql:
    image: mysql:8
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${DB_PASSWORD}"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 40s

  # Redis — gunakan redis-cli ping
  redis:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3

  # MongoDB — gunakan mongosh
  mongo:
    image: mongo:7
    healthcheck:
      test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
      interval: 15s
      timeout: 5s
      retries: 3
      start_period: 30s

  # HTTP API — gunakan curl
  api:
    build: ./backend
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:4000/health"]
      interval: 15s
      timeout: 5s
      retries: 3
      start_period: 30s

  # Node.js dengan custom health script
  node-app:
    build: ./node-app
    healthcheck:
      test: ["CMD", "node", "healthcheck.js"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 20s

  # Elasticsearch
  elasticsearch:
    image: elasticsearch:8.10.0
    healthcheck:
      test: ["CMD-SHELL", "curl -s http://localhost:9200/_cluster/health | grep -q '\"status\":\"green\"\\|\"status\":\"yellow\"'"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 60s

Conditional Dependencies

YAML — Service Dependencies
services:
  api:
    build: ./backend
    depends_on:
      # Tunggu sampai postgres benar-benar sehat
      postgres:
        condition: service_healthy
      # Tunggu sampai redis benar-benar sehat
      redis:
        condition: service_healthy
      # Tunggu sampai migrasi selesai (service_started + exit)
      migrate:
        condition: service_completed_successfully

  # Database migration (berjalan sekali lalu exit)
  migrate:
    build: ./backend
    command: ["npm", "run", "migrate"]
    environment:
      - DATABASE_URL=postgresql://user:${DB_PASSWORD}@postgres:5432/appdb
    depends_on:
      postgres:
        condition: service_healthy
    profiles:
      - migration

Monitoring Health Status

Bash
# Cek status health semua container
docker compose ps
# NAME          STATUS                    PORTS
# app-api       Up 5 min (healthy)        0.0.0.0:4000->4000/tcp
# app-db        Up 5 min (healthy)        5432/tcp
# app-redis     Up 5 min (healthy)        6379/tcp

# Detail health check dari satu container
docker inspect --format='{{json .State.Health}}' app-api | jq

# Log health check history
docker inspect --format='{{range .State.Health.Log}}{{.Output}}{{end}}' app-api

8. Scaling Services

Docker Compose memungkinkan Anda menjalankan multiple instance dari satu service untuk menangani beban yang lebih tinggi.

YAML — Scaling-Ready Service
services:
  worker:
    build: ./worker
    # Jangan hardcode container_name jika ingin scale!
    # container_name: app-worker   ← JANGAN ini
    environment:
      - REDIS_URL=redis://redis:6379
      - WORKER_CONCURRENCY=4
    deploy:
      replicas: 3
      resources:
        limits:
          cpus: '0.50'
          memory: 256M
        reservations:
          cpus: '0.25'
          memory: 128M
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
        window: 120s
Bash
# Scale worker menjadi 5 instance
docker compose up -d --scale worker=5

# Scale API menjadi 3 instance
docker compose up -d --scale api=3

# Perhatikan: port binding akan conflict jika di-scale!
# Solusi: gunakan range port atau Nginx load balancer
# ports:
#   - "4000-4010:4000"    ← assign port otomatis
💡 Tips

Untuk production scaling, pertimbangkan menggunakan Docker Swarm (docker stack deploy) atau Kubernetes. Docker Compose scaling cocok untuk development dan staging, tetapi tidak memiliki fitur load balancing built-in.

9. Production-Ready Patterns

Multi-File Compose Override

Gunakan file terpisah untuk konfigurasi berbeda:

Bash
# Struktur file:
# docker-compose.yml          ← Base config
# docker-compose.override.yml ← Auto-loaded, untuk development
# docker-compose.prod.yml     ← Production overrides
# docker-compose.test.yml     ← Testing overrides

# Development (auto-load base + override)
docker compose up -d

# Production (base + prod override)
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

# Testing (base + test override)
docker compose -f docker-compose.yml -f docker-compose.test.yml up -d
YAML — docker-compose.prod.yml
# docker-compose.prod.yml — Production overrides
services:
  api:
    restart: always
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
    environment:
      - NODE_ENV=production
      - LOG_LEVEL=warn

  nginx:
    restart: always
    logging:
      driver: json-file
      options:
        max-size: "5m"
        max-file: "5"

  postgres:
    restart: always
    command: >
      postgres
        -c shared_buffers=256MB
        -c effective_cache_size=768MB
        -c maintenance_work_mem=64MB
        -c work_mem=4MB
        -c max_connections=200
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "5"

Read-Only Filesystem & Security Hardening

YAML — Security Hardening
services:
  api:
    build: ./backend
    # Read-only root filesystem
    read_only: true
    # tmpfs untuk directory yang perlu write
    tmpfs:
      - /tmp
      - /app/tmp
    # Non-root user
    user: "1000:1000"
    # Drop capabilities
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE    # Hanya jika perlu bind port < 1024
    # Security options
    security_opt:
      - no-new-privileges:true
    # Batasi PID (mencegah fork bomb)
    pids_limit: 100

  postgres:
    image: postgres:16
    read_only: true
    tmpfs:
      - /tmp
      - /run/postgresql
    cap_drop:
      - ALL
    cap_add:
      - CHOWN
      - DAC_OVERRIDE
      - FOWNER
      - SETGID
      - SETUID
    security_opt:
      - no-new-privileges:true

10. Troubleshooting & Debugging

Perintah Debugging yang Sering Digunakan

Bash
# ===========================
# DIAGNOSTICS
# ===========================

# Cek status semua service
docker compose ps -a

# Lihat log dengan timestamp
docker compose logs -f -t api

# Lihat log 100 baris terakhir
docker compose logs --tail 100 api

# Resource usage (CPU, memory, network)
docker compose top
docker stats --no-stream

# Inspect service configuration (resolved)
docker compose config

# Validasi compose file tanpa menjalankan
docker compose config --quiet

# ===========================
# DEBUGGING CONTAINER
# ===========================

# Masuk ke container yang sedang berjalan
docker compose exec api /bin/sh

# Jalankan command satu kali
docker compose exec api env

# Masuk ke container baru (tidak ke yang running)
docker compose run --rm api /bin/sh

# ===========================
# NETWORK DEBUGGING
# ===========================

# List networks
docker network ls

# Inspect network
docker network inspect myapp_frontend

# Tes koneksi antar container
docker compose exec api ping postgres
docker compose exec api nc -zv postgres 5432

# ===========================
# CLEANUP
# ===========================

# Hapus stopped containers
docker compose rm -f

# Hapus images yang tidak digunakan
docker image prune -f

# Hapus semua unused resources
docker system prune -f --volumes

Common Issues & Solutions

Masalah Penyebab Solusi
Port already allocated Port sudah digunakan oleh proses lain lsof -i :PORT lalu kill proses atau ganti port
Connection refused Service belum siap atau network salah Tambahkan health check dan depends_on condition
Volume permission denied User ID di container berbeda dengan host Set user: "UID:GID" atau ubah permission di Dockerfile
Out of memory Container melebihi memory limit Naikkan deploy.resources.limits.memory
Environment variable tidak terbaca Nama file .env salah atau path berbeda Gunakan docker compose config untuk verifikasi

11. Quiz Pemahaman

Uji pemahaman Anda tentang Docker Compose Advanced:

1. Apa fungsi dari condition: service_healthy di depends_on?

2. Apa fungsi internal: true pada network definition?

3. Mengapa container_name TIDAK disarankan saat ingin scaling?

4. Apa perbedaan named volume dan bind mount?

5. Kapan service dengan profiles dijalankan?

🔍 Zoom
100%
🎨 Tema