Network Isolation #

Salah satu kekuatan utama Docker yang sering tidak disadari adalah network isolation — kemampuan untuk membuat container benar-benar terisolasi dari satu sama lain dan dari jaringan luar, kecuali kalau secara eksplisit diizinkan. Tanpa isolasi yang baik, Docker hanya menjadi “VM ringan” tanpa proteksi yang berarti.

Container tanpa network isolation = semua container bisa saling bicara, semua service terekspos ke network host, dan satu celah keamanan kecil bisa berakibat fatal. Inilah skenario yang sering terjadi di production yang setup-nya asal-asalan: satu container compromised, attacker bisa scan internal service, akses database, dan exfiltrate data dalam hitungan menit.

Artikel ini membahas network isolation secara mendalam: konsep, mekanisme di level OS, pola segmentasi yang umum, internal network, defense in depth, dan best practice untuk environment multi-tenant. Setelah membaca artikel ini, kamu akan bisa merancang topologi network container yang aman dan scalable.

Apa Itu Network Isolation? #

Network isolation adalah prinsip bahwa container tidak bisa saling berkomunikasi secara bebas kecuali secara eksplisit diizinkan. Setiap container punya “batas jaringan” yang hanya bisa ditembus lewat konfigurasi yang disengaja.

flowchart TB
    subgraph Host["Docker Host"]
        subgraph NetA["Network A"]
            A1[Container A1]
            A2[Container A2]
        end
        subgraph NetB["Network B"]
            B1[Container B1]
            B2[Container B2]
        end
        subgraph NetC["Network C (internal)"]
            C1[(Database)]
        end
    end
    A1 -.->|tidak bisa| B1
    A1 -.->|tidak bisa| C1
    A2 <-->|bisa| A1
    B1 <-->|bisa| B2
    A2 <-.->|via API gateway| B1

Karakteristik network isolation yang baik:

  • Default deny — container tidak bisa bicara ke container lain tanpa network yang sama.
  • Explicit allow — koneksi hanya boleh jika dideklarasikan (network, port mapping, atau policy).
  • Granular segmentation — frontend, backend, dan data dipisah ke network berbeda.
  • Internal-only — service yang tidak perlu internet tidak punya akses keluar.
  • Egress control — kemana container boleh bicara keluar juga dikontrol.
Prinsip utama: isolasi adalah default, bukan tambahan. Container terisolasi sejak detik pertama — kamu yang memilih untuk membuka isolasi itu. Kalau ragu, biarkan terisolasi.

Network Namespace: Fondasi Isolasi #

Di level OS, network namespace adalah mekanisme yang dipakai Docker untuk mengisolasi container. Setiap container berjalan di namespace sendiri yang punya:

  • Interface sendiri (eth0, lo).
  • Routing table sendiri.
  • Aturan iptables sendiri.
  • Socket sendiri.
flowchart LR
    subgraph HOST["Host Network Namespace"]
        H_ETH[eth0: 192.168.1.10]
        H_BR[docker0]
        H_RT[Host routing]
    end
    subgraph NS1["Container A Namespace"]
        A_ETH[eth0: 172.18.0.2]
        A_LO[lo: 127.0.0.1]
    end
    subgraph NS2["Container B Namespace"]
        B_ETH[eth0: 172.18.0.3]
        B_LO[lo: 127.0.0.1]
    end
    A_ETH <-.->|veth| H_BR
    B_ETH <-.->|veth| H_BR
    H_ETH --- H_RT

Container A dan B di namespace berbeda yang dihubungkan ke bridge docker0 lewat veth pair. Mereka tidak “melihat” interface satu sama lain secara langsung. Komunikasi hanya bisa terjadi karena bridge forwarding packet.

# Lihat semua network namespace di host
ip netns list

# Lihat namespace container
docker run --rm --network bridge alpine sh -c 'cat /proc/self/net/dev'
# Container hanya lihat eth0 dan lo sendiri

# Lihat di host namespace
ip link show
# Akan ada banyak veth dan bridge

Layer-Layer Isolasi #

Network isolation Docker bekerja di beberapa layer:

Layer 1: Network Namespace #

Container punya network namespace sendiri, terisolasi dari host dan container lain. Default untuk semua container.

Layer 2: Network Driver #

Pilihan bridge, host, none, overlay, macvlan menentukan jenis isolasi. Bridge = terisolasi dalam bridge sendiri. Host = tidak terisolasi.

Layer 3: Network Topology #

User-defined network memberikan batas alami. Container di network A tidak bisa bicara ke network B tanpa routing.

Layer 4: Port Mapping #

-p adalah pintu keluar dari isolasi. Tanpa -p, container benar-benar tersembunyi dari host. Dengan -p, port tertentu di-expose.

