Zum Inhalt springen
myvpsguide

Container-Netze verstehen: bridge, host, macvlan und wer wen erreicht

Warum Container sich mal per Name finden und mal nicht, was der Unterschied zwischen ports und expose ist, warum Docker an der Firewall vorbeiarbeitet und wie man das alles nachsieht.

veröffentlicht 19.08.2026
4 min lesezeit
fortgeschritten

Container-Netze wirken magisch, bis etwas nicht geht: Ein Container erreicht den anderen nicht, ein Port ist offen, der nicht offen sein sollte, oder ein Dienst funktioniert erst, nachdem man ihn auf host umgestellt hat. Dahinter stecken meistens dieselben vier Muster.

Die Netztypen#

TypWie es aussiehtWofür
bridge (benutzerdefiniert)eigenes Netz, Container finden sich per Nameder Normalfall, was Compose anlegt
bridge (Standard, docker0)eigenes Netz, keine NamensauflösungAltbestand, docker run ohne --network
hostkein eigenes Netz, Container nutzt den Host-StackBroadcast/Discovery, Leistung, auf Kosten der Trennung
macvlan / ipvlanContainer bekommt eine eigene Adresse im LANwenn er im Netz aussehen soll wie ein Gerät
nonegar kein NetzRechenaufgaben ohne Verbindung
Der Unterschied, der die meisten Fragen erklärt

Im Standard-Bridge-Netz gibt es keine Namensauflösung zwischen Containern, ping datenbank scheitert. In einem benutzerdefinierten Netz gibt es sie, und der Containername ist der Hostname. Compose legt immer ein benutzerdefiniertes Netz an. Deshalb funktioniert dasselbe Setup „in Compose“ und scheitert „mit docker run“.

Wer erreicht wen und über welchen Port#

Der zweithäufigste Fehler: Ein Container spricht den veröffentlichten Port des anderen an statt dessen internen.

services:
  db:
    image: postgres:17
    # KEIN ports: — die Datenbank muss von außen gar nicht erreichbar sein
  app:
    image: meineapp
    environment:
      # richtig: Servicename + INTERNER Port
      DATABASE_URL: postgres://app@db:5432/meineapp
    ports:
      - "127.0.0.1:8080:8080"
  • Container zu Container: Servicename und interner Port. Die ports:-Zeile spielt dabei keine Rolle.
  • Host zu Container: nur über ports:.
  • Container zum Host: über host.docker.internal (nicht überall vorhanden) oder die Bridge-Adresse des Hosts.

expose: dokumentiert nur, ports: öffnet tatsächlich. Wer eine Datenbank „für alle Fälle“ veröffentlicht, öffnet sie fürs Internet, siehe nächster Abschnitt.

Docker und die Firewall#

Ein veröffentlichter Port ignoriert ufw

Docker trägt seine Weiterleitungsregeln so ein, dass sie vor den Regeln von ufw greifen. Ein ports: "5432:5432" ist damit aus dem Internet erreichbar, obwohl ufw status den Port als gesperrt anzeigt. Das ist kein Fehler, sondern Absicht und die Ursache für unzählige offene Datenbanken.

Zwei Wege, beide richtig, am besten zusammen:

# 1. Nur an localhost binden — der wirksamste Schutz
ports:
  - "127.0.0.1:5432:5432"
# 2. Eigene Regeln in die dafür vorgesehene Kette hängen
nft list ruleset | grep -A5 DOCKER-USER   # bzw. iptables -L DOCKER-USER -n

Und danach von außen gegenprüfen, vom eigenen Rechner aus, nicht vom Server:

nmap -Pn -p 5432,8080,11434 <server-ip>

Der Rest zum Regelwerk steht in Firewall mit nftables, der Aufbau produktiver Compose-Dateien in Docker Compose produktiv.

macvlan: wenn der Container ein Gerät sein soll#

Manche Dienste wollen im LAN sichtbar sein, Netzwerkerkennung, DHCP-Dienste, Medienserver mit Auto-Discovery. Dafür gibt es macvlan:

docker network create -d macvlan \
  --subnet=192.168.1.0/24 --gateway=192.168.1.1 \
  -o parent=eth0 lan

Zwei Einschränkungen, die man vorher kennen sollte: Der Host erreicht seinen eigenen macvlan-Container nicht (dafür braucht es eine zusätzliche Schnittstelle), und viele virtuelle Umgebungen und WLAN-Adapter erlauben mehrere MAC-Adressen an einem Port nicht. Auf einem VPS ist macvlan deshalb selten die richtige Antwort, im Heimnetz häufiger.

