1. Home
  2. Linux
  3. Docker
  4. Port-Mapping und Netzwerkzugriffe

Port-Mapping und Netzwerkzugriffe

Lesedauer: 23 Minuten

Docker-Container besitzen in einem Bridge-Netzwerk eigene IP-Adressen und können darüber mit anderen Containern sowie dem Docker-Host kommunizieren. Diese interne Erreichbarkeit bedeutet jedoch nicht automatisch, dass ein Containerdienst auch von anderen Rechnern im lokalen Netzwerk erreichbar ist.

Um einen solchen Zugriff zu ermöglichen, stellt Docker das sogenannte Port-Mapping bereit. Dabei wird ein TCP- oder UDP-Port des Docker-Hosts mit einem Port innerhalb des Containers verknüpft. Beispielsweise lässt sich ein nginx-Webserver, der im Container auf TCP-Port 80 lauscht, über Port 8081 des Docker-Hosts bereitstellen.

Welche Verbindungen dadurch tatsächlich möglich sind, hängt allerdings nicht allein von der Portzuordnung ab. Auch die Netzwerkanbindung des Containers, die gebundene Host-Adresse sowie die von Docker eingerichteten NAT- und Firewallregeln spielen eine maßgebliche Rolle.

Dieser Artikel untersucht das Zusammenspiel anhand praktischer Beispiele in einer Docker-Umgebung unter Debian 13. Dabei werden unterschiedliche Zugriffswege getestet und die relevanten Netzwerk- und Firewallregeln unmittelbar den beobachteten Ergebnissen gegenübergestellt.

Container-Ports und veröffentlichte Host-Ports

Ein Webserver innerhalb eines Docker-Containers unterscheidet sich hinsichtlich seiner Netzwerkkommunikation zunächst nicht wesentlich von einem Webserver auf einem gewöhnlichen Linux-System. Er lauscht auf einer bestimmten IP-Adresse und einem TCP-Port. Bei nginx ist dies üblicherweise Port 80.

In einem Docker-Bridge-Netzwerk erhält der Container eine eigene IP-Adresse. Über diese kann der Dienst vom Docker-Host und von anderen Containern im selben Netzwerk direkt angesprochen werden. Soll der Webserver dagegen über eine IP-Adresse des Docker-Hosts erreichbar sein, muss der entsprechende Port veröffentlicht werden.

Port-Mapping mit der Option -p

Docker stellt hierfür beim Start eines Containers die Option -p beziehungsweise --publish bereit. Die grundlegende Syntax lautet:
p HOST_PORT:CONTAINER_PORT

Beispielsweise veröffentlicht die folgende Option den internen TCP-Port 80 auf Port 8081 des Docker-Hosts:
-p 8081:80

Die einzelnen Bestandteile lassen sich folgendermaßen zuordnen:

BestandteilBedeutung
-pVeröffentlicht einen Container-Port auf dem Docker-Host
8081Port, über den der Dienst auf dem Host erreichbar ist
80Port, auf dem der Dienst innerhalb des Containers lauscht
tcpVerwendetes Transportprotokoll; TCP ist der Standard, UDP muss ausdrücklich angegeben werden

Ohne weitere Angabe einer Host-IP veröffentlicht Docker den Port in der Standardkonfiguration auf allen Host-Adressen. Eine gezielte Beschränkung auf bestimmte Adressen ist ebenfalls möglich und wird später untersucht.

Praktisches Beispiel: nginx im Bridge-Netzwerk

In der Docker-Testumgebung läuft der Container academy-bind-nginx-01. Er besitzt die interne IPv4-Adresse 172.17.0.3 und stellt seinen nginx-Webserver auf TCP-Port 80 bereit.

Die Veröffentlichung lässt sich mit folgendem Befehl überprüfen:

$ sudo docker ps --format "table {{.Names}}\t{{.Ports}}"
NAMES                     PORTS
academy-cont-alpine-02
academy-cont-alpine-01
academy-volume-nginx-02   0.0.0.0:8083->80/tcp, [::]:8083->80/tcp
academy-volume-nginx-01   0.0.0.0:8082->80/tcp, [::]:8082->80/tcp
academy-bind-nginx-01     0.0.0.0:8081->80/tcp, [::]:8081->80/tcp
diag-nginx-01             0.0.0.0:8080->80/tcp, [::]:8080->80/tcp

Die Ausgabe zeigt, dass der Host-Port 8081 sowohl für alle IPv4-Adressen (0.0.0.0) als auch für alle IPv6-Adressen ([::]) veröffentlicht wurde. Die tatsächliche Erreichbarkeit hängt zusätzlich von den Netzwerk- und Firewallregeln ab. Insbesondere lässt sich aus der IPv6-Portveröffentlichung allein noch nicht auf den internen Weiterleitungsmechanismus schließen.

Der Webserver ist damit grundsätzlich über zwei unterschiedliche Adressierungswege erreichbar:

ZugriffZieladresseZielportPort-Mapping erforderlich
Direkt über das Docker-Netzwerk172.17.0.380Nein
Über die LAN-Adresse des Docker-Hosts192.168.178.1198081Ja

