Docker Container aus dem Internet erreichbar machen

Kurz erklärt

Kurz gesagt. Ein ports:-Eintrag macht einen Container auf Ihrem Host erreichbar und nicht weiter. Darüber hinaus kommen Sie auf vier Wegen: mit einer Portfreigabe am Router, die eine echte öffentliche IP-Adresse von Ihrem Internetanbieter voraussetzt; mit Cloudflare Tunnel für HTTP und HTTPS; mit Tailscale für den privaten Zugriff auf Ihre eigenen Geräte; oder mit einer eigenen öffentlichen IP-Adresse, die an Ihr Docker-Projekt geleitet wird, jedes Protokoll transportiert und nirgends geöffnete Ports braucht.

Eine öffentliche IP-Adresse für Ihren Compose-Stack?

Eine eigene öffentliche IPv4- und IPv6-Adresse, über einen verschlüsselten Tunnel an Ihren Docker-Host geleitet. Keine veröffentlichten Ports, keine Änderungen am Router, funktioniert auch hinter CGNAT.

Öffentliche IP holen

Ihr Docker-Compose-Stack läuft: Sie starten docker compose up -d, öffnen http://localhost:8080, und die Anwendung ist da. Jetzt soll das Projekt online gehen, damit andere es nutzen können.

Genau hier wird es schwierig, Docker Container aus dem Internet erreichbar zu machen, denn ab diesem Punkt ist es kein Docker-Problem mehr. Ihre ports:-Einträge haben bereits alles getan, was sie können. Zwischen Ihrem Stack und dem Internet steht das Netz Ihres Internetanbieters.

Dieser Leitfaden erklärt, was im Weg steht, welche vier Wege daran vorbeiführen und was eine eigene öffentliche IP-Adresse an einem Compose-Projekt ändert. Die vollständige Konfigurationsreferenz finden Sie in der Docker-Dokumentation.

Was ports: tatsächlich bewirkt

Wenn Sie ports: "8080:80" schreiben, weist Docker den Kernel des Hosts an, Datenverkehr auf Host-Port 8080 an Port 80 im Container weiterzuleiten. Der Service ist dann unter der Adresse des Hosts erreichbar: localhost:8080 auf dem Rechner selbst und etwa 192.168.1.50:8080 von einem Laptop im selben Netz.

Mehr passiert nicht. Ein veröffentlichter Port erreicht den Host und über den Host Ihr lokales Netz. Über Ihren Router hinaus reicht er nicht, und daran ändert keine Compose-Einstellung etwas, weil das Hindernis nicht in Docker liegt.

Private Adressen und die Übersetzung, die sie verbirgt

Sehen Sie sich die Adresse an, die Ihr Rechner in Ihrem Netz hat. Beginnt sie mit 192.168., 10. oder liegt sie zwischen 172.16. und 172.31., ist es eine private Adresse: ein Bereich für die interne Nutzung, den kein Router im öffentlichen Internet weiterleitet. Jedes Gerät in Ihrem Netz hat eine solche Adresse, und jeder Container ebenso.

Ihr Router hält die eine öffentliche IP-Adresse, die Ihnen Ihr Anbieter zugeteilt hat, und leiht sie allen Geräten dahinter, per Network Address Translation, kurz NAT. Baut Ihr Laptop eine Verbindung nach außen auf, ersetzt der Router die Absenderadresse des Pakets durch seine eigene, merkt sich das und schreibt die Antworten so um, dass sie beim anfragenden Gerät ankommen.

Das funktioniert nur in eine Richtung. Eine ausgehende Verbindung legt beim Verlassen ihren eigenen Eintrag in der Tabelle des Routers an. Eine eingehende Verbindung von einem Fremden findet keinen passenden Eintrag und kein Ziel, also wird sie verworfen. Mit einer Portfreigabe legen Sie einen solchen Eintrag von Hand und im Voraus an.

Wenn Sie nicht sicher sind, was Ihr Anschluss Ihnen bietet: Im Leitfaden So bekommen Sie eine öffentliche IP-Adresse steht, wie Sie das prüfen.

CGNAT: Wo eine Portfreigabe keine Option mehr ist

Hinter der ersten Mauer steht eine zweite, und immer mehr Menschen sitzen dahinter.

Vielen Internetanbietern sind die IPv4-Adressen ausgegangen, deshalb führen sie dieselbe Übersetzung eine Ebene höher ein zweites Mal durch. Dann ist auch die WAN-Adresse Ihres Routers privat, meist im Bereich 100.64.0.0/10, und die echte öffentliche Adresse teilen Sie sich mit vielen anderen Kunden. Das ist Carrier-Grade NAT, kurz CGNAT.

