Container-to-Container Communication #
Salah satu kekuatan utama Docker adalah kemampuannya menjalankan banyak container yang saling berkomunikasi. Container-to-container communication adalah fondasi arsitektur microservices — tiap service berjalan di container terpisah, bicara lewat network, dan tetap bisa di-maintain secara independen.
Banyak developer menjalankan container setiap hari tapi tidak benar-benar paham bagaimana container saling menemukan, apa beda pendekatan (network, volume, sidecar), dan mana yang sesuai untuk kasus mereka. Artikel ini membahas komunikasi container-to-container secara mendalam: pola-pola yang umum, anti-pattern yang sering terjadi, dan best practice untuk arsitektur production.
Apa Itu Container-to-Container Communication? #
Container-to-container communication adalah mekanisme di mana dua atau lebih container saling bertukar data — baik lewat HTTP API, gRPC, TCP socket, maupun channel lain. Docker menyediakan beberapa mekanisme untuk itu, masing-masing dengan trade-off yang berbeda.
flowchart LR
A[Container A] -->|HTTP/gRPC/TCP| B[Container B]
A -.->|via volume| C[(Shared Volume)]
A -.->|via stdin/stdout| D[Process pipe]
A -.->|via socket| E[Host socket]
Pendekatan utama:
| Pendekatan | Mekanisme | Cocok untuk |
|---|---|---|
| Network (default) | TCP/UDP lewat bridge | API, microservices |
| DNS internal | Resolve nama otomatis | Service discovery |
| Shared volume | File system bersama | File processing, batch |
| Sidecar | Container pembantu | Logging, proxy, sync |
| Host socket | Docker.sock, UNIX socket | Management tools |
Pendekatan network adalah yang paling umum dan paling fleksibel. Ia menjadi fokus utama artikel ini.
Pendekatan 1: Lewat Docker Network (Rekomendasi) #
Cara paling umum: kedua container di network yang sama, saling bicara lewat hostname.
docker network create app-net
docker run -d --name db --network app-net postgres
docker run -d --name api --network app-net my-api
Sekarang api bisa akses db dengan db:5432:
docker exec api sh -c 'wget -qO- http://db:5432 || echo "connected"'
sequenceDiagram
participant A as Container: api
participant DNS as Docker DNS (127.0.0.11)
participant B as Container: db
A->>DNS: gethostbyname("db")
DNS-->>A: 172.18.0.3
A->>B: TCP connect db:5432
B-->>A: Postgres handshake
A->>B: Query SQL
B-->>A: Response
Karakteristik pendekatan network:
- Discovery otomatis — DNS internal Docker me-resolve nama container.
- Isolasi — container di network berbeda tidak saling lihat.
- Performa — layer-2 forwarding lewat bridge, latency rendah.
- Skalabilitas — mudah tambah container baru.
Selalu pakai user-defined network untuk komunikasi antar container. Default bridge tidak punya DNS internal — kamu harus hardcode IP, yang rapuh. User-defined network = DNS internal + isolasi + scaling-friendly.
Contoh Docker Compose #
version: "3.9"
services:
web:
image: nginx
networks:
- frontend
api:
image: my-api
networks:
- frontend
- backend
db:
image: postgres
networks:
- backend
cache:
image: redis
networks:
- backend
networks:
frontend:
backend:
internal: true
Diagram:
flowchart LR
subgraph Frontend["frontend network"]
W[web]
A[api]
end
subgraph Backend["backend network (internal)"]
A
D[(db)]
C[(cache)]
end
W --> A
A --> D
A --> C
Container api ada di dua network sekaligus — bridge antara frontend dan backend. Ini adalah pola segmentasi keamanan yang umum: web bisa bicara ke api, tapi tidak langsung ke db.
Pendekatan 2: Lewat Shared Volume #
Untuk workload yang bekerja pada file, shared volume adalah pendekatan yang lebih natural daripada network.
# Buat volume bersama
docker volume create shared-data
# Container writer menulis ke volume
docker run -d --name writer \
-v shared-data:/data:rw \
my-writer
# Container reader membaca dari volume
docker run -d --name reader \
-v shared-data:/data:ro \
my-reader
flowchart LR
W[Container: writer] -->|write file| V[(Volume: shared-data)]
V -->|read file| R[Container: reader]
Pendekatan ini cocok untuk:
- ETL pipeline — extract, transform, load via file.
- Image processing — input → process → output.
- Video encoding — upload → encode → output.
- Log aggregation — log shipper baca dari shared directory.
Jangan pakai shared volume untuk data persisten. Volume bersama untuk sementara; data penting harus di named volume atau bind mount ke host. Shared volume antar container hilang saat container terakhir yang mount di-unmount.
Pendekatan 3: Lewat stdin/stdout (Process Pipe) #
Untuk container yang langsung berinteraksi lewat stdin/stdout (misal tool CLI), Docker mendukung pipe lewat --link atau container orchestration.
# Container A output jadi input container B
docker run --rm -i my-generator | docker run --rm -i my-processor
Pendekatan ini jarang dipakai di production, tapi berguna untuk:
- Quick scripting — chain beberapa tool.
- CI/CD step — pipe antar container.
- One-shot transformation — bukan long-running service.
Pendekatan 4: Sidecar Pattern #
Sidecar adalah container pembantu yang berjalan di samping container utama, memberikan fungsi tambahan.
flowchart LR
subgraph Pod["Application Pod"]
App[App Container]
Side[Sidecar Container]
end
App <-.->|localhost| Side
Contoh umum sidecar:
- Log shipper — baca log dari shared volume, kirim ke central logging.
- Metrics exporter — baca metrics dari localhost, expose ke Prometheus.
- Service mesh proxy — Envoy/Linkerd sebagai sidecar untuk mTLS.
- File sync — sync file dari container ke S3.
# Kubernetes-style sidecar
services:
app:
image: my-app
volumes:
- app-logs:/var/log/app
log-shipper:
image: fluent-bit
volumes:
- app-logs:/var/log/app:ro
environment:
- FLUENT_BIT_OUTPUT=s3
depends_on:
- app
volumes:
app-logs:
log-shipper dan app share app-logs volume. Shipper baca log, app tulis log. Tidak perlu network call untuk transfer data.
Sidecar + shared volume adalah pola dominan di Kubernetes — Pod punya banyak container yang share network namespace dan volume. Pola ini sekarang diadopsi juga di Docker Compose, terutama untuk tool manajemen.
Pendekatan 5: UNIX Socket / Host Socket #
Untuk container yang perlu kontrol langsung ke Docker daemon atau shared socket Unix.
# Container dengan akses ke Docker socket
docker run -d \
-v /var/run/docker.sock:/var/run/docker.sock \
portainer/portainer
# Custom UNIX socket antar container
docker run -d --name app -v /tmp/app.sock:/tmp/app.sock my-app
docker run -d --name sidecar -v /tmp/app.sock:/tmp/app.sock my-sidecar
Pendekatan ini powerful tapi berbahaya — container dengan akses ke Docker socket bisa spawn container lain, hapus image, atau bahkan keluar dari host. Hanya untuk tool terpercaya (Portainer, Watchtower, dll).
Mounting /var/run/docker.sock = container setara root di host. Siapa pun yang menguasai container ini punya kontrol efektif atas seluruh host. Audit setiap container yang mount docker.sock.
Perbandingan Pendekatan #
| Aspek | Network | Shared Volume | Sidecar | UNIX Socket |
|---|---|---|---|---|
| Latency | Sub-ms | Disk I/O | Network ke localhost | Lokal |
| Throughput | Tinggi | Tergantung disk | Tinggi | Sangat tinggi |
| Use case | API, microservices | File processing | Logging, proxy | Tool management |
| Skalabilitas | Sangat baik | Terbatas | Sedang | Buruk |
| Isolasi | Per network | Per volume | Per pod | Sangat rendah |
| Risiko keamanan | Rendah | Rendah | Sedang | Tinggi |
Untuk 90% kasus microservices, network + user-defined bridge adalah pilihan terbaik. File processing pakai volume, dan hanya pakai UNIX socket untuk tool manajemen khusus.
Pola Umum: API Gateway + Microservices #
Pola paling umum di production: API gateway menerima request dari client, lalu route ke microservices yang sesuai.
flowchart TB
Client[Client / Browser]
Gateway[API Gateway]
Auth[auth-service]
User[user-service]
Order[order-service]
Payment[payment-service]
DBUser[(user-db)]
DBOrder[(order-db)]
DBPay[(payment-db)]
Client -->|HTTPS| Gateway
Gateway --> Auth
Gateway --> User
Gateway --> Order
Gateway --> Payment
User --> DBUser
Order --> DBOrder
Payment --> DBPay
version: "3.9"
services:
gateway:
image: traefik
ports:
- "443:443"
networks:
- edge
auth:
image: auth-service
networks:
- edge
- backend
user:
image: user-service
networks:
- edge
- backend
order:
image: order-service
networks:
- edge
- backend
networks:
edge:
backend:
internal: true
Traefik di edge network, microservices di kedua edge dan backend, database hanya di backend (tidak terekspos).
Pola Umum: Event-Driven dengan Message Broker #
Untuk komunikasi asynchronous, container berkomunikasi lewat message broker (Kafka, RabbitMQ, Redis Streams).
flowflowchart LR
API[API Service] -->|publish| Q[Message Broker<br/>Kafka / RabbitMQ]
Q -->|subscribe| W1[Worker 1]
Q -->|subscribe| W2[Worker 2]
Q -->|subscribe| W3[Worker 3]
W1 --> DB[(Database)]
W2 --> DB
W3 --> DB
services:
api:
image: my-api
environment:
KAFKA_BROKER: kafka:9092
worker:
image: my-worker
environment:
KAFKA_BROKER: kafka:9092
deploy:
replicas: 3
kafka:
image: bitnami/kafka
networks:
- broker
db:
image: postgres
networks:
- data
networks:
broker:
internal: true
data:
internal: true
API publish event ke Kafka, workers subscribe dan proses. Worker bisa di-scale independen dari API. Database dan broker sama-sama internal — tidak ada akses dari luar.
Anti-Pattern dalam Container-to-Container Communication #
1. Hardcode IP #
# ✗ Anti-pattern: hardcode IP
docker run -e DATABASE_HOST=172.18.0.3 my-api
# IP bisa berubah saat restart!
# ✓ Solusi: pakai service name
docker run -e DATABASE_HOST=db my-api
# DNS resolve otomatis
2. Lewat Host (round-trip) #
# ✗ Anti-pattern: container A -> host -> container B
# A bicara ke host:8080, host forward ke B
# Penambahan hop, latency, dan kompleksitas
# ✓ Solusi: langsung di network yang sama
# A bicara ke B tanpa lewat host
3. Default Bridge untuk Multi-Container #
# ✗ Anti-pattern: semua container di default bridge
docker run -d --name db postgres
docker run -d --name api my-api
# api tidak bisa resolve "db" - harus pakai IP
# ✓ Solusi: user-defined network
docker network create app-net
docker run -d --name db --network app-net postgres
docker run -d --name api --network app-net my-api
4. Semua Service di Satu Network Besar #
# ✗ Anti-pattern: satu network untuk semua
docker network create all-services
docker run --network all-services --name db postgres
docker run --network all-services --name api my-api
docker run --network all-services --name admin pgadmin
# admin bisa akses db langsung - pelanggaran least privilege
# ✓ Solusi: segmentasi
docker network create backend --internal
docker network create frontend
# admin, db, cache di backend
# web, api di frontend
# api di kedua (sebagai bridge)
5. Port Mapping untuk Komunikasi Internal #
# ✗ Anti-pattern
services:
api:
ports:
- "8080:8080" # hanya supaya bisa diakses oleh web
web:
# web harus akses localhost:8080 - works tapi tidak clean
# ✓ Solusi: network sharing
services:
api:
# no ports
web:
# no ports
# Keduanya di network Compose default - web akses api:8080
Pola untuk Special Use Case #
Reverse Proxy untuk HTTP Traffic #
services:
traefik:
image: traefik
network_mode: host
app1:
image: my-app1
# no ports
app2:
image: my-app2
# no ports
Traefik di host network untuk performa, app di bridge. Traefik auto-discover via Docker labels.
Database Replication #
services:
postgres-primary:
image: postgres
environment:
POSTGRES_REPLICATION_MODE: master
postgres-replica:
image: postgres
environment:
POSTGRES_REPLICATION_MODE: slave
POSTGRES_MASTER_HOST: postgres-primary
Primary-replica communication lewat network internal — tidak ada -p ke host.
Service Mesh (Advanced) #
flowchart LR
A[App A] -->|localhost:15001| P1[Envoy Sidecar]
B[App B] -->|localhost:15001| P2[Envoy Sidecar]
P1 <-->|mTLS| P2
P1 -->|collect| CT[Control Plane]
P2 -->|collect| CT
Service mesh (Istio, Linkerd) inject sidecar ke tiap service. Komunikasi antar service lewat sidecar, yang handle mTLS, retry, observability. Kompleksitas bertambah, tapi kontrol dan observability juga bertambah.
Best Practice Container-to-Container Communication #
WAJIB:
✓ Selalu user-defined network untuk multi-container
✓ Pakai service name, bukan IP
✓ Segmentasi network (frontend/backend/data)
✓ Pakai message broker untuk async communication
✓ Internal network untuk service yang tidak perlu akses internet
JANGAN:
✗ Hardcode IP address
✗ Pakai default bridge untuk multi-container
✗ Expose port database atau service internal
✗ Lewat host untuk komunikasi internal
✗ Semua service di satu network besar
Pola Health Check dan Graceful Shutdown #
Komunikasi container-to-container yang baik bukan hanya soal “bisa bicara”, tapi juga soal bagaimana bicara dengan benar saat service masih starting up atau shutting down.
Health Check #
# Dockerfile
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget -qO- http://localhost:8080/health || exit 1
# docker-compose.yml
services:
api:
image: my-api
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:8080/health"]
interval: 30s
timeout: 3s
retries: 3
start_period: 10s
Dengan health check, Docker bisa deteksi kalau service sudah siap menerima koneksi. Ini penting untuk komunikasi antar service yang sensitif terhadap startup time.
depends_on dengan Health Check (Compose) #
services:
api:
image: my-api
depends_on:
db:
condition: service_healthy
networks:
- app-net
db:
image: postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
networks:
- app-net
api tidak akan start sampai db lulus health check. Tanpa ini, api bisa start duluan dan crash karena tidak bisa konek ke db yang belum siap.
depends_ondefault TIDAK menunggu service siap. Tanpacondition: service_healthy,depends_onhanya menunggu container start, bukan service di dalamnya listening. Untuk sistem yang butuh ordering benar, selalu tambah health check.
Graceful Shutdown #
Saat container di-stop, SIGTERM dikirim. Aplikasi harus handle ini dengan menutup koneksi existing sebelum exit.
# Python - handle SIGTERM
import signal
import sys
def handle_sigterm(*args):
print("Shutting down gracefully...")
# Close DB connections, finish in-flight requests
sys.exit(0)
signal.signal(signal.SIGTERM, handle_sigterm)
Docker mengirim SIGTERM, lalu SIGKILL setelah 10 detik (default). Aplikasi yang handle SIGTERM dengan benar akan shutdown tanpa memutus koneksi mid-flight.
services:
api:
image: my-api
stop_grace_period: 30s # kasih waktu 30 detik
stop_signal: SIGTERM
Ringkasan #
- Container-to-container communication adalah fondasi arsitektur microservices. Ada lima pendekatan: network, shared volume, sidecar, stdin/stdout pipe, dan UNIX socket.
- Network adalah pendekatan paling umum dan paling fleksibel. Pakai user-defined network + DNS internal + segmentasi.
- Shared volume cocok untuk file processing, log aggregation, dan workload yang bekerja pada data statis.
- Sidecar pattern adalah container pembantu yang running alongside app utama. Dominan di Kubernetes untuk logging, metrics, service mesh.
- UNIX socket powerful tapi berbahaya — hanya untuk tool terpercaya (Portainer, Watchtower).
- Pendekatan populer production: API Gateway + microservices dengan segmentasi network (edge/backend/data).
- Event-driven lewat message broker (Kafka, RabbitMQ) untuk komunikasi async dan worker scaling.
- Anti-pattern: hardcode IP, lewat host untuk komunikasi internal, default bridge untuk multi-container, semua service di satu network, port mapping untuk traffic internal.
- Service mesh (Istio, Linkerd) adalah evolution dari pola sidecar — mTLS otomatis, observability, retry logic. Kompleks, tapi untuk arsitektur besar, sangat berguna.
- Prinsip utama: segmentasi network, service discovery via DNS, port mapping seminimal mungkin, internal network untuk service yang tidak perlu publik.