Beide Zugriffswege führen zum selben nginx-Webserver. Der wesentliche Unterschied liegt darin, dass der direkte Zugriff über die Container-IP keine Portumsetzung benötigt. Beim Zugriff über die Host-Adresse muss Docker dagegen die Verbindung dem richtigen Container und dessen internem Port zuordnen.

Port-Mapping ermöglicht den Zugriff auf einen Containerdienst über einen veröffentlichten Port des Docker-Hosts.

Wie Docker diese Zuordnung mithilfe von NAT-Regeln und gegebenenfalls einem zusätzlichen Proxy realisiert, wird anhand der folgenden Netzwerkversuche untersucht.

Zugriff auf Containerdienste über unterschiedliche Netzwerkwege

Ein veröffentlichter Containerdienst kann auf verschiedenen Wegen erreicht werden. Entscheidend ist, welche Zieladresse und welchen Port ein Client verwendet. Dabei muss zwischen dem direkten Zugriff über die Container-IP und dem Zugriff über einen veröffentlichten Host-Port unterschieden werden.

Direkter Zugriff über die Container-IP

Der Docker-Host ist über seine Schnittstelle docker0 direkt mit dem Standard-Bridge-Netzwerk 172.17.0.0/16 verbunden. Der nginx-Container mit der IP-Adresse 172.17.0.3 kann deshalb vom Host unmittelbar auf TCP-Port 80 angesprochen werden:

$ curl -I http://172.17.0.3:80
HTTP/1.1 200 OK
---snip---

Der Webserver antwortet mit HTTP/1.1 200 OK. Für diese Verbindung wird kein Port-Mapping benötigt. Der Linux-Kernel erreicht das Container-Netzwerk über seine direkt angeschlossene Bridge-Schnittstelle.

Zugriff über den veröffentlichten Host-Port

Alternativ kann derselbe Webserver über den veröffentlichten Port 8081 erreicht werden:

$ curl -I http://127.0.0.1:8081
HTTP/1.1 200 OK
---snip---

Auch dieser Aufruf liefert HTTP/1.1 200 OK. Diesmal erfolgt der Zugriff jedoch nicht direkt auf die Container-IP. Die Verbindung wird stattdessen über die Portveröffentlichung von Docker zum nginx-Webserver weitergeleitet.

127.0.0.1 bezeichnet hier die Loopback-Adresse des Docker-Hosts, weil der curl-Befehl auf dem Host ausgeführt wird – nicht die Loopback-Adresse des Containers.

Da Docker den Port 8081 auf allen IPv4-Adressen des Hosts veröffentlicht hat, ist außerdem der Zugriff über dessen LAN-Adresse möglich:

$ curl -I http://192.168.178.119:8081
HTTP/1.1 200 OK
---snip---

Auch ein anderer Rechner im selben LAN kann über diese Adresse auf den nginx-Webserver zugreifen, sofern die Netzwerk- und Firewallregeln dies zulassen.

Die Ergebnisse der vier Zugriffswege lassen sich folgendermaßen gegenüberstellen:

AusgangspunktZieladressePortErgebnis
Docker-Host172.17.0.380HTTP 200 OK
Docker-Host127.0.0.18081HTTP 200 OK
Docker-Host192.168.178.1198081HTTP 200 OK
LAN-Client*192.168.178.1198081HTTP 200 OK
* Nicht in der Versuchsanordnung enthalten

Alle vier Versuche erreichen denselben nginx-Webserver. Allerdings unterscheidet sich die technische Verarbeitung der Verbindungen.

Der direkte Zugriff über die Container-IP benötigt lediglich eine passende Route und die erforderliche Netzwerkfreigabe. Bei der Portveröffentlichung muss Docker dagegen die Verbindung vom Host-Port zum internen Container-Port weiterleiten.

Unter Linux geschieht dies abhängig vom Verbindungsweg insbesondere durch Destination Network Address Translation (DNAT) oder den zusätzlichen Prozess docker-proxy. Beide Mechanismen werden im folgenden Abschnitt anhand der tatsächlich eingerichteten Docker-Regeln untersucht.

Wie Docker die Portweiterleitung technisch umsetzt

Beim Zugriff auf einen veröffentlichten Container-Port muss Docker die eingehende Verbindung dem richtigen Container zuordnen. Unter Linux erfolgt dies üblicherweise mithilfe von NAT-Regeln im Netzwerkstack. Abhängig von der Konfiguration kann zusätzlich der Prozess docker-proxy zum Einsatz kommen. Welche Mechanismen tatsächlich verwendet werden, lässt sich am Regelwerk des Docker-Hosts nachvollziehen.

Die DNAT-Regel für den nginx-Container

Auf dem Debian-13-Host liefert die Abfrage der NAT-Tabelle:

$ sudo iptables -t nat -S
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N DOCKER
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.18.0.0/16 ! -o br-48723e8dc020 -j MASQUERADE
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 8080 -j DNAT --to-destination 172.17.0.2:80
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 8081 -j DNAT --to-destination 172.17.0.3:80
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 8082 -j DNAT --to-destination 172.17.0.4:80
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 8083 -j DNAT --to-destination 172.17.0.5:80
pronto@ps-docker:~$