Eine Freigaberegel an Ihrem eigenen Router wartet nun auf Datenverkehr, der nie ankommt, denn wohin eine eingehende Verbindung geht, entscheidet sich vorher, auf Geräten, auf die Sie keinen Zugriff haben. Wie Sie feststellen, welche Situation bei Ihnen vorliegt, und welche Anbieter so arbeiten, erklärt der Leitfaden zum Selbst-Hosten hinter Carrier-Grade NAT.

Für einen Compose-Stack kommen vier Ansätze in Frage, und alles, was CGNAT übersteht, tut das aus einem Grund: Es beruht auf einer Verbindung, die Ihr Rechner nach außen aufbaut.

Die vier Wege im Vergleich

MethodeFunktioniert hinter CGNATProtokolleWer TLS terminiertÄnderung am RouterAdresse, unter der Sie erreichbar sind
Portfreigabe am RouterNeinJedes TCP oder UDPIhr eigener ServiceJaDie Ihres Anbieters, meist wechselnd
Cloudflare TunnelJaHTTP und HTTPSCloudflareNeinDer geteilte Adressraum von Cloudflare
TailscaleJaAlle, aber nur privatEnde-zu-Ende innerhalb Ihres TailnetsNeinEine private Tailnet-Adresse
Eigene öffentliche IPJaJedes TCP oder UDPIhr eigener ServiceNeinEine öffentliche IPv4-Adresse, die Ihnen gehört

Die ersten beiden Spalten entscheiden die meisten Fälle. Mit einer echten öffentlichen IP-Adresse und einem nicht gesperrten Port ist die Portfreigabe kostenlos und kaum zu schlagen. Spricht alles, was Sie betreiben, HTTP, ist Cloudflares Tunnel kostenlos und für diesen Zweck sehr gut. Ausführlicher geht es im Vergleich GetPublicIP vs. Cloudflare Tunnel vs. Tailscale um die Unterschiede.

Die vier Wege im Detail

Portfreigabe am Router

Jedes Protokoll, sofern Ihr Anbieter Ihnen eine echte öffentliche IP-Adresse gibt.

Geben Sie dem Docker-Host eine feste Adresse, veröffentlichen Sie den Container-Port mit einem ports:-Eintrag und richten Sie am Router eine Regel ein, die eingehenden Verkehr auf einem gewählten Port an diesen Host schickt. Das kostet nichts, vorausgesetzt, Ihr Anbieter hat Sie nicht hinter CGNAT gesetzt und sperrt den benötigten Port nicht; meist trifft es Port 25 für E-Mail und Port 80 für HTTP. Private Anschlüsse bekommen außerdem regelmäßig eine neue Adresse. Wenn eine Regel nicht wie erwartet funktioniert, geht der Leitfaden zur Fehlersuche bei der Portfreigabe die üblichen Ursachen der Reihe nach durch.

Cloudflare Tunnel

HTTP und HTTPS, wobei Cloudflare Ihr TLS terminiert.

Ein kleiner Daemon, der als weiterer Container im selben Compose-Projekt laufen kann, baut eine ausgehende Verbindung zum Netz von Cloudflare auf und hält sie offen. Weil diese Verbindung ausgehend ist, stört CGNAT nicht, und am Router ist nichts nötig. Das ist kostenlos, gut dokumentiert und für eine selbst gehostete Webanwendung eine wirklich gute Lösung. Die Grenzen sollten Sie aber kennen, bevor Sie sich festlegen: Ein Gameserver, Mailserver, DNS-Resolver oder alles, was rohes TCP oder UDP spricht, ist nicht vorgesehen. Cloudflare entschlüsselt Ihren Datenverkehr an seinem Edge und verschlüsselt ihn auf dem Weg zu Ihnen neu. Und Sie sind über einen geteilten Adressraum erreichbar, es gibt also keinen Reverse-DNS-Eintrag, den Sie setzen könnten.

Tailscale und andere Mesh-VPNs

Ihre eigenen Geräte, von überall. Kein öffentliches Hosting.

Tailscale baut aus Ihren eigenen Geräten ein privates Netz. Installieren Sie es auf dem Docker-Host, auf Laptop und Smartphone, melden Sie sich überall mit demselben Konto an, und die Geräte erreichen einander, egal ob NAT oder CGNAT dazwischen liegt. Um unterwegs auf die eigenen Services zuzugreifen, ist das von allen Wegen hier der geringste Aufwand. Verbinden können sich allerdings nur Geräte, die Sie selbst eingebunden haben, und genau das ist der Sinn des Konzepts: Ein Besucher, der nie von Ihnen gehört hat, erreicht Ihr Tailnet nicht. Websites, Gameserver und alles andere, das für andere Menschen gedacht ist, liegen daher außerhalb dessen, wofür es gemacht ist.

