Treiber-Übersicht

Treiber Isolation Container-DNS LAN-sichtbar Einsatz
bridge ✅ (user-def) Standard, Multi-Container
host Performance, Monitoring-Agents
none ✅✅ Batch, sicherheitskritische Prozesse
macvlan ✅ eigene MAC LAN-Integration, Pi-hole
ipvlan ✅ geteilte MAC Managed Switches, RZ

Standard-Bridge: Container per Name nicht auflösbar. User-defined Bridge: DNS funktioniert.


Bridge

Bridge ist der Standard-Treiber. Jedes user-defined Bridge-Netzwerk ist ein isoliertes virtuelles Netzwerk auf dem Host – Container im selben Netzwerk können sich per Name finden, Container in anderen Netzwerken nicht.

Der entscheidende Unterschied zwischen Standard-Bridge (docker0) und user-defined Bridge: Im Standard-Netzwerk funktioniert DNS-Auflösung per Name nicht – nur IP-Adressen, die sich bei jedem Neustart ändern können. Jedes selbst erstellte Netzwerk bekommt den eingebetteten DNS-Server und löst Container-Namen automatisch auf.

docker network create \
  --driver bridge \
  --subnet 10.10.1.0/24 \
  --gateway 10.10.1.1 \
  my-net
 
# Container nachträglich einbinden
docker network connect my-net container-name
 
# Container aus Netzwerk entfernen
docker network disconnect my-net container-name

Container in mehreren Netzwerken – typisches API-Pattern:

frontend-net        backend-net
nginx ──── api ──── postgres
           │
           redis

nginx und postgres können sich nie direkt erreichen – nur api steht in beiden Netzwerken. postgres und redis sind vom Internet vollständig abgeschirmt, nginx hat keinen direkten Datenbankzugriff. Das ist gewollt: minimale Angriffsfläche durch maximale Isolation.

docker network connect frontend-net api
docker network connect backend-net api

Host & None

host

Mit --network host teilt der Container den Netzwerk-Stack des Hosts direkt – kein eigenes Interface, keine NAT, keine Port-Übersetzung. Ein Prozess der im Container auf Port 80 lauscht, ist sofort auf Port 80 des Hosts erreichbar, ohne --publish.

# Host – kein eigener Netzwerk-Namespace, kein Port-Mapping nötig
docker run --network host nginx

Der Nachteil ist die fehlende Isolation: Der Container sieht alle Host-Interfaces und kann theoretisch auf alle Host-Ports zugreifen. Sinnvoll wenn der NAT-Overhead messbar ist, oder wenn ein Tool die echten Netzwerkinterfaces des Hosts lesen muss – etwa Monitoring-Agents die Netzwerkstatistiken pro Interface erheben.

host relevant für: Beszel-Agent, Gluetun-Agent, Tools die Host-Netzwerkinterfaces direkt lesen müssen.

none

# None – nur loopback, kein Internetzugriff
docker run --network none my-job

Vollständige Netzwerkisolierung – nur 127.0.0.1 ist verfügbar. Sinnvoll für Prozesse die bewusst keine Netzwerkverbindung haben sollen: kryptografische Operationen, Schlüsselgenerierung, Datentransformationen auf sensiblen Daten, oder CI-Jobs wo Netzwerkzugriff zur Laufzeit ein Sicherheitsrisiko wäre.


Macvlan & IPvlan

Beide Treiber gehen einen Schritt weiter als Bridge: Container erscheinen als eigenständige Geräte im physischen Netzwerk und bekommen eine eigene IP aus dem LAN-Subnetz.

Macvlan

Jeder Container bekommt eine eigene MAC-Adresse und ist im lokalen Netzwerk direkt sichtbar – wie ein physisches Gerät. Der Router sieht den Container als eigenständigen Host.

# Macvlan – eigene MAC pro Container
docker network create \
  --driver macvlan \
  --subnet 192.168.1.0/24 \
  --gateway 192.168.1.1 \
  --opt parent=eth0 \
  macvlan-net
 
# Container mit fester LAN-IP starten
docker run -d \
  --network macvlan-net \
  --ip 192.168.1.50 \
  --name pihole \
  pihole/pihole

pihole ist jetzt unter 192.168.1.50 im gesamten LAN erreichbar – ohne Port-Forwarding, ohne Traefik, direkt als wäre es ein physisches Gerät im Netzwerk. Jedes Gerät im LAN kann 192.168.1.50 als DNS-Server eintragen.

Typischer Einsatz: Pi-hole, Home Assistant, oder andere Dienste die eine eigene feste LAN-IP brauchen.

Host-Zugriff: Der Host selbst kann Macvlan-Container standardmäßig nicht direkt erreichen – physische Einschränkung der MAC-Isolierung. Workaround: ein Macvlan-Interface auf dem Host erstellen (ip link add macvlan0 link eth0 type macvlan mode bridge).

Switch-Anforderung: Der physische Switch-Port muss Promiscuous Mode oder mehrfache MACs pro Port erlauben. Bei managed Switches im Rechenzentrum oft nicht der Fall.

IPvlan

IPvlan teilt die MAC-Adresse des Eltern-Interfaces – Container bekommen nur eigene IP-Adressen, keine eigene MAC. Daher kein Promiscuous-Mode-Problem.

# IPvlan – geteilte MAC, eigene IP (L2-Modus)
docker network create \
  --driver ipvlan \
  --subnet 192.168.1.0/24 \
  --gateway 192.168.1.1 \
  --opt parent=eth0 \
  ipvlan-net

IPvlan bevorzugen wenn Switch Promiscuous Mode oder mehrfache MACs blockiert – typisch in Rechenzentren und bei managed Enterprise-Switches.


DNS & Namensauflösung