Die Regel -A DOCKER ! -i docker0 -p tcp -m tcp --dport 8081 -j DNAT --to-destination 172.17.0.3:80 gehört zur Chain DOCKER innerhalb der NAT-Tabelle. Sie beschreibt, unter welchen Bedingungen Docker die Zieladresse und den Zielport eines Pakets verändert.

ParameterBedeutung
-A DOCKERDie Regel befindet sich in der Chain DOCKER.
! -i docker0Das Paket darf nicht über die Schnittstelle docker0 eingegangen sein. Das Ausrufezeichen negiert die Bedingung.
-p tcpDie Regel gilt für TCP-Pakete.
-m tcpAktiviert die TCP-spezifischen Prüfkriterien.
--dport 8081Der TCP-Zielport muss 8081 sein.
-j DNATFührt eine Zieladressübersetzung durch (Destination NAT).
--to-destination 172.17.0.3:80Ersetzt das ursprüngliche Ziel durch die Container-IP 172.17.0.3 und Port 80.

Ein Client adressiert seine HTTP-Anfrage beispielsweise an 192.168.178.119:8081. Durch DNAT verändert der Linux-Kernel das Ziel des Pakets zu 172.17.0.3:80. Die Quelladresse des Clients wird durch diese Regel nicht verändert.

PREROUTING und die Routingentscheidung

Damit eingehende Pakete überhaupt zur Docker-NAT-Chain gelangen, existiert auf dem Host folgende Regel:

-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER

Auch ihre Bestandteile lassen sich einzeln erklären:

ParameterBedeutung
-A PREROUTINGDie Regel befindet sich in der Chain PREROUTING der NAT-Tabelle.
-m addrtypeAktiviert die Prüfung des Adresstyps.
--dst-type LOCALDie Zieladresse gehört zu den lokalen Adressen des Hosts.
-j DOCKERÜbergibt die Verarbeitung an die Chain DOCKER.

Die Chain PREROUTING wird von eingehenden Paketen vor der Routingentscheidung durchlaufen. Docker kann dadurch die Zieladresse bereits verändern, bevor der Kernel entscheidet, ob ein Paket lokal zugestellt oder weitergeleitet werden soll.

Im vorliegendem nginx-Beispiel wird aus dem ursprünglichen Ziel 192.168.178.119:8081 das neue Ziel 172.17.0.3:80. Da diese Adresse zum Docker-Bridge-Netzwerk gehört, kann der Kernel das Paket anschließend in Richtung docker0 weiterleiten.

Wichtig: DNAT allein bedeutet noch nicht, dass die Verbindung zugelassen wird. Weitergeleitete Pakete müssen zusätzlich die entsprechenden Filterregeln passieren. Diese werden später anhand des Zugriffs zwischen zwei Docker-Netzwerken untersucht.

Besonderheit: Zugriff über localhost

Bei einer Verbindung vom Docker-Host selbst wird nicht PREROUTING, sondern für die lokale NAT-Verarbeitung die Chain OUTPUT relevant. Auf dem Docker-Host enthält sie folgende Regel:

-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER

Der Parameter ! -d 127.0.0.0/8 schließt jedoch den IPv4-Loopback-Adressbereich ausdrücklich von dieser Weiterleitung aus. Trotzdem lieferte unser Versuch mit curl -I http://127.0.0.1:8081 eine erfolgreiche HTTP-Antwort. Die Untersuchung der lauschenden TCP-Sockets zeigt, dass Docker zusätzlich einen eigenen Proxyprozess gestartet hat:

$ sudo ss -ltnp '( sport = :8081 )'
State           Recv-Q          Send-Q                   Local Address:Port                   Peer Address:Port          Process
LISTEN          0               4096                           0.0.0.0:8081                        0.0.0.0:*              users:(("docker-proxy",pid=32037,fd=8))
LISTEN          0               4096                              [::]:8081                           [::]:*              users:(("docker-proxy",pid=32043,fd=8))

Der IPv4-Prozess wurde mit folgenden Parametern ausgeführt:

$ ps -p 32037 -o args=
/usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 8081 -container-ip 172.17.0.3 -container-port 80 -use-listen-fd

docker-proxy kann TCP-Verbindungen im Userspace entgegennehmen und eine Verbindung zum entsprechenden Containerdienst herstellen. Damit erklärt sich auch die erfolgreiche Erreichbarkeit über 127.0.0.1:8081, obwohl diese Adresse von der gezeigten NAT-OUTPUT-Regel ausgenommen ist.

Docker kann also DNAT und docker-proxy parallel bereitstellen, ohne dass jede einzelne Verbindung beide Mechanismen durchlaufen muss.

Die hier dargestellten Regeln entsprechen der untersuchten Docker-Testumgebung. Andere Docker-Versionen und Konfigurationen, insbesondere mit einem anderen Firewall-Backend, können sich im Aufbau ihres Regelwerks unterscheiden.

Host-IP-Bindung: Portveröffentlichung gezielt einschränken