Eine eigene öffentliche IP-Adresse

Jedes Protokoll, auf einer Adresse, die Ihnen gehört.

Statt Ihren Stack über die Infrastruktur eines anderen erreichbar zu machen, bekommen Sie eine eigene öffentliche IPv4-Adresse, die an Ihren Rechner geleitet wird. Der Tunnel ist eine ausgehende Verbindung, CGNAT und Router-Konfiguration spielen also keine Rolle mehr. Am anderen Ende kommen gewöhnliche Pakete auf einer gewöhnlichen öffentlichen Adresse an: in welchem Protokoll auch immer, auf welchem Port Sie auch immer weiterleiten, ohne dass unterwegs etwas sie prüft oder umschreibt.

Welcher Weg zu dem passt, was Sie betreiben

Eine Webanwendung und sonst nichts. Cloudflare Tunnel, es sei denn, Sie legen Wert auf Privatsphäre und echte Ende-zu-Ende-Verschlüsselung.

Nur Ihre eigenen Geräte brauchen Zugriff. Tailscale. Öffentlich muss dann überhaupt nichts erreichbar sein.

Alles, was nicht HTTP ist, oder CGNAT plus mehr als Webverkehr. Eine Portfreigabe, wenn Ihr Anschluss es zulässt, sonst eine eigene öffentliche IP-Adresse.

Läuft Ihr Stack nicht in Docker, gelten dieselben vier Wege. Sie sind im Leitfaden Heimserver aus dem Internet erreichbar machen beschrieben.

So sieht das in einer Compose-Datei aus

Der Agent ist ein weiterer Service neben denen, die Sie schon betreiben. Er tritt demselben Netz bei wie Ihre anderen Container, hält den Tunnel offen und leitet die angegebenen Ports an die genannten Services weiter.

Zwei Ports auf Ihrer öffentlichen Adresse, per Name an den Service `web` geschickt. Die Mapping-Syntax beherrscht auch UDP, IPv6, Dual-Stack und Ziele außerhalb des Compose-Projekts; die Referenz dazu, mit einem vollständigen Beispiel, steht auf der Docker-Seite der Dokumentation.

Ihre eigenen Services bleiben unverändert. `web` ist oben einfach das, was Sie ohnehin betreiben: keine neuen Umgebungsvariablen, keine Labels, und es bekommt von alldem nichts mit.

services:
  web:
    image: nginx:alpine
    restart: unless-stopped

  getpublicip:
    image: ghcr.io/getpublicip/getpublicip:latest
    restart: unless-stopped
    environment:
      GETPUBLICIP_API_KEY: "${GETPUBLICIP_API_KEY}"
      GETPUBLICIP_MAPPINGS: "80/tcp->web:80; 443/tcp->web:443"
    cap_add:
      - NET_ADMIN
    devices:
      - /dev/net/tun
    sysctls:
      net.ipv4.ip_forward: 1
      net.ipv4.conf.all.src_valid_mark: 1
      net.ipv6.conf.all.forwarding: 1

Warum es keine ports:-Einträge gibt

Sehen Sie sich web in dieser Datei noch einmal an. Der Service veröffentlicht nichts. In einem Leitfaden darüber, wie man Container erreichbar macht, lohnt es sich, hier kurz innezuhalten.

Datenverkehr für Ihre öffentliche Adresse kommt im Agent-Container an, auf der Tunnel-Schnittstelle. Der Agent wendet dann eine Destination-NAT-Regel an: dieselbe Art von Regel, die Ihr Router bei einer Portfreigabe anwendet, nur dass sie im Container läuft statt am Rand Ihres Netzes. Sie ersetzt die Zieladresse des Pakets, also die öffentliche Adresse, durch die Adresse des Containers im Compose-Netz und reicht das Paket weiter.

Auf diesem Weg ist kein veröffentlichter Host-Port beteiligt. Der Port ist auf Ihrer öffentlichen IPv4-Adresse offen, nicht auf Ihrem Host und nicht an Ihrem Router. Ein Portscan Ihres Anschlusses zu Hause findet also nichts Neues, das lauscht. Ein ports:-Eintrag bei web würde den Service nur zusätzlich in Ihrem lokalen Netz veröffentlichen, meist das Gegenteil dessen, was Sie wollten.