Layer 5: iptables Rules #

Docker menambah aturan iptables untuk NAT, port mapping, dan default-deny. Custom rules bisa menambah lapisan firewall tambahan.

flowchart TB
    L1[Layer 1: Network Namespace] --> L2[Layer 2: Network Driver]
    L2 --> L3[Layer 3: Network Topology]
    L3 --> L4[Layer 4: Port Mapping]
    L4 --> L5[Layer 5: iptables]
    style L1 fill:#dfd
    style L2 fill:#dfd
    style L3 fill:#dfd
    style L4 fill:#fdd
    style L5 fill:#fdd

Hijau = default isolated, Merah = default terbuka ke host/internet. Layer 4 dan 5 perlu konfigurasi eksplisit untuk menjaga isolasi.


Segmentasi: Fondasi Isolasi yang Baik #

Segmentasi adalah pemisahan network berdasarkan fungsi atau tingkat keamanan. Ini adalah pola paling efektif untuk isolasi.

Tiga Layer Segmentasi Klasik #

flowchart TB
    subgraph Edge["Edge (public)"]
        LB[Load Balancer]
    end
    subgraph App["Application (internal)"]
        API1[API 1]
        API2[API 2]
    end
    subgraph Data["Data (most restricted)"]
        DB[(Database)]
        Cache[(Cache)]
    end
    LB --> API1
    LB --> API2
    API1 --> DB
    API1 --> Cache
    API2 --> DB
    API2 --> Cache
# docker-compose.yml
version: "3.9"
services:
  loadbalancer:
    image: traefik
    ports:
      - "443:443"
    networks:
      - edge
  
  api:
    image: my-api
    networks:
      - edge
      - app
    deploy:
      replicas: 3
  
  worker:
    image: my-worker
    networks:
      - app
  
  postgres:
    image: postgres
    networks:
      - data
    # Tidak ada port mapping!
  
  redis:
    image: redis
    networks:
      - data

networks:
  edge:
  app:
    internal: false  # keluar ke internet untuk API call
  data:
    internal: true   # TIDAK ada akses keluar

Karakteristik:

  • edge network — tempat load balancer, satu-satunya yang terekspos publik.
  • app network — tempat aplikasi logic, bisa bicara keluar (misal ke third-party API).
  • data network — internal only, tidak ada akses internet. Database benar-benar tersembunyi.
  • Service placementapi ada di edge dan app (sebagai bridge). postgres hanya di data (tersembunyi dari edge).
internal: true adalah fitur powerful. Network dengan internal: true tidak punya gateway keluar ke internet. Container di network ini benar-benar off-grid. Cocok untuk database, cache, internal service — semua yang tidak butuh (dan tidak boleh punya) akses keluar.

Multi-Tenant: Tenant A vs Tenant B #

Untuk environment multi-tenant (banyak user/customer), segmentasi per tenant adalah keharusan.

services:
  tenant-a-app:
    image: tenant-a-app
    networks:
      - tenant-a
  
  tenant-b-app:
    image: tenant-b-app
    networks:
      - tenant-b

networks:
  tenant-a:
    name: tenant-a-net
    internal: true
  tenant-b:
    name: tenant-b-net
    internal: true

Tenant A dan Tenant B punya network berbeda. Mereka sama sekali tidak bisa saling lihat, walaupun di host yang sama. Compromised tenant tidak akan mengancam tenant lain.

flowchart TB
    subgraph TenantA["Tenant A"]
        A1[App A]
        ADB[(DB A)]
        A1 --- ADB
    end
    subgraph TenantB["Tenant B"]
        B1[App B]
        BDB[(DB B)]
        B1 --- BDB
    end
    A1 -.->|X no access| B1
    style A1 fill:#ddf
    style B1 fill:#fdd

Internal Network: Akses Internet = 0 #

Network dengan internal: true punya perilaku khusus:

  • Tidak ada gateway keluar ke host/internet.
  • Tidak ada NAT keluar — paket tidak akan pernah bisa keluar.
  • Container tetap bisa bicara satu sama lain di network yang sama.
  • DNS internal Docker masih aktif.
networks:
  internal-db:
    internal: true
# Verifikasi: container di internal network tidak bisa ping google
docker run --rm --network internal-db alpine ping -c 1 8.8.8.8
# ping: connect: Network is unreachable

Use case:

  • Database — tidak perlu (dan tidak boleh) akses internet.
  • Cache internal — Redis, Memcached.
  • Message broker — Kafka, RabbitMQ.
  • Admin tools — pgAdmin, RedisInsight.
  • Internal API — service-to-service yang tidak boleh dipanggil dari internet.
Jangan pakai internal: true untuk service yang perlu fetch dependency runtime. Misalnya image yang fetch konfigurasi dari S3 saat start. Image harus sudah contain semua dependency di build time.