Wird ein Container-Port mit -p 8081:80 veröffentlicht, bindet Docker ihn in der Standardkonfiguration an alle Host-Adressen. Der Dienst kann damit grundsätzlich auch über die LAN-Adresse des Docker-Hosts erreicht werden, sofern die Netzwerk- und Firewallregeln dies zulassen.

Das ist jedoch nicht immer erwünscht. Beispielsweise soll eine Webanwendung möglicherweise ausschließlich vom Docker-Host selbst angesprochen werden. Docker ermöglicht deshalb, bei der Portveröffentlichung eine bestimmte Host-IP-Adresse anzugeben.

Versuch: nginx nur über localhost veröffentlichen

Für diesen Versuch wird ein temporärer nginx-Container gestartet, dessen Host-Port ausdrücklich an die IPv4-Loopback-Adresse gebunden ist:

$ sudo docker run -d --name academy-cont-nginx-local-01 -p 127.0.0.1:8084:80 nginx:alpine
f75b1a276196f4caa8905643bbbecc874ee15e3ab24c89d654e50a677db4bd08

m Unterschied zum bisherigen Port-Mapping enthält die Option -p jetzt drei Angaben:

BestandteilBedeutung
127.0.0.1IPv4-Adresse des Hosts, an die der veröffentlichte Port gebunden wird
8084Veröffentlichter TCP-Port des Hosts
80Interner TCP-Port des Containers

Die Kontrolle mit docker ps zeigt entsprechend:

$ sudo docker ps --filter name=academy-cont-nginx-local-01 --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
NAMES                         STATUS          PORTS
academy-cont-nginx-local-01   Up 55 seconds   127.0.0.1:8084->80/tcp

Anders als bei der Veröffentlichung auf allen Host-Adressen erscheint hier weder 0.0.0.0:8084 noch [::]:8084.

Zugriffstests

Zunächst wird überprüft, ob der Webserver über die Loopback-Adresse des Docker-Hosts erreichbar ist:

$ curl -I http://127.0.0.1:8084
HTTP/1.1 200 OK
---snip---

Der Webserver antwortet erwartungsgemäß mit HTTP/1.1 200 OK.

Anschließend wird derselbe Host-Port über die LAN-Adresse angesprochen:

$ curl -I --connect-timeout 3 http://192.168.178.119:8084
curl: (7) Failed to connect to 192.168.178.119 port 8084 after 0 ms: Could not connect to server

Der Verbindungsaufbau scheitert, da Docker den Port 8084 ausschließlich an die Loopback-Adresse gebunden hat.

Abschließend wird überprüft, ob der Webserver weiterhin direkt über seine Container-IP erreichbar ist:

$ curl -I http://172.17.0.7:80
HTTP/1.1 200 OK
---snip---

Auch ein anderer Container im selben Docker-Bridge-Netzwerk kann den Dienst direkt erreichen:

$ sudo docker exec academy-volume-nginx-01 curl -I http://172.17.0.7:80
HTTP/1.1 200 OK
---snip---

Zusammenfassung der Zugriffstests:

AusgangspunktZieladresseErgebnis
Docker-Host127.0.0.1:8084HTTP 200 OK
Docker-Host192.168.178.119:8084Verbindung fehlgeschlagen
LAN-Client*192.168.178.119:8084Verbindung fehlgeschlagen
Docker-Host172.17.0.7:80HTTP 200 OK
Anderer Container im selben Bridge-Netzwerk172.17.0.7:80HTTP 200 OK
* Nicht in der Versuchsanordnung enthalten

Die Ergebnisse zeigen, dass die Bindung an 127.0.0.1 die Veröffentlichung über den Host-Port einschränkt, nicht jedoch die interne Erreichbarkeit des Containers.

Der nginx-Webserver lauscht weiterhin auf Port 80 innerhalb seines Containers. Andere Teilnehmer desselben Docker-Netzwerks können ihn deshalb direkt über seine Container-IP erreichen.

Technische Einordnung

Bei der Loopback-Bindung legt Docker die Portveröffentlichung ausschließlich für 127.0.0.1:8084 an. Entsprechend steht der veröffentlichte Port nicht über die LAN-Adresse 192.168.178.119 zur Verfügung.

Diese Beschränkung ist von den Firewallregeln für die direkte Container-Kommunikation zu unterscheiden. Ein Zugriff auf 172.17.0.7:80 verwendet nicht den veröffentlichten Host-Port 8084 und wird daher auch nicht durch dessen Loopback-Bindung verhindert.

Die beiden Varianten lassen sich somit folgendermaßen gegenüberstellen:

Eigenschaft-p 8081:80-p 127.0.0.1:8084:80
Host-Port80818084
IPv4-BindungAlle Host-AdressenNur Loopback
Zugriff vom Host über localhostMöglichMöglich
Zugriff über die Host-LAN-AdresseGrundsätzlich möglichNicht über diese Portveröffentlichung
Direkter Zugriff über Container-IPUnabhängig vom Port-MappingUnabhängig vom Port-Mapping

