docker-compose.yml #

File docker-compose.yml adalah dokumen yang mendefinisikan stack aplikasi multi-container. Formatnya YAML, dengan struktur hierarkis yang membagi konfigurasi menjadi beberapa bagian logis: service (wajib), volume, network, config, dan secret.

Artikel ini membahas anatomi file docker-compose.yml secara menyeluruh — apa saja top-level key, bagaimana mereka saling terkait, dan konvensi yang sebaiknya diikuti agar file tetap mudah dibaca dan di-maintain.

Top-Level Keys #

File docker-compose.yml paling lengkap punya beberapa top-level keys. Beberapa wajib, beberapa opsional.

# docker-compose.yml
version: "3.8"  # opsional di Compose V2

services:        # WAJIB
  web:
    ...
  api:
    ...

volumes:         # opsional
  db-data:

networks:        # opsional
  backend:

configs:         # opsional, untuk file konfigurasi
  nginx.conf:

secrets:         # opsional, untuk data sensitif
  db-password:

services — Wajib #

services adalah satu-satunya top-level key yang wajib. Ia berisi daftar service yang akan dijalankan. Setiap service merepresentasikan satu container (atau beberapa container jika di-scale).

volumes — Untuk Data Persisten #

volumes mendeklarasikan volume yang digunakan service. Jika service punya field volumes: yang merujuk ke volume yang belum dideklarasikan, Compose akan otomatis membuatkannya. Tapi best practice adalah deklarasi eksplisit di top-level.

networks — Untuk Isolasi #

networks mendeklarasikan network yang digunakan service. Secara default, Compose membuat satu network untuk semua service di file. Untuk konfigurasi yang lebih advanced, kamu bisa mendefinisikan beberapa network.

configs — Untuk File Konfigurasi #

configs memungkinkan kamu mounting file atau direktori ke dalam container. Bedanya dengan bind mount: config didefinisikan di Compose file dan di-inject saat runtime.

secrets — Untuk Data Sensitif #

secrets mirip dengan configs tapi untuk data sensitif seperti password, API key, dan sertifikat. Secrets disimpan di Docker’s secret storage (terenkripsi di production) dan di-mount ke /run/secrets/ di container.

Versi schema: Sejak Docker Compose V2, field version: di top-level menjadi opsional dan sudah di-deprecate. Jika kamu menggunakan Compose V2, hilangkan version: dari file. Ini membuat file lebih future-proof dan menghindari error “version tidak dikenal”.

Anatomi Service #

Setiap service di bawah services: punya banyak field. Tidak semua wajib, tapi masing-masing punya peran spesifik.

Field Dasar #

services:
  web:
    image: nginx:alpine          # image yang dipakai
    # ATAU
    build: ./web                  # path ke Dockerfile
    
    container_name: my-nginx      # opsional, nama container
    hostname: web-server          # opsional, hostname di dalam container
    
    ports:
      - "8080:80"                 # port mapping
    
    environment:
      - NODE_ENV=production       # env var
      - DEBUG=false
    
    volumes:
      - ./html:/usr/share/nginx/html:ro
      - db-data:/var/lib/data
    
    networks:
      - frontend
      - backend
    
    depends_on:
      - api
      - db
    
    restart: unless-stopped
    
    user: "1000:1000"
    working_dir: /app
    command: ["nginx", "-g", "daemon off;"]
    entrypoint: ["/docker-entrypoint.sh"]

Image vs Build #

Service bisa di-spesifikasikan dengan image: (langsung pakai image dari registry) atau build: (build dari Dockerfile lokal).

services:
  # Pakai image dari Docker Hub
  redis:
    image: redis:7-alpine

  # Build dari Dockerfile lokal
  api:
    build:
      context: ./api
      dockerfile: Dockerfile.dev
      args:
        - NODE_ENV=development

  # Build DAN kasih tag
  api-prod:
    build: ./api
    image: myregistry.com/api:v1.2.3

Saat menggunakan build:, image yang dihasilkan disimpan di cache Docker lokal. Untuk multi-stage, Compose otomatis menggunakan stage yang ditentukan di Dockerfile (biasanya stage terakhir).

Ports #

Field ports memetakan port host ke port container.

ports:
  - "8080:80"        # host:container
  - "127.0.0.1:8080:80"  # host_ip:host_port:container_port
  - "8443:443"        # HTTPS
  - "3000"            # random host port, expose 3000

Format singkat "HOST:CONTAINER" adalah yang paling umum. Untuk kasus khusus (binding ke IP tertentu), gunakan format panjang.

Untuk production, hindari - "80:80" atau port rendah lainnya di host. Port di bawah 1024 butuh root untuk binding. Gunakan port tinggi di host (mis. "8080:80") lalu reverse proxy.

Environment Variables #

services:
  api:
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgres://user:pass@db:5432/myapp
      - DEBUG=false
    
    # Atau pakai map syntax
    environment:
      NODE_ENV: production
      DATABASE_URL: postgres://user:pass@db:5432/myapp
    
    # Pakai file .env
    env_file:
      - .env
      - .env.production

