Docker Compose Netzwerk: DNS, Ports und host mode erklärt
Erfahren Sie, wie Compose das Standardnetzwerk erstellt, Dienste per DNS-Namen verbindet und warum veröffentlichte Ports UFW umgehen können.
Was Compose erstellt, bevor Ihre Anwendung startet
Die Netzwerkfunktion 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 Unklarheiten bei der Netzwerkfunktion von Compose entstehen, weil nicht bekannt ist, dass dieses Standardverhalten bereits vorhanden ist.
Hier ist eine kleine Datei. Speichern Sie sie als compose.yaml in einem Verzeichnis namens shop.
services:
web:
image: nginx:1.27
ports:
- "8080:80"
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: exampleStarten Sie das Projekt und sehen Sie nach, was Docker erstellt hat:
docker compose up -d
docker network lsDie Liste enthält jetzt ein Netzwerk namens shop_default. Compose nennt es <project>_default, und der Projektname entspricht standardmäßig dem Namen des Verzeichnisses in Kleinbuchstaben. Überschreiben Sie ihn mit docker compose -p myproject up -d oder mit einem Top-Level-name: myproject in 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 Datenverkehr wird beim Verlassen auf die Adresse des Hosts umgesetzt.
docker compose down löscht dieses Netzwerk wieder. Deshalb kann ein veralteter Container aus einem alten Projekt ein Netzwerk weiterhin belegen: Docker lehnt den Vorgang 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, lesen Sie zuerst Aufbau der Compose-Datei und Befehle für den Lebenszyklus. Alles Folgende setzt voraus, dass Sie ein Projekt starten und beenden können.
DNS nach Dienstnamen ist der Teil, den Anfänger übersehen
In jedem benutzerdefinierten Netzwerk führt Docker einen integrierten DNS-Server aus, den jeder Container unter 127.0.0.11 erreicht. Er löst Dienstnamen in die aktuellen Containeradressen auf. Daher erreicht web die Datenbank unter dem Hostnamen db und Port 5432, ganz ohne Konfiguration.
docker compose exec web getent hosts dbDas gibt eine Zeile wie 172.18.0.2 db aus. Wenn keine Ausgabe erfolgt, befinden sich die beiden Dienste nicht im selben Netzwerk.
Der häufigste Fehler ist, localhost in der Anwendungskonfiguration zu verwenden. Innerhalb eines Containers bezeichnet localhost diesen Container, 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. Ein Client, der DNS-Einträge dauerhaft zwischenspeichert, bleibt dadurch an einem nicht mehr laufenden Container hängen. Das alte bridge-Netzwerk, das ein einfaches docker run ohne --network verwendet, bietet außerdem überhaupt keine Namensauflösung. Deshalb passt die Dokumentation zu Container-Links aus dem Jahr 2016 nicht zu dem, was Sie sehen.
Sie benötigen ports: nicht, um zwei Dienste zu verbinden
ports: veröffentlicht einen Container-Port auf dem Host. Es gilt für Netzwerkverkehr, der von außerhalb von Docker eintrifft. Mit Datenverkehr zwischen Diensten hat es nichts zu tun. Dieser funktioniert bereits über den gesamten Portbereich im Projektnetzwerk.
Das ports: - "5432:5432", das viele zu ihrem Datenbankdienst hinzufügen, ist daher nutzlos und verursacht ein echtes Sicherheitsrisiko: Es macht Postgres über die öffentliche Schnittstelle des Servers erreichbar. Löschen 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 die Loopback-Schnittstelle 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 erläutert.
expose: dient unter Compose nur der Dokumentation. Es öffnet nichts, weil zwischen Containern im selben Netzwerk nichts blockiert war.
Wann network_mode host geeignet ist und welche Kosten es verursacht
Der Host-Modus entfernt den eigenen Netzwerk-Namespace des Containers. Der Prozess verwendet dann direkt die Schnittstellen des Hosts.
services:
probe:
image: alpine:3.20
network_mode: host
command: sleep infinityDafür gibt es konkrete Gründe. Ein Prozess, der Broadcast- oder Multicast-Datenverkehr im lokalen Netzwerk sehen muss, kann ihn hinter einer Bridge nicht sehen. Das betrifft beispielsweise die Geräteerkennung für einen Medienserver oder eine Heimautomatisierungszentrale. Die Bridge leitet diesen Datenverkehr nicht an den Container weiter. Ein Monitoring-Agent, der die Zähler der Host-Schnittstellen ausliest, benötigt die Schnittstellen des Hosts. Außerdem entfällt der Hop für die Adressübersetzung. 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 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 per Servicename funktioniert in keine Richtung mehr. Der Container ist nicht mit dem Projektnetzwerk verbunden. Daher kann er db nicht auflösen. Die anderen Services können den Container ebenfalls nicht auflösen. Er erreicht diese Services nur über Ports, die auf dem Host veröffentlicht sind, normalerweise über 127.0.0.1.
Die Isolation ist aufgehoben. 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 dem Verhalten eines Pakets, das mit apt installiert wurde. Das hat einen Vorteil: Dieser Datenverkehr 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, wenn Sie ihn aktivieren. Zusätzlich können Container keine Host-IP-Adressen binden. Es werden nur TCP und UDP verarbeitet. Wenn ein Teil Ihres Teams Linux-Server und ein anderer 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 ersetzt er 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, obwohl sich beide auf demselben Server befinden. 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 Verbindung zu einem vorhandenen Netzwerk herzustellen, anstatt ein neues zu erstellen, und es bei docker compose down unverändert zu lassen. Der separate Schlüssel name: ist wichtiger, als es zunächst scheint: Ohne ihn sucht Compose nach einem Netzwerk mit genau dem 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, was die Anwendungsdatei mit internal macht. Die Datenbank befindet sich nur in diesem projektspezifischen Netzwerk. Der Proxy kann sie daher nicht erreichen. Nur app kann darauf zugreifen. Wenn Sie internal: true unter einem Netzwerk hinzufügen, entfernen Sie außerdem vollständig dessen Route zur Außenwelt. Das ist eine gute Standardeinstellung für eine Datenbank. Beachten Sie jedoch einen wichtigen Nachteil, bevor Sie diese Einstellung verwenden: 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 wegen eines Timeouts fehl.
Eine vollständige Anleitung mit Routing-Regeln und Zertifikaten finden Sie unter mehrere Anwendungen hinter einer Traefik-Instanz betreiben.
Veröffentlichte Ports umgehen UFW
Dies ist der Teil der Compose-Netzwerkfunktion, der zu einem Sicherheitsvorfall werden kann. 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. Der curl liefert die Seite trotzdem aus. Nichts ist defekt. Docker schreibt eigene Adressübersetzungs- und Weiterleitungsregeln direkt in iptables. Datenverkehr zu einem veröffentlichten Container-Port wird an den Container weitergeleitet, statt an den Host zugestellt zu werden. Daher durchläuft er nicht die von UFW verwaltete Chain für lokal bestimmten Datenverkehr. Die Regeln von Docker werden außerdem vor denen von UFW abgearbeitet.
Die kurze Lösung besteht darin, nur dort zu veröffentlichen, wo Sie den Port benötigen:
ports:
- "127.0.0.1:8080:80"Dadurch wird die Host-Seite an loopback 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 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 zuerst ab, mit welchem Netzwerk jeder Container tatsächlich verbunden ist:
docker network inspect shop_defaultDer Block Containers führt 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 in einem kurzlebigen Container, der mit demselben Netzwerk verbunden ist. So 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, aber nc fehlschlägt, läuft der Dienst, lauscht jedoch nicht an diesem Port oder lauscht innerhalb des eigenen Containers an 127.0.0.1 statt an 0.0.0.0. Das ist bei Entwicklungsservern häufig der Fall. 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 keinen Rechner 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. Verschieben 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 Servicenamen erreichen?
Sie befinden sich nicht im selben Netzwerk. Compose fügt jeden Service automatisch zu <project>_default hinzu. Sobald Sie einem Service jedoch eine networks:-Liste hinzufügen, ist diese Liste die vollständige Menge seiner Netzwerke. Das Standardnetzwerk wird dann nicht mehr automatisch verwendet. Führen Sie docker network inspect <network> aus und prüfen Sie, ob beide Container im Block Containers angezeigt werden. Prüfen Sie außerdem, dass keiner der Services network_mode: host verwendet. Ein Container im Host-Modus befindet sich in keinem Docker-Netzwerk und kann Servicenamen nicht auflösen.
Muss ich Ports veröffentlichen, damit ein Service 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 Datenbankports ist eine häufige und kostspielige Gewohnheit, weil die Datenbank dadurch an 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 des ausgehenden Datenverkehrs erfolgen automatisch. Host verwendet direkt den Netzwerk-Stack des Hosts für den Container. Dadurch gibt es keine separate Adresse, keine Auflösung über den Servicenamen, keine Portveröffentlichung und keine Isolation von den anderen Listenern des Hosts. Bridge ist die Standardeinstellung und die richtige Wahl, sofern der Prozess nicht die Schnittstellen 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 Services, die miteinander kommunizieren müssen. Compose erstellt oder löscht dieses 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 die Kette, die UFW filtert, nicht. Binden Sie die Host-Seite mit "127.0.0.1:8080:80" an loopback und stellen Sie öffentliche Dienste über einen Reverse Proxy auf den Ports 80 und 443 bereit.