Default Bridge vs User-Defined Network untuk Isolasi #

Default bridge network Docker punya kelemahan isolasi serius:

  • Semua container di default bridge berbagi satu subnet.
  • Tidak ada DNS internal.
  • Tidak ada segmentasi.

User-defined network memberikan isolasi yang jauh lebih granular:

  • Satu network = satu batas isolasi.
  • Container di network A tidak bicara ke network B.
  • DNS internal = service discovery yang aman.
  • Bisa di-custom subnet dan gateway.
# Anti-pattern: semua di default bridge
docker run -d --name app my-app
docker run -d --name db postgres
docker run -d --name cache redis
# app, db, cache semua di bridge yang sama
# Compromised cache bisa scan db

# Best practice: segmentasi
docker network create app-net
docker network create data-net
docker run -d --name app   --network app-net my-app
docker run -d --name db    --network data-net postgres
docker run -d --name cache --network data-net redis
# app tidak punya akses langsung ke db/cache
# (kecuali dikoneksikan manual)

Container dengan Banyak Network: Layered Security #

Satu container bisa terhubung ke multiple network. Ini dipakai untuk membuat layered security — container jadi “gateway” yang bisa kamu kontrol.

docker network create edge --internal=false
docker network create app --internal=false
docker network create data --internal=true

docker run -d --name api --network edge nginx
# api di-attach ke app juga
docker network connect app api
# api di-attach ke data juga
docker network connect data api
flowchart LR
    Edge["edge (public)"] -->|eth0a| API[API]
    App["app (work)"] -->|eth0b| API
    Data["data (internal)"] -->|eth0c| API
    Data --> DB[(db)]
    Data --> Cache[(cache)]

Container api punya tiga interface di tiga network berbeda. Ia jadi satu-satunya titik kontrol antara:

  • Internet (lewat edge) ↔ Aplikasi (app)
  • Aplikasi (app) ↔ Data (data)

Kalau api compromised, attacker bisa lewat api ke db. Tapi db tidak bisa di-reach langsung dari internet — harus lewat api. Ini memberikan kamu satu titik audit, satu titik policy enforcement.

Ini adalah pola “jump host” yang sudah lama dipakai di security tradisonal. Container berperan sebagai bastion host. Ia lebih terekspos, tapi ia juga yang paling dimonitor dan diaudit.

Network Isolation dan Keamanan Produksi #

1. Default Deny: Disable Semua yang Tidak Diperlukan #

# Hapus semua default network policy
iptables -P FORWARD DROP

# Allow hanya traffic yang perlu
iptables -A FORWARD -i docker0 -o docker0 -j ACCEPT  # container-to-container
iptables -A FORWARD -i docker0 -j ACCEPT              # container keluar

Docker menambah rule ini secara otomatis, tapi beberapa rule tambahan bisa memperkuat isolasi:

# Drop traffic antar bridge (sudah default di Docker, tapi tambahan tidak ada salahnya)
iptables -A FORWARD -i br-a -o br-b -j DROP

2. Custom iptables untuk Layer Tambahan #

Tambah rule di chain DOCKER-USER (lihat artikel NAT):

# Hanya izinkan traffic dari network 10.0.0.0/8 ke container
iptables -A DOCKER-USER -s 10.0.0.0/8 -j ACCEPT
iptables -A DOCKER-USER -j DROP

3. NetworkPolicy (Kubernetes) #

Untuk Kubernetes, NetworkPolicy adalah cara formal untuk declare isolation.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-policy
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: web
      ports:
        - protocol: TCP
          port: 8080
  egress:
    - to:
        - podSelector:
            matchLabels:
              app: db
      ports:
        - protocol: TCP
          port: 5432
    # DNS ke kube-system
    - to:
        - namespaceSelector:
            matchLabels:
              name: kube-system
      ports:
        - protocol: UDP
          port: 53

api hanya boleh terima dari web, hanya boleh bicara ke db dan DNS. Default deny untuk semua traffic lain.

Tanpa NetworkPolicy di K8s, semua pod bisa bicara ke semua pod. Ini default K8s. Untuk production, NetworkPolicy adalah keharusan. Tool seperti Calico, Cilium, atau Weave bisa enforce policy ini.

Anti-Pattern yang Melanggar Isolasi #

1. Host Network untuk Convenience #

# ✗ Anti-pattern: host network untuk skip konfigurasi
docker run -d --network host my-app
# Tidak ada isolasi. Container = warga kelas satu di host.
# ✓ Solusi: bridge + port mapping
docker run -d --network app-net -p 8080:8080 my-app

2. Default Bridge untuk Multi-Container #

# ✗ Anti-pattern: semua di default bridge, hardcode IP
docker run -d --name db postgres
docker run -d -e DB_HOST=172.17.0.2 my-api
# IP bisa berubah saat restart
# ✓ Solusi: user-defined network + DNS
docker network create app-net
docker run -d --name db  --network app-net postgres
docker run -d -e DB_HOST=db --network app-net my-api

3. Database Terekspos #

# ✗ Anti-pattern: postgres dengan -p
docker run -d -p 5432:5432 postgres
# Database terekspos ke semua interface host
# ✓ Solusi: tidak ada -p, postgres hanya di network internal
docker run -d --network data-net --name db postgres
# data-net internal: true, no port mapping

4. Volume Docker Socket Mounted #

# ✗ Anti-pattern: container biasa mount docker.sock
docker run -d -v /var/run/docker.sock:/var/run/docker.sock my-app
# my-app bisa kontrol Docker daemon
# ✓ Solusi: batasi ke container yang memang butuh (Portainer, dll)
# Untuk app biasa, JANGAN mount docker.sock

5. Semua Service di Satu Network #

# ✗ Anti-pattern
docker network create all-in-one
docker run -d --name app --network all-in-one my-app
docker run -d --name db  --network all-in-one postgres
docker run -d --name admin --network all-in-one pgadmin
# admin bisa akses db - padahal harusnya tidak
# ✓ Solusi: pisahkan
docker network create data-net --internal
docker network create mgmt-net
docker run -d --name db    --network data-net postgres
docker run -d --name admin --network mgmt-net,data-net pgadmin
# admin punya akses ke db, tapi db tidak terekspos

Monitoring Network Isolation #

Untuk memastikan isolasi bekerja, monitoring yang baik:

# Lihat semua network dan container di dalamnya
docker network ls
for net in $(docker network ls -q); do
  echo "=== Network: $(docker network inspect $net --format '{{.Name}}') ==="
  docker network inspect $net --format '{{range .Containers}}{{.Name}} {{end}}'
done

# Lihat rule iptables aktif
sudo iptables -L -n -v | head -50

# Lihat policy di K8s
kubectl get networkpolicy -A
# Prometheus alert: container yang mount docker.sock
docker_container_mounts{type="bind",source="/var/run/docker.sock"} > 0

Best Practice Network Isolation #

WAJIB:
  ✓ Segmentasi network per layer (edge, app, data)
  ✓ Internal network untuk service yang tidak perlu internet
  ✓ User-defined network, bukan default bridge
  ✓ Tidak ada port mapping untuk service internal
  ✓ Container tertentu sebagai gateway dengan audit
  ✓ NetworkPolicy di Kubernetes untuk semua pod
  ✓ Monitoring dan alert untuk perubahan topology

JANGAN:
  ✗ Pakai host network untuk "simplify"
  ✗ Semua service di satu network besar
  ✗ Mount /var/run/docker.sock untuk app biasa
  ✗ Default bridge untuk multi-container
  ✗ Hardcode IP antar container
  ✗ Expose port database
  ✗ Pakai network yang sama untuk multi-tenant

Ringkasan #

  • Network isolation = container tidak bisa saling bicara secara default, hanya yang diizinkan. Prinsip default deny, explicit allow.
  • Network namespace adalah fondasi level OS. Tiap container punya interface, routing, dan iptables sendiri.
  • Lima layer isolasi: namespace (L1), driver (L2), topology (L3), port mapping (L4), iptables (L5). L1-L3 = default aman. L4-L5 perlu konfigurasi eksplisit.
  • Segmentasi = pemisahan per layer (edge/app/data) atau per tenant. Edge terekspos publik, app bekerja internal, data benar-benar tersembunyi.
  • internal: true = network tanpa gateway keluar. Database dan cache wajib pakai mode ini. Service tidak akan pernah bisa akses internet.
  • Layered security = container di banyak network sekaligus, jadi gateway/bridge yang bisa diaudit. Pola jump host.
  • Anti-pattern: host network untuk convenience, default bridge untuk multi-container, database terekspos, docker.sock mount sembarangan, satu network untuk semua.
  • NetworkPolicy di Kubernetes adalah formal declaration untuk default deny + explicit allow. Tanpa policy, semua pod bisa bicara ke semua pod.
  • Monitoring: audit docker network ls, iptables rules, dan NetworkPolicy di K8s. Alert untuk perubahan topology.
  • Prinsip utama: isolation by default, open by configuration. Kalau ragu, biarkan terisolasi dan buka hanya yang dibutuhkan.
  • Untuk multi-tenant: satu tenant = satu network, internal. Tenant A dan tenant B tidak boleh tahu keberadaannya.

← Sebelumnya: Container-to-Container Communication   Berikutnya: User-defined Network →

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