Docker-Netzwerke sind der Teil, der Einsteiger am häufigsten überrascht. Container haben eigene Netzwerkschnittstellen, eigene Adressen und eigene Namensauflösung – und welches Netzwerk ein Container nutzt, entscheidet darüber, ob zwei Container sich sehen und ob Dienste von außen erreichbar sind.

Die vier Netzwerktreiber

  • bridge (Standard): Ein eigenes Software-Netz je Docker-Installation. Container bekommen Adressen aus einem internen Bereich (üblicherweise 172.17.0.0/16) und erreichen sich über eigene Namen, wenn sie im selben benutzerdefinierten Netz sind.
  • host: Der Container nutzt die Netzwerkschnittstellen des Hosts direkt. Keine eigene IP, kein Portmapping nötig. Schnellste Variante, aber ohne Isolation – zwei Container können nicht denselben Port belegen.
  • none: Kein Netzwerk außer der eigenen Schleife. Richtig für Aufgaben ohne Netzzugriff und für maximale Isolation.
  • overlay: Für Verbindungen zwischen mehreren Hosts (Docker Swarm). Setzt eine Steuerungsebene voraus.
  • macvlan: Der Container erhält eine eigene MAC-Adresse im physischen Netz – er erscheint dort als eigenes Gerät. Nützlich für Alt-Anwendungen, die eine echte Netzpräsenz erwarten.

Warum benutzerdefinierte Netze fast immer richtig sind

# Das Standard-Netz "bridge" hat zwei grosse Nachteile:
#   1. KEINE Namensauflösung zwischen Containern
#      (der veraltete --link funktioniert nur in eine Richtung)
#   2. Alle Container teilen sich ein Netz

# Benutzerdefiniertes Netz: Namensauflösung inklusive
docker network create app-netz

docker run -d --name db  --network app-netz postgres:16
docker run -d --name api --network app-netz myapi:1.0

# Der Container "api" erreicht die Datenbank jetzt ueber den NAMEN
#   psql -h db -U app           <- "db" wird aufgeloest
#   kein IP-Eintrag noetig, kein Wissen ueber interne Adressen

# Netzwerke ansehen
docker network ls
docker network inspect app-netz
docker network inspect app-netz --format '{{range .Containers}}{{.Name}} {{.IPv4Address}}{{"\n"}}{{end}}'

# Container nachtraeglich verbinden
docker network connect app-netz mein-container
docker network disconnect app-netz mein-container

# Netzwerk mit eigenem Bereich (Kollisionen mit dem Firmennetz vermeiden)
docker network create --subnet 10.99.0.0/24 --gateway 10.99.0.1 intern

Ports: veröffentlichen oder nicht

# Ein Container ist OHNE -p von aussen NICHT erreichbar.
# Die interne IP ist nur auf dem Docker-Host erreichbar.

# Nur fuer den Host sichtbar (empfohlen fuer Datenbanken!)
docker run -d -p 127.0.0.1:5432:5432 postgres:16
#   Achtung: "-p 5432:5432" ohne Adresse = auf ALLEN Schnittstellen,
#   auch im Internet, wenn der Host dort direkt steht!

# Fuer alle erreichbar (nur fuer echte oeffentliche Dienste)
docker run -d -p 80:80 -p 443:443 nginx:alpine

# Beliebiges Host-Port (praktisch zum Testen, unbrauchbar fuer Betrieb)
docker run -d -p 8080 nginx:alpine

# Nur internes Netz, kein Portmapping
docker run -d --network app-netz postgres:16

# Die haeufigste Fehlannahme: "EXPOSE 5432" im Dockerfile
#   EXPOSE veroeffentlicht NICHTS! Es ist nur eine Dokumentation.
#   Erreichbar wird ein Dienst ausschliesslich ueber -p / --publish
#   oder weil ein anderer Container im selben Netz darauf zugreift.

# Ports laufender Container ansehen
docker port mein-container

Der Weg einer Anfrage

Internet
   |
   v
Host-Schnittstelle (z. B. 203.0.113.10:443)
   |
   v  Docker-Proxy-Regel (DNAT)
Container-IP im Bridge-Netz (172.17.0.5:443)

# Folge: Im Container sieht die Anwendung die HOST-Adresse
# (die Docker-Bruecke ist die Gegenstelle), NICHT die echte Client-IP.
# Deshalb sind die Kopfzeilen X-Real-IP / X-Forwarded-For so wichtig,
# und deshalb erscheint alles im Protokoll mit derselben Quelladresse.

# Bei "host"-Netzwerk entfaellt die Uebersetzung - die echte
# Client-Adresse kommt durch. Ein Grund, warum Reverse-Proxies
# haeufig im Host-Netz betrieben werden.

Fehlersuche Schritt für Schritt

# 1. Laeuft der Container ueberhaupt?
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
docker inspect mein-container --format '{{.State.Status}} exit={{.State.ExitCode}}'
docker logs --tail 50 mein-container

