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_on default TIDAK menunggu service siap. Tanpa condition: service_healthy, depends_on hanya 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.

← Sebelumnya: DNS Internal   Berikutnya: Network Isolation →

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