Podman: fast gleich, an zwei Stellen anders#

Podman verhält sich im Wesentlichen wie Docker, mit zwei relevanten Unterschieden:

  1. Rootless veröffentlicht anders. Ohne Root-Rechte läuft der Verkehr durch einen Benutzerprozess (pasta bzw. slirp4netns). Folge: Der Container sieht als Absenderadresse oft nicht die echte Client-IP, für Protokolle und Sperrlisten relevant.
  2. Ports unter 1024 darf ein unprivilegierter Benutzer nicht belegen:
sysctl -w net.ipv4.ip_unprivileged_port_start=80   # oder Reverse-Proxy davor

Die Namensauflösung zwischen Containern übernimmt bei Podman ein eigener kleiner DNS-Dienst, sie funktioniert ebenfalls nur in benutzerdefinierten Netzen. Mehr dazu in Podman ohne Root.

Fehlersuche: die fünf Befehle#

# 1. Welche Netze gibt es, wer hängt drin?
docker network ls
docker network inspect <netz> | jq '.[0].Containers'

# 2. Findet der Container den anderen überhaupt?
docker compose exec app getent hosts db

# 3. Ist der Port im Container offen — oder lauscht der Dienst auf 127.0.0.1?
docker compose exec db ss -tlnp

# 4. Was ist von außen offen?
ss -tlnp | grep docker

# 5. Kommt etwas an? (im Netz-Namensraum des Containers mitschneiden)
nsenter -t $(docker inspect -f '{{.State.Pid}}' <container>) -n ss -tlnp

Punkt 3 ist der Klassiker: Ein Dienst im Container bindet auf 127.0.0.1 des Containers. Dann ist er von außerhalb des Containers unerreichbar, egal wie viele Ports veröffentlicht sind. Die Lösung steht in der Konfiguration des Dienstes, nicht in Docker.

MTU: der Fehler, der wie ein Serverproblem aussieht#

Läuft der Host in einem Tunnel oder VPN, ist die nutzbare Paketgröße kleiner. Container erben davon nichts und schicken zu große Pakete: Verbindungen kommen zustande und bleiben dann mitten in der Übertragung stehen.

networks:
  default:
    driver: bridge
    driver_opts:
      com.docker.network.driver.mtu: 1400
# Gegenprobe: größte Paketgröße ohne Fragmentierung
docker compose exec app ping -M do -s 1372 8.8.8.8

Häufige Fragen#

Warum funktioniert localhost im Container nicht?#

Weil localhost im Container der Container selbst ist, nicht der Host. Für den Host nimmst du dessen Bridge-Adresse oder host.docker.internal, für andere Container deren Servicenamen.

Ist network_mode: host schlimm?#

Es hebt die Netztrennung auf: Der Container sieht alle Schnittstellen und kann jeden Port belegen. Für einzelne Dienste mit gutem Grund vertretbar, als Standardlösung ein Rückschritt.

Wie erreiche ich einen Container aus dem LAN?#

Über einen veröffentlichten Port am Host und, wenn nötig, einen Reverse-Proxy davor (Caddy oder nginx). macvlan nur, wenn der Container wirklich ein eigenes Gerät sein soll.

Brauche ich mehrere Netze in Compose?#

Sobald ein Dienst ins Internet soll und ein anderer nicht. Zwei Netze, eins für den Proxy, eins für Anwendung und Datenbank, sind der einfachste wirksame Schnitt.

Und IPv6?#

Docker beherrscht es, hat es aber lange nicht standardmäßig aktiviert. Wer es braucht, aktiviert es bewusst und prüft dann die Firewall-Regeln für beide Familien (IPv6 auf dem VPS).

Kurz gesagt#

  • Namensauflösung gibt es nur im benutzerdefinierten Netz, Compose legt so eins an.
  • Container zu Container: Servicename plus interner Port, nie der veröffentlichte.
  • ports: ohne IP-Präfix umgeht ufw. Immer 127.0.0.1: davor, außer es soll wirklich öffentlich sein.
  • Dienst lauscht im Container auf 127.0.0.1? Dann hilft kein veröffentlichter Port.
  • Verbindungen bleiben mitten drin stehen: MTU prüfen.
MRMedia

Geschrieben aus dem laufenden Betrieb: MRMedia betreibt eigene Proxmox- Hosts, einen Backup-Server und Kundendienste in der EU. Fehler in einer Anleitung sind Fehler im eigenen Betrieb. Korrekturen bitte über Discord.