# 2. In welchem Netz ist er - und mit welcher Adresse?
docker inspect mein-container --format '{{json .NetworkSettings.Networks}}' | jq
docker inspect mein-container --format '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{$v.IPAddress}}{{"\n"}}{{end}}'

# 3. Laeuft IM Container ein Dienst - und auf welcher Adresse?
#    Der haeufigste Fehler: Der Dienst lauscht nur auf 127.0.0.1!
docker exec mein-container ss -tlnp
#    "127.0.0.1:8080" -> von aussen NICHT erreichbar
#    "0.0.0.0:8080"   -> erreichbar
#    Bei Anwendungen: Host-Bindung auf 0.0.0.0 konfigurieren
#    (nginx: listen 0.0.0.0:80; node: app.listen(8080, '0.0.0.0'))

# 4. Ist die Gegenstelle vom Container aus erreichbar?
docker exec -it mein-container sh -c 'nc -vz db 5432'
docker exec -it mein-container cat /etc/hosts
docker exec -it mein-container getent hosts db
#    Leer -> kein gemeinsames Netzwerk oder falscher Name

# 5. Ist das Portmapping aktiv?
docker port mein-container
curl -v http://127.0.0.1:8080/
#    Verbindungsabbruch -> Dienst lauscht nicht auf 0.0.0.0 (Schritt 3)

# 6. Auf dem Host pruefen
ss -tlnp | grep -E '8080|:80 |:443'
iptables -t nat -L DOCKER -n | head -20     # Docker-Regeln

# 7. Protokoll der Docker-Verwaltung
journalctl -u docker --since '10 min ago' | tail -30

Häufige Fehler

  1. Dienst lauscht auf 127.0.0.1 im Container. Der Klassiker. Der Container selbst funktioniert, ist aber von außen unerreichbar.
  2. Standard-Netz verwendet. Keine Namensauflösung, dadurch hartcodierte IP-Adressen, die sich nach einem Neustart ändern.
  3. -p 5432:5432 statt -p 127.0.0.1:5432:5432. Datenbanken werden versehentlich im Internet erreichbar – eine der häufigsten Ursachen für Datenabflüsse bei Docker.
  4. localhost statt Dienstnamen verwendet. localhost zeigt innerhalb des Containers auf den Container selbst, nicht auf den Host. Für den Host: host.docker.internal (Docker Desktop) oder die Adresse der Brücke.
  5. Subnetz kollidiert. Das Docker-Netz verwendet 172.17.0.0/16. Befindet sich dieser Bereich im Firmennetz, ist er nicht erreichbar. Eigenen Bereich beim Anlegen festlegen.
  6. Firewall auf dem Host blockiert. Docker schreibt eigene Regeln in iptables (Kette DOCKER). Eine nachträglich geladene Firewall-Konfiguration kann diese überschreiben.
  7. IPv6 vergessen. Ohne --ipv6 oder eine passende Konfiguration ist der Dienst über IPv6 nicht erreichbar – und manche Clients versuchen es zuerst.
  8. Zu viele benutzerdefinierte Netze. Jedes Netz ist eine Brücke mit eigenen Regeln. Ein bis zwei Netze je Anwendung genügen: eines für die Außenwelt, eines für die interne Kommunikation.

Muster für einen sauberen Aufbau

docker network create --subnet 10.99.1.0/24 app-intern

# Datenbank: nur intern, kein Portmapping
docker run -d --name db \
  --network app-intern \
  --restart unless-stopped \
  -e POSTGRES_PASSWORD_FILE=/run/secrets/db_pass \
  -v db-daten:/var/lib/postgresql/data \
  postgres:16

# Anwendung: intern, plus Portmapping nur auf die Schleife
docker run -d --name api \
  --network app-intern \
  --restart unless-stopped \
  -p 127.0.0.1:8080:8080 \
  myapi:1.0

# Reverse-Proxy: veroeffentlicht nach aussen, leitet weiter
docker run -d --name proxy \
  --network app-intern \
  --restart unless-stopped \
  -p 80:80 -p 443:443 \
  -v /srv/proxy/conf:/etc/nginx/conf.d:ro \
  nginx:alpine

# Ergebnis:
#   Internet -> Proxy (Port 80/443) -> api:8080 -> db:5432
#   Datenbank und Anwendung sind von aussen NICHT erreichbar.
#   Nur der Proxy ist Angriffsflaeche.

# In der compose-Datei dasselbe Muster
#   services:
#     db:
#       networks: [intern]          # kein "ports"!
#     api:
#       networks: [intern]
#       ports: ["127.0.0.1:8080:8080"]
#     proxy:
#       networks: [intern]
#       ports: ["80:80", "443:443"]

Verwandte Grundlagen: Docker, Docker Compose, Docker-Grundlagen, Firewall.