Docker betreibt in jedem user-defined Netzwerk einen eingebetteten DNS-Server unter 127.0.0.11. Container-Namen werden automatisch aufgelöst – kein /etc/hosts-Eintrag, keine externe Konfiguration nötig.

Das Standard-Bridge-Netzwerk (docker0) hat keinen eingebetteten DNS. Wer dort Container per Name ansprechen will, ist auf veraltete --link-Flags angewiesen – nicht empfohlen.

# Alias beim Verbinden – Container unter mehreren Namen erreichbar
docker network connect --alias db --alias database my-net postgres
 
# Lookup testen
docker run --rm --network my-net busybox nslookup postgres

Aliase sind nützlich wenn eine Applikation einen bestimmten Hostnamen erwartet der nicht dem Container-Namen entspricht – z.B. db statt postgres-15-prod.

Statische Einträge in /etc/hosts – Workaround wenn kein eigener DNS vorhanden:

extra_hosts:
  - "intern.example.com:192.168.100.10"

Relevant bei Gluetun: Container hinter einem VPN-Tunnel können lokale DNS-Namen nicht auflösen – extra_hosts schreibt den Eintrag direkt in /etc/hosts des Containers, bevor der VPN-Tunnel steht.

Globaler DNS in /etc/docker/daemon.json:

{
  "dns": ["1.1.1.1", "8.8.8.8"]
}

Compose – Patterns

In Compose-Files werden Netzwerke deklarativ definiert – Docker erstellt sie beim ersten up automatisch. Der Compose-Projektname wird als Prefix verwendet (projektname_netzwerkname), sofern kein expliziter name: gesetzt ist.

Isolation mit geteilter API

Das häufigste Pattern: Frontend und Datenbank teilen sich kein Netzwerk – nur die API steht in beiden. Die Datenbank ist vom Internet vollständig abgeschirmt, auch wenn nginx kompromittiert wäre.

services:
  nginx:
    networks: [web]
 
  api:
    networks: [web, internal]
 
  postgres:
    networks: [internal]
 
  redis:
    networks: [internal]
 
networks:
  web:
  internal:

Externes Netzwerk (z.B. Traefik proxy)

Ein Netzwerk das außerhalb des Stacks existiert wird als external: true markiert. Compose erstellt es nicht und löscht es nicht beim down. Fehlt das Netzwerk beim Start, bricht docker compose up sofort ab.

networks:
  proxy:
    name: proxy
    external: true
  internal:
    driver: bridge

Festes Subnetz & Container-IP

Sinnvoll wenn Container-IPs vorhersagbar sein müssen – für Firewall-Regeln, externe Konfigurationen oder wenn FIREWALL_OUTBOUND_SUBNETS in Gluetun auf eine bestimmte IP zeigt.

networks:
  backend:
    driver: bridge
    ipam:
      config:
        - subnet: 172.30.0.0/24
          gateway: 172.30.0.1
 
services:
  postgres:
    networks:
      backend:
        ipv4_address: 172.30.0.10

network_mode Varianten

# Host-Netzwerk
service-a:
  network_mode: host
 
# Netzwerk-Stack eines anderen Containers teilen (→ Gluetun-Pattern)
service-b:
  network_mode: "service:gluetun"
  # Kein ports: Block möglich – Ports nur bei gluetun definieren

Bei network_mode: "service:gluetun" teilen beide Container denselben Netzwerk-Stack vollständig. Alle Ports die service-b nach außen exponieren soll, müssen beim gluetun-Container definiert sein – ein ports:-Block bei service-b selbst wird ignoriert.


Default Address Pools anpassen

Docker wählt Subnetze für neue Netzwerke automatisch aus vordefinierten Pools – standardmäßig aus dem 172.16.0.0/12-Bereich. In Umgebungen wo dieser Bereich bereits für VPNs, interne Netze oder Site-to-Site-Verbindungen vergeben ist, entstehen Routing-Konflikte: Docker-Container können bestimmte interne Ziele nicht mehr erreichen, weil die Route lokal auf den Docker-Bridge zeigt statt nach außen.

Die Lösung ist ein eigener Address Pool der garantiert konfliktfrei ist:

/etc/docker/daemon.json:

{
  "default-address-pools": [
    { "base": "10.100.0.0/16", "size": 24 }
  ]
}

Docker erstellt dann Subnetze 10.100.0.0/24, 10.100.1.0/24 usw. – kein Overlap mit 172.x-Ranges. Nach der Änderung muss der Docker-Daemon neu gestartet werden (systemctl restart docker). Bestehende Netzwerke behalten ihre alten Subnetze – die neue Konfiguration gilt nur für neu erstellte Netzwerke.


Netzwerke inspizieren & aufräumen

docker network inspect ist das wichtigste Debugging-Tool bei Netzwerkproblemen. Es zeigt welche Container hängen wo, welche IPs vergeben sind, und ob das Subnetz wie erwartet konfiguriert ist. Der Go-Template-Filter -f ermöglicht gezielte Abfragen ohne JSON-Parsing per Hand.

docker network ls
docker network inspect my-net
docker network inspect my-net -f '{{json .IPAM.Config}}' | jq .
 
# Welche Container hängen in welchem Netzwerk?
docker network inspect my-net -f '{{range .Containers}}{{.Name}} {{.IPv4Address}}{{"\n"}}{{end}}'
 
# Ungenutzte Netzwerke entfernen – räumt auch verwaiste Netzwerke auf
docker network prune

Verwaiste Netzwerke entstehen oft nach docker compose down ohne --volumes wenn das Compose-File zwischenzeitlich geändert wurde. docker network prune entfernt alle Netzwerke die keinem laufenden Container mehr zugeordnet sind.