env_file membaca file .env dan inject variabelnya ke service. File .env harus di-.gitignore jika berisi secret.

Volumes #

Field volumes punya beberapa format.

services:
  web:
    volumes:
      # Named volume
      - db-data:/var/lib/data
      
      # Bind mount
      - ./html:/usr/share/nginx/html
      
      # Bind mount dengan opsi
      - ./html:/usr/share/nginx/html:ro
      
      # Anonymous volume
      - /var/lib/data
      
      # tmpfs
      - type: tmpfs
        source: /tmp
        target: /tmp
        tmpfs:
          size: 100m

Format source:target:options adalah yang paling eksplisit dan disarankan. Format singkat - source:target juga valid dan sering dipakai.

Networks #

Service otomatis join ke network default yang dibuat Compose. Untuk custom network:

services:
  web:
    networks:
      - frontend
      - backend

networks:
  frontend:
  backend:
    driver: bridge
    driver_opts:
      com.docker.network.bridge.name: br-backend

Service web sekarang punya dua network interface. Ia bisa komunikasi dengan service lain di frontend maupun backend. Service di backend tidak bisa di-reach dari service di frontend kecuali di-expose juga.

Depends On #

depends_on menentukan urutan startup.

services:
  web:
    depends_on:
      - api
  
  api:
    depends_on:
      - db
      - redis

Dalam contoh di atas, db dan redis start duluan, lalu api, lalu web. Tapi — penting — depends_on hanya menunggu container start, bukan service siap menerima request.

Untuk benar-benar menunggu service siap (mis. database sudah menerima connection), gunakan healthcheck + depends_on: { ... condition: service_healthy }.

services:
  api:
    depends_on:
      db:
        condition: service_healthy
  
  db:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5

api sekarang tidak start sampai healthcheck db mengembalikan status healthy. Ini lebih reliable untuk dependency yang butuh waktu inisialisasi.

Restart Policy #

services:
  web:
    restart: "no"                # default
    restart: always              # selalu restart
    restart: on-failure          # restart hanya kalau exit code non-zero
    restart: unless-stopped      # restart kecuali di-stop manual

Untuk production, unless-stopped atau always adalah pilihan yang aman. Untuk development, no (default) biasanya cukup karena kamu sendiri yang start/stop.

User, Working Directory, Command #

services:
  app:
    user: "1000:1000"          # jalankan sebagai non-root
    working_dir: /app
    command: ["python", "app.py"]
    entrypoint: ["/entrypoint.sh"]

command override CMD di Dockerfile. entrypoint override ENTRYPOINT. user membuat container jalan sebagai user tertentu (bukan root) untuk keamanan.

Resource Limits #

services:
  api:
    deploy:
      resources:
        limits:
          cpus: "0.5"
          memory: 512M
        reservations:
          cpus: "0.25"
          memory: 256M

deploy adalah field yang biasa dipakai di Docker Swarm. Untuk Compose di single host, kamu bisa pakai deploy.resources (didukung di Compose V2) atau gunakan mem_limit dan cpus di top-level (legacy).


Contoh File Lengkap #

File docker-compose.yml untuk stack e-commerce sederhana:

services:
  web:
    build: ./web
    ports:
      - "3000:3000"
    environment:
      - API_URL=http://api:8080
    depends_on:
      api:
        condition: service_healthy
    networks:
      - frontend
    restart: unless-stopped

  api:
    build: ./api
    environment:
      - DATABASE_URL=postgres://user:pass@db:5432/shop
      - REDIS_URL=redis://cache:6379
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_healthy
    networks:
      - frontend
      - backend
    restart: unless-stopped

  db:
    image: postgres:16-alpine
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=pass
      - POSTGRES_DB=shop
    volumes:
      - db-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user -d shop"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - backend
    restart: unless-stopped

  cache:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3
    networks:
      - backend
    restart: unless-stopped

  proxy:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./certs:/etc/nginx/certs:ro
    depends_on:
      - web
    networks:
      - frontend
    restart: unless-stopped

volumes:
  db-data:

networks:
  frontend:
  backend:

Stack ini punya 5 service: web (frontend), api (backend), database, cache, dan reverse proxy. Network dipisah jadi frontend dan backend — web dan proxy di frontend, database dan cache di backend yang tidak di-expose ke host.

Konvensi Penulisan #

Indentasi dan Formatting #

Gunakan indentasi 2 spasi (standar YAML). Jangan pakai tab. Compose parser tidak menerima tab.

# BAGUS
services:
  web:
    image: nginx
    ports:
      - "8080:80"

# BURUK
services:
  web:
      image: nginx
      ports:
        - "8080:80"

Panjang Baris #

Usahakan tidak lebih dari 120 karakter per baris agar mudah dibaca di code review.

Komentar #

Gunakan komentar untuk menjelaskan WHY, bukan WHAT. Kode menjelaskan what; komentar menjelaskan why.

services:
  api:
    # PostgreSQL dengan tuning untuk high-concurrency workload
    image: postgres:16-alpine
    command: postgres -c max_connections=200

