Docker Compose Netzwerk: DNS, Host-Modus und Ports
Erfahren Sie, wie das Compose-Bridge-Netzwerk funktioniert, warum DNS über Dienstnamen aufloest, wann host sinnvoll ist und warum veröffentlichte Ports UFW umgehen.
Was Compose vor dem Start Ihrer Anwendung erstellt
Die Netzwerkkonfiguration von Docker Compose beginnt mit einer Regel: docker compose up erstellt ein privates Netzwerk für das Projekt, verbindet jeden Dienst damit und ermöglicht den Diensten, sich über den Dienstnamen zu erreichen. Dafür müssen Sie keine einzige networks:-Zeile schreiben. Die meisten Missverständnisse bei der Compose-Netzwerkkonfiguration entstehen, weil nicht bekannt ist, dass dieses Standardnetzwerk bereits vorhanden ist.
Hier ist eine kleine Datei. Speichern Sie sie in einem Verzeichnis namens shop als compose.yaml.
services:
web:
image: nginx:1.27
ports:
- "8080:80"
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: exampleStarten Sie das Projekt und prüfen Sie, was Docker erstellt hat:
docker compose up -d
docker network lsDie Liste enthält jetzt ein Netzwerk namens shop_default. Compose benennt es als <project>_default. Der Projektname entspricht standardmäßig dem kleingeschriebenen Verzeichnisnamen. Überschreiben Sie ihn mit docker compose -p myproject up -d oder mit einem Eintrag name: myproject auf der obersten Ebene der Datei. Der Treiber ist bridge. Dabei handelt es sich um einen virtuellen Switch innerhalb des Hosts. Jeder Container erhält eine Adresse in einem privaten Subnetz. Ausgehender Netzwerkverkehr wird auf dem Weg nach außen in die Adresse des Hosts übersetzt.
docker compose down löscht dieses Netzwerk wieder. Deshalb kann ein veralteter Container aus einem alten Projekt ein Netzwerk weiterhin belegen: Docker bricht mit error while removing network: network shop_default has active endpoints ab. Beenden oder entfernen Sie den Container, der noch mit dem Netzwerk verbunden ist.
Wenn Compose für Sie neu ist, sollten Sie zuerst Aufbau von Compose-Dateien und Befehle für den Lebenszyklus lesen. Alle folgenden Abschnitte setzen voraus, dass Sie ein Projekt starten und beenden können.
DNS über den Dienstnamen ist der Teil, den Einsteiger übersehen
In jedem benutzerdefinierten Netzwerk führt Docker einen eingebetteten DNS-Server aus, den jeder Container unter 127.0.0.11 erreicht. Er löst Dienstnamen in die aktuellen Container-Adressen auf. Daher erreicht web die Datenbank unter dem Hostnamen db auf Port 5432, ganz ohne zusätzliche Konfiguration.
docker compose exec web getent hosts dbDas gibt eine Zeile wie 172.18.0.2 db aus. Wenn keine Ausgabe erscheint, befinden sich die beiden Dienste nicht im selben Netzwerk.
Der Fehler, den fast jeder einmal macht, ist die Verwendung von localhost in der Anwendungskonfiguration. Innerhalb eines Containers bezeichnet localhost diesen Container selbst, nicht den Host und nicht den anderen Dienst. Postgres-Clients melden das eindeutig:
could not connect to server: Connection refused
Is the server running on host "localhost" (127.0.0.1) and accepting
TCP connections on port 5432?Die Verbindungszeichenfolge muss postgresql://postgres:example@db:5432/postgres lauten. Der Hostteil ist der Dienstname.
Zwei Details sparen später Zeit. Namen werden in die jeweils aktuell laufenden Container aufgelöst. Daher liefert docker compose up -d --scale web=3 einen Namen mit drei Adressen, während ein Client, der DNS-Einträge dauerhaft zwischenspeichert, an einem nicht mehr verfügbaren Container festhält. Außerdem bietet das veraltete Netzwerk bridge, das von einem einfachen docker run ohne --network verwendet wird, überhaupt keine Namensauflösung. Deshalb passen Hinweise zu Container-Links aus dem Jahr 2016 nicht zu dem, was Sie sehen.
Sie benötigen ports: nicht, um zwei Dienste miteinander zu verbinden
ports: veröffentlicht einen Container-Port auf dem Host. Das gilt für Netzwerkverkehr, der von außerhalb von Docker eintrifft. Mit dem Datenverkehr zwischen Diensten hat das nichts zu tun. Dieser funktioniert im gesamten Portbereich des Projektnetzwerks bereits.
Das ports: - "5432:5432", das viele zu ihrem Datenbankdienst hinzufügen, ist daher nutzlos und sogar schädlich: Es macht Postgres über die öffentliche Schnittstelle des Servers erreichbar. Entfernen Sie es. Wenn der Dienst für eine Migration von Ihrem Laptop aus erreichbar sein soll, binden Sie ihn mit "127.0.0.1:5432:5432" an das Loopback-Interface und greifen Sie über einen SSH-Tunnel darauf zu. Der Unterschied zwischen einem lauschenden Socket, einem veröffentlichten Port und einer Firewall-Regel wird unter Funktionsweise von Ports und lauschenden Diensten unter Linux erklärt.
expose: dient unter Compose nur der Dokumentation. Es öffnet nichts, weil zwischen Containern im selben Netzwerk nichts geschlossen war.
Wann network_mode host sinnvoll ist und welche Kosten damit verbunden sind
Der Host-Modus entfernt den eigenen Netzwerk-Namespace des Containers. Der Prozess kann dadurch die Schnittstellen des Hosts direkt verwenden.
services:
probe:
image: alpine:3.20
network_mode: host
command: sleep infinityDafür gibt es konkrete Gründe. Ein Prozess, der Broadcast- oder Multicast-Verkehr im lokalen Netzwerk sehen muss, kann diesen hinter einer Bridge nicht empfangen. Das betrifft beispielsweise die Geräteerkennung für einen Medienserver oder eine Smart-Home-Zentrale. Die Bridge leitet diesen Verkehr nicht an den Container weiter. Ein Monitoring-Agent, der die Interface-Zähler des Hosts ausliest, benötigt ebenfalls die Schnittstellen des Hosts. Außerdem entfällt der Adressübersetzungsschritt. Das ist bei hohen Paketraten relevant.
Die Nachteile sind spezifisch.
ports: funktioniert nicht mehr. Docker weist darauf hin, dass veröffentlichte Ports im Host-Netzwerkmodus verworfen werden. Der Container bindet stattdessen an die Ports, an die sein Prozess bindet. Zwei Container im Host-Modus, die Port 8080 verwenden wollen, kollidieren. Der zweite Container beendet sich mit bind: address already in use.
Die Namensauflösung über den Dienstnamen entfällt in beide Richtungen. Der Container ist nicht mit dem Projektnetzwerk verbunden. Deshalb kann er db nicht auflösen, und die anderen Dienste können ihn ebenfalls nicht auflösen. Er erreicht diese Dienste nur über Ports, die auf dem Host veröffentlicht sind, normalerweise unter 127.0.0.1.
Die Isolation entfällt. Ein Prozess, der innerhalb eines Containers im Host-Modus an 0.0.0.0 bindet, lauscht auf jeder Schnittstelle des Servers, einschließlich der öffentlichen Schnittstelle. Das entspricht einem Paket, das mit apt installiert wurde. Das hat einen Vorteil: Dieser Verkehr durchläuft den normalen Eingabepfad. Daher gelten dafür die UFW-Regeln. Das ist bei veröffentlichten Ports nicht der Fall.
Der Host-Modus ist eine Funktion der Linux Docker Engine. Docker Desktop unterstützt ihn erst ab Version 4.34 und nur nach dessen Aktivierung. Zusätzlich können Container keine Host-IP-Adressen binden. Außerdem werden nur TCP und UDP verarbeitet. Wenn ein Teil Ihres Teams Linux-Server und der andere Teil Docker Desktop verwendet, müssen Sie damit rechnen, dass sich dieselbe Datei unterschiedlich verhält.
Verwenden Sie den Host-Modus, wenn Sie die Schnittstellen des Hosts benötigen. Verwenden Sie ihn nicht, um ein Verbindungsproblem zu beheben. Meist ersetzen Sie damit ein Problem durch ein schwierigeres.
Zwei Compose-Projekte über ein externes Netzwerk verbinden
Ein von einem Projekt erstelltes Netzwerk ist für ein anderes Projekt nicht sichtbar. Deshalb kann ein Reverse Proxy in proxy/compose.yaml keine Anwendung in app/compose.yaml erreichen, selbst wenn beide auf demselben Server laufen. Die Lösung ist ein Netzwerk, das keinem der beiden Projekte gehört.
Erstellen Sie es einmal manuell:
docker network create edgeDeklarieren Sie es anschließend in jedem Projekt als externes Netzwerk. Die Proxy-Seite:
services:
proxy:
image: traefik:v3.1
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- edge
networks:
edge:
name: edge
external: trueDie Anwendungsseite:
services:
app:
image: nginx:1.27
networks:
- edge
- internal
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: example
networks:
- internal
networks:
edge:
name: edge
external: true
internal:external: true weist Compose an, eine vorhandene Netzwerkverbindung zu verwenden, statt eine neue zu erstellen, und sie auf docker compose down bestehen zu lassen. Der separate Schlüssel name: ist wichtiger, als es zunächst scheint: Ohne ihn sucht Compose nach einem Netzwerk mit dem exakten Namen edge. Mit ihm können Sie das Netzwerk in Ihrer Datei anders nennen als auf dem Host.
Wenn das Netzwerk nicht vorhanden ist, verweigert Compose den Start und meldet, dass das Netzwerk als extern deklariert wurde, aber nicht gefunden werden konnte. Erstellen Sie es zuerst.
Beachten Sie, wie die Anwendungsdatei mit internal umgeht. Die Datenbank befindet sich ausschließlich in diesem projektspezifischen Netzwerk. Der Proxy kann sie daher nicht erreichen, sondern nur app. Wenn Sie internal: true unter einem Netzwerk ergänzen, wird die Route nach außen vollständig entfernt. Das ist eine sinnvolle Standardeinstellung für eine Datenbank. Beachten Sie jedoch die Konsequenz, bevor Sie sie festlegen: Ein Container in einem internen Netzwerk kann nichts herunterladen. Ein Entrypoint, der beim Start apt-get update oder pip install ausführt, hängt daher und schlägt anschließend mit einem Timeout fehl.
Eine vollständige Beispielkonfiguration mit Routing-Regeln und Zertifikaten finden Sie unter Mehrere Anwendungen hinter einer Traefik-Instanz betreiben.
Veröffentlichte Ports umgehen UFW
Dieser Teil der Compose-Netzwerkfunktion führt zu einem Sicherheitsvorfall. Sie veröffentlichen einen Port, prüfen, dass UFW aktiv ist und alles außer SSH ablehnt, und der Dienst ist trotzdem aus dem Internet erreichbar.
sudo ufw status
curl http://203.0.113.10:8080UFW meldet, dass der Port blockiert ist. curl liefert die Seite trotzdem aus. Nichts ist defekt. Docker schreibt eigene Regeln für Adressübersetzung und Weiterleitung direkt in iptables. Zugriffe auf einen veröffentlichten Container-Port werden an den Container weitergeleitet, statt an den Host zugestellt zu werden. Daher durchlaufen sie nicht die von UFW verwaltete Chain für lokal bestimmte Zugriffe. Die Regeln von Docker werden außerdem vor den UFW-Regeln abgeglichen.
Die kurze Lösung besteht darin, Ports nur dort zu veröffentlichen, wo sie benötigt werden:
ports:
- "127.0.0.1:8080:80"Dadurch wird die Host-Seite an die Loopback-Schnittstelle gebunden. Der Port ist dann vom Server selbst und über einen SSH-Tunnel erreichbar, aber von keinem anderen Ort. Platzieren Sie den öffentlichen Einstiegspunkt hinter einem Reverse Proxy, der Port 80 und 443 gezielt veröffentlicht. Die vollständige Erklärung einschließlich der DOCKER-USER-Chain für Fälle, in denen Sie einen veröffentlichten Port filtern müssen, finden Sie unter warum Docker Ports direkt an UFW vorbei veröffentlicht und wie Sie das beheben.
So debuggen Sie das Problem mit vier Befehlen
Fragen Sie zunächst ab, in welchem Netzwerk sich jeder Container tatsächlich befindet:
docker network inspect shop_defaultDer Block Containers listet alle verbundenen Container mit ihrer Adresse auf. Fehlt ein Dienst in dieser Liste, befindet er sich in einem anderen Netzwerk, im Host-Modus oder läuft nicht.
Testen Sie die Namensauflösung aus einem kurzlebigen Container, der mit demselben Netzwerk verbunden ist. Dadurch benötigen Sie keine zusätzlichen Werkzeuge in Ihren eigenen Images:
docker run --rm --network shop_default busybox nslookup db
docker run --rm --network shop_default busybox nc -zv db 5432Ein Fehlschlag von nslookup weist auf ein Problem mit der Namensauflösung oder der Netzwerkzugehörigkeit hin. Wenn nslookup erfolgreich ist, während nc fehlschlägt, läuft der Dienst zwar, lauscht aber nicht auf diesem Port oder lauscht innerhalb seines eigenen Containers auf 127.0.0.1 statt auf 0.0.0.0. Das kommt bei Entwicklungsservern häufig vor. Die Korrektur erfolgt in der Bind-Adresse der Anwendung, nicht in Docker.
Ein weiterer Fehler wirkt wie ein Docker-Problem. Wenn Container miteinander kommunizieren können, aber keine Maschine in Ihrem Büro- oder VPN-Netzwerk erreichen, überschneidet sich das Docker-Subnetz wahrscheinlich mit diesem Netzwerk. Docker verwendet standardmäßig Adressen ab 172.17.0.0/16. Ändern Sie den Pool in /etc/docker/daemon.json:
{
"default-address-pools": [
{ "base": "10.200.0.0/16", "size": 24 }
]
}Führen Sie anschließend sudo systemctl restart docker aus und erstellen Sie die betroffenen Netzwerke neu, da ein vorhandenes Netzwerk das Subnetz behält, mit dem es erstellt wurde.
FAQ
Warum können meine Container sich nicht über den Dienstnamen erreichen?
Sie befinden sich nicht im selben Netzwerk. Compose fügt jeden Dienst automatisch zu <project>_default hinzu. Sobald Sie einem Dienst jedoch eine networks:-Liste hinzufügen, ist diese Liste die vollständige Menge seiner Netzwerke. Das Standardnetzwerk wird dann nicht mehr implizit ergänzt. Führen Sie docker network inspect <network> aus und prüfen Sie, ob beide Container im Block Containers erscheinen. Prüfen Sie außerdem, dass keiner der Dienste network_mode: host verwendet. Ein Container im Host-Modus ist mit keinem Docker-Netzwerk verbunden und kann keine Dienstnamen auflösen.
Muss ich Ports veröffentlichen, damit ein Dienst einen anderen erreicht?
Nein. In einem Compose-Netzwerk ist jeder Port jedes Containers für die anderen Container in diesem Netzwerk erreichbar. ports: dient ausschließlich dazu, einen Container für Datenverkehr außerhalb von Docker bereitzustellen. expose: dient der Dokumentation. Das Veröffentlichen eines Datenbank-Ports ist eine häufige und riskante Gewohnheit, weil die Datenbank dadurch auf der öffentlichen Schnittstelle Ihres Servers erreichbar ist.
Was ist der Unterschied zwischen Bridge- und Host-Netzwerk?
Bridge gibt dem Container einen eigenen Netzwerk-Namespace und eine eigene Adresse auf einem virtuellen Switch. Die Namensauflösung zwischen Containern und die Übersetzung ausgehenden Datenverkehrs erfolgen automatisch. Host verwendet direkt den Netzwerk-Stack des Hosts für den Container: Es gibt keine separate Adresse, keine Auflösung über den Dienstnamen, keine Port-Veröffentlichung und keine Isolierung von den anderen Listenern des Hosts. Bridge ist die Standardeinstellung und die richtige Wahl, sofern der Prozess nicht die Netzwerkschnittstellen des Hosts benötigt.
Wie verbinde ich Container aus zwei verschiedenen Compose-Dateien?
Erstellen Sie mit docker network create edge ein gemeinsames Netzwerk. Deklarieren Sie es anschließend in beiden Dateien mit external: true und verbinden Sie die Dienste, die miteinander kommunizieren müssen, mit diesem Netzwerk. Compose erstellt oder löscht das Netzwerk nicht. Wenn Sie den Erstellungsschritt überspringen, verweigert Compose den Start und meldet, dass das Netzwerk als extern deklariert, aber nicht gefunden wurde.
Warum ist mein Container aus dem Internet erreichbar, obwohl UFW den Port blockiert?
Weil ein veröffentlichter Port über die Weiterleitungsregeln verarbeitet wird, die Docker zu iptables hinzufügt. Diese Regeln werden vor den UFW-Regeln ausgewertet. Außerdem durchläuft weitergeleiteter Datenverkehr nicht die Chain, die UFW filtert. Binden Sie die Host-Seite mit "127.0.0.1:8080:80" an das Loopback-Interface und stellen Sie öffentliche Dienste über einen Reverse Proxy auf den Ports 80 und 443 bereit.