Ziele werden als Servicenamen statt als Adressen angegeben, weil Compose die Container-Adressen nach eigenem Ermessen vergibt. Der Agent prüft diese Namen standardmäßig alle 30 Sekunden erneut, sodass ein Container, der nach einem Neustart eine andere Adresse hat, weiter erreichbar bleibt.

Was der Container braucht und was nicht

Drei Dinge, alle oben zu sehen: die Capability NET_ADMIN, damit er die Tunnel-Schnittstelle und die Weiterleitungsregeln konfigurieren kann; das Gerät /dev/net/tun, das ist der Tunnel selbst; und die sysctls für die Weiterleitung, damit Datenverkehr, der auf dem Tunnel ankommt, an einen anderen Container weitergegeben werden kann.

privileged: true braucht er nicht. Wenn ein Setup das verlangt, wird etwas umgangen statt konfiguriert. Ein WireGuard-Kernelmodul braucht er ebenfalls nicht: Das Image nutzt es, wenn der Kernel es anbietet, und weicht sonst auf das mitgelieferte Userspace-wireguard-go aus.

Was Sie bekommen, das die Tunnel-Dienste nicht bieten

Jedes Protokoll, nicht nur HTTP. Die Weiterleitung geschieht auf Paketebene, was darüber läuft, ist also Ihre Sache. Ein Minecraft-Server über UDP, ein Mailserver auf 25 und 587, SSH: Jedes davon ist dieselbe Art Mapping wie ein Webserver.

Eine Adresse, die Ihnen gehört. Kein Hostname auf geteilter Infrastruktur. Sie bleibt Ihre, wenn Sie den Stack auf einen anderen Rechner, zu einem anderen Anbieter oder in ein anderes Land umziehen, und DNS-Einträge, die darauf zeigen, überstehen den Umzug.

Reverse DNS. Weil die Adresse Ihnen zugeteilt ist, kann ein PTR-Eintrag für sie gesetzt werden. Die Zustellbarkeit von E-Mails hängt unbedingt davon ab, und deshalb sind Mailserver mit den anderen Ansätzen nicht machbar.

Ihr TLS bleibt Ihres. GetPublicIP leitet Pakete weiter und terminiert nie TLS, auf unserer Seite liegen also keine Zertifikate oder Schlüssel. Die Sitzung wird zwischen Ihrem Besucher und dem Container ausgehandelt, der sie bei Ihnen terminiert, und erst dort zum ersten Mal entschlüsselt. Dieser Service braucht ein eigenes gültiges Zertifikat, wie auf jedem öffentlichen Host.

Typische Stolperfallen

Ein API-Schlüssel steuert genau einen Tunnel

Ein WireGuard-Schlüssel gehört zu genau einem Endpunkt. Zwei Container, die mit demselben API-Schlüssel gestartet werden, werfen sich daher gegenseitig vom Netz, und jeweils gewinnt der letzte Handshake. Das zeigt sich als sporadische Aussetzer statt als klare Fehlermeldung, mit einem verlässlichen Anzeichen: Beide Container starten immer wieder neu, weil jeder zu dem Schluss kommt, er sei der defekte. Erstellen Sie auf der Seite zu API-Schlüsseln für jeden Tunnel einen eigenen Schlüssel.

restart: unless-stopped ist unverzichtbar

Die meisten Unterbrechungen beheben sich von selbst. Bei den selteneren Fehlern, die ein Neuverbinden nicht löst, beendet sich der Agent bewusst, statt einen halb defekten Tunnel vor Ort zu reparieren, und verlässt sich darauf, dass Docker ihn neu startet. Ohne Restart-Policy hält er an und bleibt gestoppt.

IPv6 muss im Netz aktiviert sein

Docker-Bridge-Netze transportieren ohne weitere Angabe nur IPv4, ein Container in einem Standardnetz hat also keine IPv6-Adresse, an die weitergeleitet werden könnte. Setzen Sie enable_ipv6: true und ein IPv6-Subnetz, bevor Sie IPv6-Mappings hinzufügen.

Windows braucht einen WSL2-Kernel 6.6

Docker Desktop betreibt Container in einer virtuellen WSL2-Maschine, und der Tunnel braucht ein Netfilter-Modul, das Kernel 5.15 nicht enthält. wsl --version zeigt, welche Version Sie haben, und wsl --update bringt Sie auf den neueren Stand.

Ihre Container sehen den Agent, nicht den Besucher

Standardmäßig wird die Absenderadresse eingehenden Verkehrs durch die des Agents ersetzt, sodass jeder Besucher in Ihren Logs mit derselben lokalen Adresse erscheint. Das ist für Geolokalisierung, Rate Limiting und fail2ban relevant. GETPUBLICIP_PRESERVE_CLIENT_IP schaltet das Umschreiben ab und erfordert zusätzlich eine Routenänderung in jedem Ziel-Service: Sobald Antworten an echte Clients im Internet adressiert sind, entscheidet die Standardroute des Service, wohin sie gehen, nicht mehr sein eigenes Subnetz. Die Docker-Dokumentation beschreibt das; die Einstellung allein hinterlässt einen Service, der hängt, während die Logs weiter gesund aussehen.

Unverschlüsseltes HTTP bleibt unverschlüsselt

Der Tunnel verschlüsselt die Strecke zwischen unserem Edge und Ihrem Rechner. Für die Strecke zwischen Ihrem Besucher und der öffentlichen Adresse, also das Internet, tut er nichts. Liefern Sie HTTPS aus Ihrem eigenen Container aus.

Häufig gestellte Fragen

Wie mache ich einen Docker Container aus dem Internet erreichbar?

Es gibt vier praktikable Wege. Eine Portfreigabe funktioniert, wenn Ihr Internetanbieter Ihnen eine echte öffentliche IP-Adresse gibt und den Port nicht sperrt. Cloudflare Tunnel veröffentlicht HTTP und HTTPS über eine ausgehende Verbindung. Tailscale verbindet Ihre eigenen Geräte privat miteinander. Eine eigene öffentliche IP-Adresse leitet eine echte IPv4-Adresse über einen WireGuard-Tunnel an Ihren Stack weiter, transportiert jedes Protokoll und kommt ohne geöffnete Ports am Router aus.

Kann ein Docker Container eine eigene öffentliche IP-Adresse haben?

Nicht die Adresse Ihres Internetanbieters, denn die hält Ihr Router stellvertretend für alle Geräte dahinter. Sie können sich aber eine eigene öffentliche IPv4-Adresse über einen WireGuard-Tunnel auf den Rechner leiten lassen und von dort einzelne Ports anhand des Servicenamens an Container weiterleiten. Die Adresse gehört dann Ihnen, nicht einem einzelnen Container.

Kann ich einen Docker-Compose-Service ohne Portfreigabe erreichbar machen?

Ja. Ein Tunnel erreicht Ihren Stack über eine Verbindung, die Ihr Rechner nach außen aufbaut. Es gibt also keinen eingehenden Port, den Sie freigeben müssten, und am Router ist nichts zu ändern. Mit GetPublicIP brauchen die erreichbaren Services auch keine veröffentlichten Ports, weil der Datenverkehr auf der Tunnel-Schnittstelle ankommt und nicht an einem auf dem Host veröffentlichten Port.

Wie mache ich Docker Container erreichbar, wenn mein Anbieter CGNAT nutzt?

Bei Carrier-Grade NAT bekommt Ihr Router eine private statt einer öffentlichen Adresse, deshalb hilft keine Regel, die Sie zu Hause einrichten. Alles, was funktioniert, beruht auf einer ausgehenden Verbindung Ihres Rechners: Cloudflare Tunnel für HTTP und HTTPS, Tailscale für den privaten Zugriff zwischen Ihren eigenen Geräten, eine eigene öffentliche IP-Adresse für jedes Protokoll.

Braucht der GetPublicIP-Container den privileged-Modus?

Nein. Er braucht die Capability NET_ADMIN, das Gerät /dev/net/tun und die sysctls für die Weiterleitung, sonst nichts. Ein WireGuard-Kernelmodul auf dem Host ist ebenfalls nicht nötig, denn das Image weicht auf die Userspace-Implementierung wireguard-go aus, wenn das Kernelmodul nicht verfügbar ist.

Funktioniert das unter Windows und macOS?

Ja, mit Docker Desktop, und die Compose-Datei ist auf allen drei Plattformen dieselbe. Unter macOS und Windows laufen Ihre Container in einer Linux-VM, daher stammt das Tunnel-Gerät aus dieser VM. Prüfen Sie unter Windows die Version des WSL2-Kernels: Version 5.15 fehlt ein Netfilter-Modul, das der Tunnel braucht, Version 6.6 bringt es mit.

Wie es weitergeht

Wenn eine eigene öffentliche IP-Adresse zu dem passt, was Sie betreiben, finden Sie in der Docker-Dokumentation die vollständige Referenz: die gesamte Mapping-Syntax, ein funktionierendes Beispiel, IPv6, die Weiterleitung an einen Service auf dem Docker-Host und die Hinweise für die einzelnen Plattformen.

Anleitungen und Kategorien