Urutan Service #

Tidak ada aturan baku, tapi beberapa konvensi yang baik:

  • Service yang paling penting duluan (mis. web, api)
  • Database/cache di akhir
  • Helper service (proxy, monitoring) sesuai peran
  • Group service yang terkait bersama

Konsistensi Penamaan #

  • Service: kebab-case (user-service, auth-service)
  • Volume: kebab-case (user-uploads, app-cache)
  • Network: deskriptif (frontend, backend, monitoring)

Multiple Compose Files #

Untuk environment berbeda, kamu bisa override dengan file tambahan.

# Default + development override
docker compose -f docker-compose.yml -f docker-compose.dev.yml up

# Default + production override
docker compose -f docker-compose.yml -f docker-compose.prod.yml up

Override file menimpa field di default. Ini memungkinkan konfigurasi base yang sama dipakai untuk banyak environment.


Validasi #

Sebelum menjalankan, validasi file Compose-mu.

# Tampilkan parsed config
docker compose config

# Tampilkan dan highlight issues
docker compose config --quiet

docker compose config membaca semua file (default + override + env file), merge, parse, lalu tampilkan hasilnya. Jika ada error syntax atau referensi yang tidak valid, ia akan muncul di sini.

--quiet flag hanya menampilkan error dan warning tanpa full output — cocok untuk CI/CD validation step.


Referensi Lanjutan #

Profiles #

Docker Compose punya fitur profiles yang memungkinkan kamu group service berdasarkan tujuan (development, testing, production). Service dengan profile hanya dijalankan jika profile tersebut diaktifkan.

services:
  web:
    image: nginx
    ports:
      - "80:80"

  debug-tools:
    image: alpine
    profiles: ["debug"]
    command: sleep infinity

  load-generator:
    image: loadtest
    profiles: ["load-test"]
# Default — debug-tools dan load-generator tidak jalan
docker compose up

# Dengan profile debug
docker compose --profile debug up

# Dengan profile load-test
docker compose --profile load-test up

Profile sangat berguna untuk development environment di mana kamu kadang butuh tools tambahan (debugger, log viewer, mock server) tanpa harus menambahkannya ke setup default.

Variable Substitution #

YAML mendukung variable substitution dari environment host.

services:
  api:
    image: myapp:${VERSION:-latest}
    ports:
      - "${PORT:-8080}:8080"
    environment:
      - LOG_LEVEL=${LOG_LEVEL}

${VAR} mengambil nilai dari environment. ${VAR:-default} mengambil nilai default jika VAR tidak di-set. Compose membaca environment host saat file di-parse.

Multiple Compose Files dan Merge #

Field overriding saat pakai multiple files:

docker compose -f base.yml -f override.yml up

Saat override.yml punya service yang sama dengan base.yml, field di override menimpa field di base. Tapi lists dan maps di-merge, bukan replace.

# base.yml
services:
  web:
    environment:
      - LOG_LEVEL=info
      - NODE_ENV=production

# override.yml
services:
  web:
    environment:
      - LOG_LEVEL=debug

Hasil: LOG_LEVEL=debug (overridden) + NODE_ENV=production (preserved).

JSON Syntax #

Compose juga support JSON sebagai alternatif YAML. Berguna jika YAML tidak available di toolchain-mu.

{
  "services": {
    "web": {
      "image": "nginx",
      "ports": ["80:80"]
    }
  }
}

Tapi YAML lebih readable untuk konfigurasi yang kompleks, jadi JSON jarang dipakai kecuali untuk konfigurasi yang di-generate programmatically.

Tools pendukung: Beberapa IDE punya extension untuk docker-compose.yml — auto-complete, validation, dan visualisasi service dependency. Rekomendasi: VSCode dengan extension “Docker” atau “Kubernetes” (juga support Compose).

Ringkasan #

  • docker-compose.yml adalah file YAML deklaratif yang mendefinisikan seluruh stack aplikasi multi-container.
  • Top-level keys: services (wajib), volumes, networks, configs, secrets. Field version: sudah opsional di Compose V2.
  • Setiap service punya banyak field: image/build, ports, environment, volumes, networks, depends_on, restart, command, dll.
  • depends_on ≠ service readydepends_on hanya menunggu container start. Untuk benar-benar menunggu service siap, pakai healthcheck + condition: service_healthy.
  • Network isolation — pisahkan service ke beberapa network (frontend, backend) untuk membatasi komunikasi.
  • Resource limits penting di production untuk mencegah satu service menghabiskan semua resource.
  • Validasi dengan docker compose config sebelum menjalankan, untuk catch error syntax dan referensi invalid.
  • Override file (-f docker-compose.dev.yml) memungkinkan konfigurasi base yang sama untuk banyak environment.
  • Konvensi: indentasi 2 spasi, tidak pakai tab, komentar menjelaskan why, nama service dan volume deskriptif.

← Sebelumnya: Apa itu Docker Compose?   Berikutnya: Multi-Container →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact