Netzwerkisolation zwischen Compose-Stacks durchsetzen

Netzwerkisolation zwischen Compose-Stacks durchsetzen

Verschiedene Compose-Stacks auf demselben Host können sich standardmäßig gegenseitig erreichen — das ist oft unerwünscht.

Das Problem: Default-Isolation existiert nicht

Compose erstellt pro Stack ein eigenes Netzwerk (stackname_default). Auf den ersten Blick sieht das nach Isolation aus — aber Container auf unterschiedlichen Compose-Netzwerken können sich dennoch erreichen, wenn sie im selben Docker-Subnetz liegen oder auf dasselbe externe Netzwerk zugreifen.

Der Docker-Default-Bridge (docker0) erlaubt Kommunikation zwischen allen Containern die kein explizites Netzwerk haben. User-defined Networks isolieren besser, aber nur wenn man die Konfiguration versteht.

Isolation testen

# Kann Stack A auf Stack B zugreifen?
docker exec appA curl http://appB:80

# Oder per IP direkt
docker inspect appB --format '{{.NetworkSettings.Networks}}'
docker exec appA curl http://172.20.0.5:80

Saubere Isolation mit internen Netzwerken

Interne Netzwerke (kein Außenzugang)

# Stack A: backend-app
services:
  app:
    image: myapp
    networks:
      - internal
      - dmz          # nur für Reverse-Proxy-Zugang

  database:
    image: postgres:15
    networks:
      - internal     # nur intern erreichbar, nicht von außen

networks:
  internal:
    internal: true   # kein Traffic raus, kein Traffic rein von außen
  dmz:
    external: true   # geteilt mit Reverse Proxy
# Stack B: anderer Stack
services:
  otherapp:
    image: otherapp
    networks:
      - internal     # eigenes internes Netzwerk, anderer Name → kein Overlap
      - dmz

networks:
  internal:
    internal: true
  dmz:
    external: true

Da beide Stacks internal: true-Netzwerke haben, die nicht geteilt werden, können sie sich nicht gegenseitig erreichen. Der Reverse Proxy (Caddy, Traefik) ist das einzige, das im gemeinsamen dmz-Netzwerk sitzt.

Gemeinsames Netzwerk nur für Reverse Proxy

# Einmalig das DMZ-Netzwerk anlegen
docker network create dmz
# Caddy / Reverse Proxy Stack
services:
  caddy:
    image: caddy:latest
    networks:
      - dmz

networks:
  dmz:
    external: true

Nur der Reverse Proxy darf in beide Netzwerke — Backend-Services sind nur über ihn erreichbar.

Isolation verifizieren

# Sollte fehlschlagen wenn korrekt isoliert:
docker exec appA curl http://appB:80
# → curl: (6) Could not resolve host: appB

# Direkter IP-Test (DNS umgehen)
IPAPP_B=$(docker inspect appB --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}')
docker exec appA curl http://$IPAPP_B:80
# → curl: (7) Failed to connect

Docker Default Bridge abschalten

Wenn Container ohne --network gestartet werden, landen sie auf der Default Bridge und können sich alle erreichen. Lösung: Default Bridge in daemon.json deaktivieren (fortgeschritten, bricht bestehende Setups):

{
  "bridge": "none"
}

Realistischer: konsequent user-defined Networks in jedem Compose-Stack verwenden und network_mode: bridge nie explizit setzen.

Checkliste für Isolation

  • [ ] Jeder Stack hat eigene internal: true-Netzwerke für Backend-Services
  • [ ] Datenbanken sind nicht im gemeinsamen Netzwerk
  • [ ] Nur Reverse Proxy hat Zugang zum gemeinsamen DMZ-Netzwerk
  • [ ] Ports werden nicht unnötig nach außen gemappt (ports: weglassen für interne Services)
  • [ ] Isolation mit curl-Test verifiziert

Zusammenfassung

Echte Isolation zwischen Compose-Stacks erfordert bewusste Netzwerk-Konfiguration: internal: true für Backend-Netzwerke, ein gemeinsames externes Netzwerk nur für den Reverse Proxy, und konsequenter Verzicht auf unnötige Port-Mappings.