Multi-Container #
Salah satu kekuatan utama Docker Compose adalah kemampuannya mengelola banyak container sebagai satu kesatuan yang kohesif. Saat aplikasimu punya beberapa service yang saling terkait — web frontend, API backend, database, cache, message broker — Compose memungkinkan kamu mendefinisikan, menjalankan, dan menghentikan semuanya dengan satu perintah.
Artikel ini membahas pola-pola multi-container yang umum, cara Compose mengelola networking dan dependency antar service, serta best practice untuk stack yang bersih dan maintainable.
Pola Arsitektur Multi-Container #
Ada beberapa pola arsitektur yang umum dijumpai di aplikasi multi-container. Masing-masing punya karakteristik dan use case yang berbeda.
Three-Tier Architecture #
Pola paling klasik: presentation tier (web), application tier (API), data tier (database). Setiap tier adalah service terpisah.
flowchart LR
U[User] --> W[Web Frontend]
W --> A[API Backend]
A --> D[(Database)]
A --> C[(Cache)]
services:
web:
image: nginx:alpine
ports:
- "80:80"
depends_on:
- api
api:
build: ./api
environment:
- DATABASE_URL=postgres://user:pass@db:5432/myapp
- REDIS_URL=redis://cache:6379
depends_on:
- db
- cache
db:
image: postgres:16-alpine
volumes:
- db-data:/var/lib/postgresql/data
cache:
image: redis:7-alpine
volumes:
db-data:
Pola ini jelas pemisahan tanggung jawabnya. Web layer scalable terpisah dari API. Database terisolasi. Cache opsional.
Microservices #
Aplikasi besar dipecah menjadi banyak service kecil, masing-masing dengan tanggung jawab tunggal. Compose bisa manage stack microservice untuk development, meski production biasanya butuh Kubernetes.
flowchart TB
GW[API Gateway] --> U[user-service]
GW --> O[order-service]
GW --> P[product-service]
GW --> Pay[payment-service]
U --> UD[(user-db)]
O --> OD[(order-db)]
P --> PD[(product-db)]
Pay --> PayDB[(payment-db)]
Untuk 5+ service, Compose masih bisa handle, tapi monitoring, scaling, dan deployment butuh orchestrator yang lebih advanced.
Frontend + Backend #
Pola umum di mana frontend dan backend di-deploy terpisah.
services:
frontend:
build: ./frontend
ports:
- "3000:3000"
environment:
- API_URL=http://api:8080
backend:
build: ./backend
ports:
- "8080:8080"
environment:
- DATABASE_URL=postgres://user:pass@db:5432/myapp
db:
image: postgres:16-alpine
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
Frontend pakai port 3000 (atau 5173 untuk Vite), backend 8080. Compose otomatis buat network internal sehingga frontend bisa komunikasi dengan backend via hostname api (nama service).
Worker + Queue #
Aplikasi yang punya worker asynchronous processing message dari queue.
services:
api:
build: ./api
environment:
- QUEUE_URL=amqp://rabbitmq:5672
worker:
build: ./worker
environment:
- QUEUE_URL=amqp://rabbitmq:5672
depends_on:
- rabbitmq
rabbitmq:
image: rabbitmq:3-management
ports:
- "15672:15672"
Worker dan API share konfigurasi queue yang sama. Scaling worker mudah: docker compose up -d --scale worker=5.
Networking Antar Service #
Salah satu hal paling powerful dari Compose adalah automatic service discovery di network internal. Service di file Compose yang sama otomatis bisa saling resolve via hostname = nama service.
services:
api:
image: myapi
environment:
- DATABASE_URL=postgres://postgres:secret@db:5432/myapp
Pada environment variable di atas, db adalah hostname. Compose otomatis membuat DNS entry di internal network sehingga api bisa resolve db ke IP container database.
Network Default #
Jika kamu tidak mendefinisikan network eksplisit, Compose membuat satu network default yang dinamai seperti <project>_default. Semua service otomatis join ke network ini.
# Lihat network yang dibuat Compose
docker network ls
# Inspect network
docker network inspect myapp_default
Multiple Networks #
Untuk kontrol lebih, definisikan beberapa network dan assign service sesuai perannya.
services:
web:
networks:
- frontend
- backend
api:
networks:
- frontend
- backend
db:
networks:
- backend
networks:
frontend:
backend:
Pada konfigurasi ini, db hanya ada di network backend. Service web punya interface di kedua network. Service db tidak bisa di-reach dari luar backend.
Ini pattern umum untuk defense in depth — service kritis dipisah dari frontend network.
Aliases #
Service bisa punya beberapa hostname dalam satu network.
services:
api:
networks:
frontend:
aliases:
- api-service
- myapi
Service api sekarang bisa di-reach via api, api-service, atau myapi di network frontend.
External Network #
Kamu bisa pakai network yang sudah ada di Docker (bukan yang dibuat Compose).
services:
api:
networks:
- shared-network
networks:
shared-network:
external: true
name: my-shared-network
Berguna untuk menghubungkan stack Compose yang berbeda atau service yang di-manage di luar Compose (mis. database managed).
Dependency Management #
Compose mendukung dua jenis dependency: depends_on (startup order) dan links (legacy, jarang dipakai).
Depends On: Startup Order #
services:
web:
depends_on:
- api
api:
depends_on:
- db
Dalam contoh, db start dulu, lalu api, lalu web. Tapi depends_on hanya menunggu container start, bukan service siap menerima request.
Untuk benar-benar menunggu service siap, tambahkan healthcheck dan condition.
services:
api:
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
condition bisa berupa:
service_started(default) — container sudah start.service_healthy— healthcheck pass.service_completed_successfully— container exit dengan code 0.
Long Startup Dependencies #
Beberapa service butuh waktu lama untuk siap (database besar, JVM warm-up, lazy initialization). Healthcheck + retry adalah kombinasi yang tepat.
services:
api:
depends_on:
db:
condition: service_healthy
restart: on-failure
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d myapp"]
interval: 5s
timeout: 3s
retries: 10
start_period: 30s
start_period memberi service waktu 30 detik tanpa healthcheck dianggap gagal — berguna untuk service yang butuh waktu inisialisasi panjang.
Sharing Data Antar Service #
Container biasanya terisolasi, tapi kadang perlu berbagi data. Compose mendukung ini via volumes.
Shared Volume #
services:
web:
volumes:
- uploads:/var/www/uploads
processor:
volumes:
- uploads:/var/lib/processor
volumes:
uploads:
Web meng-upload file ke /var/www/uploads, processor membaca dari /var/lib/processor. Volume uploads adalah shared storage.
Read-Only Mount #
Untuk container yang hanya butuh baca:
services:
web:
volumes:
- configs:/etc/app:ro
:ro membuat mount read-only. Service hanya bisa baca, tidak bisa tulis.
Init Container Pattern #
Container yang jalan duluan untuk setup data, lalu exit.
services:
app:
volumes:
- data:/app/data
depends_on:
- init-data
init-data:
image: alpine
volumes:
- data:/app/data
command: sh -c "echo '$(date)' > /app/data/init.txt"
restart: "no"
Init container jalan sekali, tulis data, exit. Service app bisa pakai data yang sudah disiapkan.
Service Scaling #
Compose mendukung scaling service ke beberapa instance dengan flag --scale.
# Scale web ke 3 instance
docker compose up -d --scale web=3
# Scale worker ke 5 instance
docker compose up -d --scale worker=5
Setiap instance adalah container terpisah. Mereka share konfigurasi tapi punya state terpisah (filesystem, proses ID, dll).
Load Balancing #
Untuk service yang di-scale, kamu butuh load balancer di depan. Compose sendiri tidak punya internal load balancing.
services:
web:
build: ./web
expose:
- "3000"
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- web
expose (bukan ports) membuat port hanya accessible di internal network, bukan host. Nginx di depan jadi satu-satunya entry point.
Scaling punya limit.--scaletidak bisa di pakai untuk service yang punyacontainer_name(nama harus unik). Juga tidak kompatibel denganportsshorthand (gunakan long formtarget: publishedagar tiap container dapet published port unik).
Environment-Specific Configuration #
Untuk setup yang sama dipakai di development, staging, dan production, pakai multiple Compose files.
# docker-compose.yml (base)
services:
web:
build: .
environment:
- NODE_ENV=production
# docker-compose.override.yml (development, auto-loaded)
services:
web:
environment:
- NODE_ENV=development
volumes:
- ./src:/app/src # hot reload
ports:
- "3000:3000"
Saat docker compose up di development, base + override di-merge otomatis. Untuk production, jalankan hanya base:
docker compose -f docker-compose.yml up -d
Best Practice #
Pisahkan Konfigurasi per Environment #
Gunakan file terpisah untuk development, staging, production. Secret management via secrets: (untuk compose di Swarm) atau .env (untuk development).
Beri Nama Service yang Deskriptif #
Nama service jadi hostname internal. Pilih nama yang menjelaskan fungsinya.
# BAGUS
services:
web:
...
api:
...
db:
...
cache:
...
# BURUK
services:
container1:
...
myservice:
...
app:
...
Gunakan Healthcheck untuk Dependency Kritis #
Service yang punya dependency wajib yang harus siap sebelum dependent start, tambahkan healthcheck dan condition: service_healthy.
Hindari Hardcoded Secret #
Gunakan .env file atau secrets: (untuk Swarm). Jangan hardcode password di YAML.
Volume untuk Data Persisten #
Selalu. Database, upload, log penting. Container bisa di-recreate tanpa kehilangan data.
Image Tag Eksplisit #
image: postgres:16-alpine # BAGUS
image: postgres:latest # BURUK - surprise update
latest tag bisa berubah tanpa kamu sadari. Selalu pin versi.
Resource Limits #
Untuk mencegah satu service menghabiskan semua resource, set limits.
services:
api:
deploy:
resources:
limits:
cpus: "1.0"
memory: 1G
Kunci sukses multi-container: clear dependency graph, healthcheck untuk service yang lambat start, network isolation sesuai peran, dan resource limits agar satu service tidak mengganggu yang lain. Mulai dengan stack kecil, tambahkan service satu per satu, dan selalu test scaling dan restart behavior.
Contoh Praktis: Stack E-Commerce Lengkap #
Stack e-commerce nyata biasanya punya 6-10 service. Contoh di bawah ini menunjukkan pola yang clean.
services:
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:
- web
networks:
- frontend
restart: unless-stopped
web:
build: ./web
environment:
- API_URL=http://api:8080
depends_on:
- api
networks:
- frontend
- backend
restart: unless-stopped
api:
build: ./api
environment:
- DATABASE_URL=postgres://shop:secret@db:5432/shop
- REDIS_URL=redis://cache:6379
- RABBITMQ_URL=amqp://guest:guest@mq:5672
depends_on:
db:
condition: service_healthy
cache:
condition: service_healthy
mq:
condition: service_healthy
networks:
- backend
restart: unless-stopped
worker:
build: ./worker
environment:
- DATABASE_URL=postgres://shop:secret@db:5432/shop
- REDIS_URL=redis://cache:6379
- RABBITMQ_URL=amqp://guest:guest@mq:5672
depends_on:
- api
networks:
- backend
restart: unless-stopped
db:
image: postgres:16-alpine
environment:
- POSTGRES_USER=shop
- POSTGRES_PASSWORD=secret
- POSTGRES_DB=shop
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U shop -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
mq:
image: rabbitmq:3-management
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "ping"]
interval: 10s
timeout: 5s
retries: 5
networks:
- backend
restart: unless-stopped
volumes:
db-data:
networks:
frontend:
backend:
Stack ini punya 7 service, 2 network, dan 1 volume. nginx adalah satu-satunya service yang di-expose ke host. Semua komunikasi internal terjadi via network backend yang tidak di-expose.
Observability #
Saat stack multi-container dijalankan, kamu butuh visibility ke setiap service.
Logs — Compose punya built-in log aggregation:
# Semua log
docker compose logs
# Follow log service tertentu
docker compose logs -f api
# Tail 100 baris terakhir
docker compose logs --tail 100
# Dengan timestamp
docker compose logs -t
Health status — lihat status semua service:
docker compose ps
Resource usage — lihat CPU dan memory per service:
docker stats
Untuk production-grade observability (metrics, distributed tracing), kamu butuh tool tambahan: Prometheus + Grafana, Datadog, New Relic, atau platform equivalent. Compose sendiri tidak menyediakan observability built-in.
Migration ke Production #
Saat stack siap untuk production, ada beberapa hal yang perlu diubah.
1. External database — Jangan jalankan database production di Compose di single host. Gunakan managed database (RDS, Cloud SQL) atau setup multi-host dengan replication.
2. Secret management — Ganti .env dengan secret manager proper: AWS Secrets Manager, HashiCorp Vault, atau Kubernetes Secrets.
3. Reverse proxy + TLS — Di production, selalu terminate TLS di reverse proxy. Cert-manager di Kubernetes atau Caddy/Traefik bisa otomatis manage sertifikat Let’s Encrypt.
4. Resource limits — Set CPU dan memory limit agar satu service tidak mengganggu service lain.
5. Log aggregation — Kirim log ke centralized logging (CloudWatch, Stackdriver, ELK, Loki) untuk retention dan searchability.
6. Health monitoring — Integrate dengan alerting (PagerDuty, OpsGenie) untuk notifikasi insiden.
7. Backup strategy — Database harus di-backup regular ke storage terpisah. Test restore procedure.
Setelah semua ini, baru pertimbangkan migrasi ke Kubernetes atau platform managed jika skala sudah melampaui single host.
Compose di production single host OK untuk traffic rendah-menengah. Untuk high traffic, multi-host, high availability, atau zero-downtime deploy, butuh orchestrator yang lebih powerful. Jangan paksakan Compose untuk workload yang butuh auto-scaling atau self-healing.
Ringkasan #
- Multi-container adalah kekuatan utama Docker Compose. Compose mengelola networking, dependency, dan lifecycle beberapa service sebagai satu stack.
- Pola umum: three-tier, microservices, frontend+backend, worker+queue. Pilih sesuai kompleksitas aplikasi.
- Automatic service discovery — service di Compose yang sama bisa saling resolve via hostname = nama service, tanpa setup DNS manual.
- Multiple networks untuk defense in depth — pisahkan service publik (frontend) dari yang sensitif (backend).
- Depends_on hanya menunggu container start. Untuk benar-benar menunggu service siap, pakai healthcheck +
condition: service_healthy.- Shared volume untuk data sharing antar service.
init containerpattern untuk setup data sebelum service utama start.--scaleuntuk multiple instance, tapi butuh reverse proxy/load balancer di depan.- Multi-file Compose (
-f override.yml) memungkinkan konfigurasi base yang sama untuk development, staging, production.- Best practice: clear dependency graph, healthcheck untuk service kritis, image tag eksplisit, resource limits, secret via
.env/secrets:, volume untuk data persisten.- Compose cocok untuk dev/test/small prod. Untuk multi-host dan high availability, butuh Kubernetes atau platform managed.
← Sebelumnya: Struktur File Berikutnya: Environment Variable →