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:
| Bestandteil | Bedeutung |
|---|---|
-p | Veröffentlicht einen Container-Port auf dem Docker-Host |
8081 | Port, über den der Dienst auf dem Host erreichbar ist |
80 | Port, auf dem der Dienst innerhalb des Containers lauscht |
tcp | Verwendetes 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:
| Zugriff | Zieladresse | Zielport | Port-Mapping erforderlich |
|---|---|---|---|
| Direkt über das Docker-Netzwerk | 172.17.0.3 | 80 | Nein |
| Über die LAN-Adresse des Docker-Hosts | 192.168.178.119 | 8081 | Ja |
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.

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.
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:
| Ausgangspunkt | Zieladresse | Port | Ergebnis |
|---|---|---|---|
| Docker-Host | 172.17.0.3 | 80 | HTTP 200 OK |
| Docker-Host | 127.0.0.1 | 8081 | HTTP 200 OK |
| Docker-Host | 192.168.178.119 | 8081 | HTTP 200 OK |
| LAN-Client* | 192.168.178.119 | 8081 | HTTP 200 OK |
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.
| Parameter | Bedeutung |
|---|---|
-A DOCKER | Die Regel befindet sich in der Chain DOCKER. |
! -i docker0 | Das Paket darf nicht über die Schnittstelle docker0 eingegangen sein. Das Ausrufezeichen negiert die Bedingung. |
-p tcp | Die Regel gilt für TCP-Pakete. |
-m tcp | Aktiviert die TCP-spezifischen Prüfkriterien. |
--dport 8081 | Der TCP-Zielport muss 8081 sein. |
-j DNAT | Führt eine Zieladressübersetzung durch (Destination NAT). |
--to-destination 172.17.0.3:80 | Ersetzt das ursprüngliche Ziel durch die Container-IP 172.17.0.3 und Port 80. |
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:
| Parameter | Bedeutung |
|---|---|
-A PREROUTING | Die Regel befindet sich in der Chain PREROUTING der NAT-Tabelle. |
-m addrtype | Aktiviert die Prüfung des Adresstyps. |
--dst-type LOCAL | Die 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.
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.
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:
| Bestandteil | Bedeutung |
|---|---|
127.0.0.1 | IPv4-Adresse des Hosts, an die der veröffentlichte Port gebunden wird |
8084 | Veröffentlichter TCP-Port des Hosts |
80 | Interner 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:
| Ausgangspunkt | Zieladresse | Ergebnis |
|---|---|---|
| Docker-Host | 127.0.0.1:8084 | HTTP 200 OK |
| Docker-Host | 192.168.178.119:8084 | Verbindung fehlgeschlagen |
| LAN-Client* | 192.168.178.119:8084 | Verbindung fehlgeschlagen |
| Docker-Host | 172.17.0.7:80 | HTTP 200 OK |
| Anderer Container im selben Bridge-Netzwerk | 172.17.0.7:80 | HTTP 200 OK |
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-Port | 8081 | 8084 |
| IPv4-Bindung | Alle Host-Adressen | Nur Loopback |
| Zugriff vom Host über localhost | Möglich | Möglich |
| Zugriff über die Host-LAN-Adresse | Grundsätzlich möglich | Nicht über diese Portveröffentlichung |
| Direkter Zugriff über Container-IP | Unabhängig vom Port-Mapping | Unabhä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.
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.
| Eigenschaft | Alpine-Container | nginx-Container |
|---|---|---|
| Container | academy-cont-alpine-01 | academy-bind-nginx-01 |
| Docker-Netzwerk | academy-net-01 | bridge |
| Subnetz | 172.18.0.0/16 | 172.17.0.0/16 |
| Container-IP beim Test | 172.18.0.2 | 172.17.0.3 |
| Interner Webserver-Port | – | TCP 80 |
| Veröffentlichter Host-Port | Keiner | TCP 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.
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.
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:
| Bestandteil | Bedeutung |
|---|---|
ACCEPT | Das passende Paket wird zugelassen. |
tcp | Die Regel betrifft TCP-Verkehr. |
!docker0 | Das Paket darf nicht über docker0 eingegangen sein. |
docker0 | Das Paket soll über die Schnittstelle docker0 weitergeleitet werden. |
0.0.0.0/0 | Beliebige IPv4-Quelladresse. |
172.17.0.3 | IPv4-Zieladresse des nginx-Containers. |
tcp dpt:80 | TCP-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.

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
| Zugriff | Port-Mapping erforderlich | Ergebnis |
|---|---|---|
Container im selben Netzwerk → 172.18.0.4:80 | Nein | HTTP 200 OK |
Container im anderen Bridge-Netzwerk → 172.18.0.4:80 | Nein | Timeout |
| Container im anderen Bridge-Netzwerk → veröffentlichter Host-Port eines nginx-Containers | Ja | HTTP 200 OK im vorherigen Versuch |
Die Ergebnisse verdeutlichen den Unterschied zwischen der internen Erreichbarkeit eines Containerdienstes und seiner Veröffentlichung über den Docker-Host.
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.
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:
| Konfiguration | Erreichbarkeit | Typischer Einsatzzweck |
|---|---|---|
-p 8081:80 | Über alle Host-Adressen, soweit Netzwerkregeln es zulassen | Direkt erreichbarer Webdienst |
-p 127.0.0.1:8084:80 | Über den IPv4-Loopback-Port des Hosts | Ausschließlich lokal veröffentlichter Dienst |
Ohne -p | Über geeignete direkte Netzwerkverbindungen, insbesondere innerhalb gemeinsamer Docker-Netzwerke | Interner 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.
| Befehl | Funktion |
|---|---|
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-01 | Interne Container-IP ermitteln |
sudo docker run -d --name academy-cont-nginx-local-01 -p 127.0.0.1:8084:80 nginx:alpine | nginx mit ausschließlich lokal veröffentlichtem IPv4-Host-Port starten |
curl -I http://127.0.0.1:8084 | HTTP-Erreichbarkeit über localhost prüfen |
curl -I --connect-timeout 3 http://192.168.178.119:8084 | Erreichbarkeit über die LAN-Adresse prüfen |
sudo docker exec academy-cont-alpine-01 wget -S --spider -T 3 http://192.168.178.119:8081 | Veröffentlichten Host-Port aus einem Container testen |
sudo iptables -t nat -S | NAT-Regeln einschließlich Docker-Portweiterleitungen anzeigen |
sudo iptables -t filter -L DOCKER -n -v | Docker-Filterregeln und Paket-Zähler anzeigen |
sudo iptables -L DOCKER-FORWARD -n -v | Weiterleitungsregeln 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-01 | Temporä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.
![]()