Docker Netzwerke – Verstehen und richtig einsetzen
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.