Eine Loopback-Bindung ist damit eine sinnvolle Maßnahme, wenn ein Containerdienst nicht allgemein über die Netzwerkschnittstellen des Hosts veröffentlicht werden soll. Sie stellt jedoch keine vollständige Isolation des Containers dar. Insbesondere bleibt die direkte Erreichbarkeit über verbundene Docker-Netzwerke bestehen.

Bei aktuellen Docker-Versionen wird eine Loopback-Portveröffentlichung auf den Host beschränkt. Bei Docker-Versionen vor 28.0.0 konnten unter bestimmten Bedingungen auch Rechner im selben Layer-2-Netzwerk solche Ports erreichen. Die Docker-Version ist deshalb bei einer Sicherheitsbewertung zu berücksichtigen.

Port-Mapping zwischen getrennten Docker-Netzwerken

Docker isoliert unterschiedliche Bridge-Netzwerke standardmäßig voneinander. Container im selben Netzwerk können direkt miteinander kommunizieren, während Verbindungen zwischen verschiedenen Bridge-Netzwerken durch Docker-Firewallregeln eingeschränkt werden.

Ein veröffentlichter Container-Port stellt jedoch einen ausdrücklich vorgesehenen Zugangsweg über den Docker-Host bereit. Damit kann ein Containerdienst auch für Clients erreichbar sein, die nicht zum selben Docker-Netzwerk gehören.

Versuchsaufbau: Zwei getrennte Bridge-Netzwerke

Für diesen Versuch werden zwei bereits vorhandene Container verwendet. academy-cont-alpine-01 befindet sich im benutzerdefinierten Netzwerk academy-net-01, während academy-bind-nginx-01 dem Standardnetzwerk bridge angehört.

EigenschaftAlpine-Containernginx-Container
Containeracademy-cont-alpine-01academy-bind-nginx-01
Docker-Netzwerkacademy-net-01bridge
Subnetz172.18.0.0/16172.17.0.0/16
Container-IP beim Test172.18.0.2172.17.0.3
Interner Webserver-Port–TCP 80
Veröffentlichter Host-PortKeinerTCP 8081

Zunächst wird sichergestellt, dass der Alpine-Container ausschließlich mit academy-net-01 verbunden ist:

$ sudo docker inspect --format '{{json .NetworkSettings.Networks}}' academy-cont-alpine-01 | jq -r 'keys[]'
academy-net-01

Versuch 1: Direkter Zugriff auf die Container-IP

Der Alpine-Container versucht, den nginx-Webserver direkt unter 172.17.0.3:80 zu erreichen. Dieser Zugriff wurde bereits bei der Untersuchung der benutzerdefinierten Docker-Netzwerke getestet.

$ sudo docker exec academy-volume-nginx-01 curl -I --connect-timeout 3 http://172.18.0.4:80
curl: (28) Connection timed out after 3001 milliseconds

Die Verbindung endet mit einem Timeout. Obwohl der Docker-Host beide Subnetze kennt, verhindert die standardmäßige Docker-Netzwerkisolation die direkte Weiterleitung zwischen den beiden Bridge-Netzwerken.

Die Ursache ist somit nicht grundsätzlich eine fehlende Route. Vielmehr schränken die von Docker eingerichteten Filterregeln den Verkehr zwischen den Netzwerken ein.

Versuch 2: Zugriff über den veröffentlichten Host-Port

Diesmal wird der Webserver nicht über seine Container-IP, sondern über die LAN-Adresse des Docker-Hosts und den veröffentlichten Port 8081 angesprochen:

$ sudo docker exec academy-cont-alpine-01 wget -S --spider -T 3 http://192.168.178.119:8081
Connecting to 192.168.178.119:8081 (192.168.178.119:8081)
  HTTP/1.1 200 OK
---snip---

Obwohl sich die Container in unterschiedlichen Bridge-Netzwerken befinden, ist die Verbindung erfolgreich.

Das liegt daran, dass der nginx-Webserver über den Host-Port 8081 ausdrücklich veröffentlicht wurde. Docker ermöglicht für diesen Dienst einen gezielten Zugang, ohne die allgemeine Isolation der beiden Netzwerke aufzuheben.

Die beteiligten NAT- und Firewallregeln

Für die Portveröffentlichung ist zunächst die bereits bekannte DNAT-Regel verantwortlich:

-A DOCKER ! -i docker0 -p tcp -m tcp --dport 8081 -j DNAT --to-destination 172.17.0.3:80

Sie verändert das Paket-Ziel von 192.168.178.119:8081 zu 172.17.0.3:80, bevor die endgültige Routingentscheidung erfolgt.

Anschließend muss die Weiterleitung durch die Filterregeln zugelassen werden. Auf dem untersuchten Host enthält die Chain DOCKER in der Filter-Tabelle unter anderem folgende Regel:

$ sudo iptables -t filter -L DOCKER -n -v
Chain DOCKER (2 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 ACCEPT     tcp  --  !docker0 docker0  0.0.0.0/0            172.17.0.5           tcp dpt:80
    3   192 ACCEPT     tcp  --  !docker0 docker0  0.0.0.0/0            172.17.0.4           tcp dpt:80
    5   316 ACCEPT     tcp  --  !docker0 docker0  0.0.0.0/0            172.17.0.3           tcp dpt:80
    7   448 ACCEPT     tcp  --  !docker0 docker0  0.0.0.0/0            172.17.0.2           tcp dpt:80
    0     0 DROP       all  --  !docker0 docker0  0.0.0.0/0            0.0.0.0/0
    0     0 DROP       all  --  !br-48723e8dc020 br-48723e8dc020  0.0.0.0/0            0.0.0.0/0

Die Regel ACCEPT tcp -- !docker0 docker0 0.0.0.0/0 172.17.0.3 tcp dpt:80 lässt sich folgendermaßen aufschlüsseln:

BestandteilBedeutung
ACCEPTDas passende Paket wird zugelassen.
tcpDie Regel betrifft TCP-Verkehr.
!docker0Das Paket darf nicht über docker0 eingegangen sein.
docker0Das Paket soll über die Schnittstelle docker0 weitergeleitet werden.
0.0.0.0/0Beliebige IPv4-Quelladresse.
172.17.0.3IPv4-Zieladresse des nginx-Containers.
tcp dpt:80TCP-Zielport 80 im Container.

Die Filterregel prüft bereits die durch DNAT veränderte Zieladresse. Deshalb erscheint hier Port 80 statt Port 8081.

Nicht veröffentlichte Dienste werden dadurch nicht automatisch erreichbar. In derselben Chain befinden sich zusätzlich DROP-Regeln, die ansonsten unerlaubten eingehenden Verkehr zu den jeweiligen Bridge-Netzwerken blockieren.

Ergebnis: Gezielte Freigabe statt allgemeiner Netzwerkverbindung

Der Versuch zeigt, dass eine Docker-Portveröffentlichung den Zugriff auf einen Dienst auch aus einem anderen Docker-Bridge-Netzwerk ermöglichen kann.

Dabei arbeiten mehrere Mechanismen zusammen: DNAT schreibt das ursprüngliche Ziel auf die Container-IP und den internen Port um. Der Linux-Kernel ermittelt anschließend den Weiterleitungsweg, und die Docker-Filterregeln entscheiden, ob das Paket passieren darf.

Die Isolation der beiden Bridge-Netzwerke bleibt grundsätzlich erhalten. Es wird lediglich die Verbindung zum ausdrücklich veröffentlichten Dienst ermöglicht.

Docker unterbindet die direkte Kommunikation zwischen getrennten Bridge-Netzwerken. Ein ausdrücklich veröffentlichter Container-Port bleibt über den Docker-Host erreichbar.

Container-Kommunikation ohne Port-Mapping

Ein Container benötigt keinen veröffentlichten Host-Port, um mit anderen Containern im selben Docker-Netzwerk zu kommunizieren. Entscheidend ist, dass die Container über ein gemeinsames Netzwerk verbunden sind und der gewünschte Dienst dort Verbindungen annimmt.

Das lässt sich mit einem nginx-Webserver demonstrieren, der ausschließlich innerhalb des benutzerdefinierten Netzwerks academy-net-01 betrieben wird.

Versuch: nginx ohne veröffentlichten Host-Port

Der Container wird mit folgendem Befehl gestartet:

$ sudo docker run -d --name academy-cont-nginx-internal-01 --network academy-net-01 nginx:alpine
3e038aca990d7589d4c9cfc76286689fca7d8241648c38db8398f749620c817d

Die Option --network academy-net-01 verbindet den Container mit dem angegebenen Docker-Netzwerk. Da auf die Option -p verzichtet wird, veröffentlicht Docker keinen Host-Port.

Trotzdem läuft der nginx-Webserver innerhalb des Containers auf TCP-Port 80.

Versuch 1: Zugriff aus demselben Netzwerk

Vom bereits vorhandenen Alpine-Container wird der Webserver über seinen Containernamen angesprochen:

$ sudo docker exec academy-cont-alpine-01 wget -S --spider -T 3 http://academy-cont-nginx-internal-01:80
Connecting to academy-cont-nginx-internal-01:80 (172.18.0.4:80)
  HTTP/1.1 200 OK
---snip---

Der Zugriff funktioniert. Docker löst den Containernamen über seinen integrierten DNS-Dienst auf die Adresse 172.18.0.4 auf. Anschließend erfolgt die Kommunikation direkt über das gemeinsame Bridge-Netzwerk.

Eine Portveröffentlichung auf dem Docker-Host ist dafür nicht erforderlich.

Versuch 2: Zugriff aus einem anderen Bridge-Netzwerk

Nun wird derselbe Webserver von academy-volume-nginx-01 angesprochen. Dieser Container befindet sich im Standardnetzwerk bridge, während unser Testcontainer ausschließlich mit academy-net-01 verbunden ist:

$ sudo docker exec academy-volume-nginx-01 curl -I --connect-timeout 3 http://172.18.0.4:80
curl: (28) Connection timed out after 3001 milliseconds

Die Verbindung scheitert. Docker unterbindet die direkte Kommunikation zwischen den getrennten Bridge-Netzwerken durch seine Filterregeln.

Da der Testcontainer außerdem keinen Host-Port veröffentlicht hat, steht auch kein entsprechender Zugangsweg über den Docker-Host zur Verfügung.

Gegenüberstellung der Ergebnisse

ZugriffPort-Mapping erforderlichErgebnis
Container im selben Netzwerk → 172.18.0.4:80NeinHTTP 200 OK
Container im anderen Bridge-Netzwerk → 172.18.0.4:80NeinTimeout
Container im anderen Bridge-Netzwerk → veröffentlichter Host-Port eines nginx-ContainersJaHTTP 200 OK im vorherigen Versuch

Die Ergebnisse verdeutlichen den Unterschied zwischen der internen Erreichbarkeit eines Containerdienstes und seiner Veröffentlichung über den Docker-Host.

Dieser Unterschied ist auch für die spätere Verwendung eines Reverse Proxys von Bedeutung. Mehrere Webanwendungen können über ein gemeinsames Docker-Netzwerk mit einem Reverse Proxy kommunizieren, ohne ihre eigenen Ports auf dem Host veröffentlichen zu müssen. Lediglich der Reverse Proxy benötigt dann die entsprechenden veröffentlichten Ports, sofern er von außerhalb des Docker-Hosts erreichbar sein soll.

Sicherheitsaspekte bei der Veröffentlichung von Container-Ports

Die Veröffentlichung eines Container-Ports beeinflusst unmittelbar, von welchen Systemen ein Dienst erreichbar ist. Dabei ist zu beachten, dass Docker eigene NAT- und Firewallregeln einrichtet, um die gewünschten Netzwerkverbindungen zu ermöglichen.

Ein veröffentlichter Port stellt deshalb nicht nur eine technische Verbindung zwischen Host und Container her, sondern erweitert unter Umständen auch die von außen erreichbare Angriffsfläche.

Veröffentlichte Ports und Host-Firewall

Wird ein Container mit der Option -p 8081:80 gestartet, veröffentlicht Docker den Dienst in der Standardkonfiguration auf allen Host-Adressen. Damit kann der Webserver grundsätzlich auch von anderen Rechnern im LAN erreicht werden.

Wie unsere Untersuchung gezeigt hat, richtet Docker hierfür nicht nur eine DNAT-Regel ein, sondern auch entsprechende Freigaben innerhalb seiner Filter-Chains.

Eine solche Freigabe lässt sich beispielsweise mit dem oben bereits gezeigten Befehl anzeigen:

$ sudo iptables -t filter -L DOCKER -n -v
Chain DOCKER (2 references)
 pkts bytes target  prot opt in       out      source       destination
    ---snip---
    5   316 ACCEPT  tcp  --  !docker0 docker0  0.0.0.0/0    172.17.0.3   tcp dpt:80
    ---snap---

Diese Regel erlaubt passende Verbindungen zum nginx-Container auf TCP-Port 80. Die Quelle ist dabei nicht auf ein bestimmtes Subnetz beschränkt.

Daraus ergibt sich eine wichtige Konsequenz: Docker verwaltet einen Teil der Firewallregeln selbst. Wer einen Container-Port veröffentlicht, sollte deshalb nicht allein aus den vorhandenen Regeln der Host-Firewall auf die tatsächliche Erreichbarkeit schließen.

Insbesondere bei Firewalls, die mit einfachen Regeln in der INPUT-Chain arbeiten, ist Vorsicht geboten. Durch DNAT weitergeleitete Pakete durchlaufen bei diesem Verbindungsweg die FORWARD-Chain. Eine Einschränkung eingehender Verbindungen in INPUT schützt deshalb nicht automatisch einen veröffentlichten Containerdienst.

Für zusätzliche Zugriffsbeschränkungen stellt Docker bei Verwendung des iptables-Backends unter anderem die Chain DOCKER-USER bereit. Sie ermöglicht eigene Filterregeln, ohne die von Docker automatisch verwalteten Chains unmittelbar verändern zu müssen. Dabei ist zu berücksichtigen, dass die Pakete dort bereits die DNAT-Umsetzung durchlaufen haben können.

Port-Mapping gezielt einsetzen

Aus den bisherigen Versuchen ergeben sich unterschiedliche Möglichkeiten, einen Containerdienst bereitzustellen:

KonfigurationErreichbarkeitTypischer Einsatzzweck
-p 8081:80Über alle Host-Adressen, soweit Netzwerkregeln es zulassenDirekt erreichbarer Webdienst
-p 127.0.0.1:8084:80Über den IPv4-Loopback-Port des HostsAusschließlich lokal veröffentlichter Dienst
Ohne -pÜber geeignete direkte Netzwerkverbindungen, insbesondere innerhalb gemeinsamer Docker-NetzwerkeInterner Backend- oder Datenbankdienst

Die letzte Variante bedeutet keineswegs, dass ein Container vollständig vom Netzwerk isoliert ist. Sie verhindert lediglich, dass Docker einen entsprechenden Host-Port veröffentlicht. Ebenso ersetzt die Bindung an 127.0.0.1 keine Kontrolle der internen Container-Kommunikation.

Konsequenzen für den praktischen Betrieb

Ein sinnvoller Grundsatz besteht darin, nur diejenigen Container-Ports zu veröffentlichen, die tatsächlich von außerhalb des Docker-Netzwerks erreichbar sein müssen.

Bei Anwendungen mit mehreren Komponenten ist es beispielsweise häufig nicht erforderlich, Datenbanken, APIs und andere interne Dienste jeweils auf einem eigenen Host-Port bereitzustellen. Stattdessen können diese Komponenten innerhalb geeigneter Docker-Netzwerke miteinander kommunizieren.

Ein vorgeschalteter Reverse Proxy kann den externen Zugriff auf die benötigten Webanwendungen zentral bereitstellen. Dadurch lässt sich die Anzahl der unmittelbar veröffentlichten Dienste reduzieren.

Die Portveröffentlichung allein gewährleistet allerdings weder die Sicherheit des Dienstes noch eine vollständige Isolation der beteiligten Container. Auch die Konfiguration der Anwendungen, ihre Berechtigungen, die verwendeten Docker-Netzwerke und die übergeordneten Firewallregeln müssen berücksichtigt werden.

Fazit

Das Port-Mapping ermöglicht es, Dienste innerhalb eines Docker-Containers über eine IP-Adresse und einen Port des Docker-Hosts erreichbar zu machen. Dabei bleibt die interne Netzwerkadressierung des Containers unabhängig von der veröffentlichten Host-Adresse.

Die praktischen Versuche haben gezeigt, dass Docker zwischen der direkten Container-Kommunikation und dem Zugriff über veröffentlichte Host-Ports unterscheidet. Container innerhalb desselben Bridge-Netzwerks können grundsätzlich direkt miteinander kommunizieren, ohne dass dafür ein Port-Mapping erforderlich ist. Zwischen getrennten Bridge-Netzwerken wird diese direkte Kommunikation dagegen standardmäßig durch Docker-Firewallregeln eingeschränkt.

Ein ausdrücklich veröffentlichter Port kann dennoch aus einem anderen Docker-Netzwerk erreichbar sein. Unter Linux wird dies unter anderem durch das Zusammenspiel von DNAT, Routing und den entsprechenden Filterregeln ermöglicht. Je nach Konfiguration und Verbindungsweg kann zusätzlich docker-proxy beteiligt sein.

Die Wahl der Host-IP bei der Portveröffentlichung bestimmt außerdem, über welche Adressen ein Dienst angeboten wird. Eine Bindung an 127.0.0.1 beschränkt die Veröffentlichung auf den lokalen IPv4-Loopback-Zugriff, verhindert jedoch nicht die direkte Kommunikation innerhalb des Docker-Netzwerks.

Port-Mapping regelt somit die Veröffentlichung eines Containerdienstes, nicht dessen vollständige Netzwerkisolation. Für einen sicheren Betrieb müssen deshalb die Portbindungen, die Docker-Netzwerke und die tatsächlich wirksamen Firewallregeln gemeinsam betrachtet werden.

Befehlsreferenz

Die folgende Übersicht fasst die wesentlichen Befehle zur Untersuchung und Konfiguration von Docker-Portveröffentlichungen zusammen. Die angegebenen Container- und Netzwerknamen beziehen sich auf die verwendete Testumgebung.

BefehlFunktion
sudo docker ps --format "table {{.Names}}\t{{.Ports}}"Container und ihre veröffentlichten Ports anzeigen
sudo docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' academy-bind-nginx-01Interne Container-IP ermitteln
sudo docker run -d --name academy-cont-nginx-local-01 -p 127.0.0.1:8084:80 nginx:alpinenginx mit ausschließlich lokal veröffentlichtem IPv4-Host-Port starten
curl -I http://127.0.0.1:8084HTTP-Erreichbarkeit über localhost prüfen
curl -I --connect-timeout 3 http://192.168.178.119:8084Erreichbarkeit über die LAN-Adresse prüfen
sudo docker exec academy-cont-alpine-01 wget -S --spider -T 3 http://192.168.178.119:8081Veröffentlichten Host-Port aus einem Container testen
sudo iptables -t nat -SNAT-Regeln einschließlich Docker-Portweiterleitungen anzeigen
sudo iptables -t filter -L DOCKER -n -vDocker-Filterregeln und Paket-Zähler anzeigen
sudo iptables -L DOCKER-FORWARD -n -vWeiterleitungsregeln von Docker untersuchen
sudo ss -ltnp '( sport = :8081 )'Lauschende TCP-Sockets und zugehörige Prozesse für Port 8081 anzeigen
sudo docker rm -f academy-cont-nginx-local-01Temporären Testcontainer beenden und entfernen

Die Befehle zur Untersuchung der Netzwerkkonfiguration und Firewallregeln sind lesend und verändern den Docker-Host nicht. Die Beispiele mit docker run und docker rm erstellen beziehungsweise entfernen dagegen Container.

Die gezeigten iptables-Kommandos beziehen sich auf eine Docker-Installation mit entsprechendem Firewall-Backend. Bei anderen Docker-Konfigurationen kann die Untersuchung des nftables-Regelwerks erforderlich sein.

Loading

Updated on 8. November 2026
Was this article helpful?

Related Articles