Bei der lokalen Verwendung von Docker-Containern ist der übliche Weg, einen Port freizugeben und über localhost:port
auf den Dienst zuzugreifen. Das funktioniert normalerweise, ist aber natürlich keine gute Lösung. Ein paar Nachteile sind mitunter:
- Schneller Verlust des Überblicks über zugewiesene Ports, insbesondere bei mehreren Containern
- Zugriff über HTTPS nur, wenn der Container entsprechend konfiguriert wurde - und selbst dann gibt es nur ein selbstsigniertes Zertifikat, das eine Sicherheitswarnung im Webbrowser auslöst
- Einträge von Passwortmanagern sind schwieriger zu trennen, wenn alle Dienste über
localhost
statt über eine eindeutige Domain laufen
Ein besserer Weg wäre die Verwendung von Subdomains und gültigen TLS-Zertifikaten. Es würde jedoch einen höheren Verwaltungsaufwand bedeuten, jedem Dienst ein eigenes Zertifikat von einer lokalen Zertifizierungsstelle zuzuweisen, und man müsste sich selbst um die CA und die Signierung kümmern.
Um diesen Prozess zu vereinfachen, kann ein Reverse Proxy wie Caddy verwendet werden.
Reverse Proxy
Ein Reverse Proxy ist ein Server, der vor anderen Webservern sitzt und Anfragen von Clients an diese Webserver weiterleitet. Daher kümmern sie sich oft auch um sicherheitsrelevante Komponenten der Kommunikation, wie die TLS-Terminierung von HTTPS-Verbindungen.
Der Anwendungsfall in diesem Zusammenhang ist ähnlich. Wir verwenden einen Reverse-Proxy, um Anfragen an localhost
zu verarbeiten. Dienste in Docker-Containern müssen dabei keinen Port mehr freigeben, die Kommunikation zwischen Proxy und Dienst läuft über ein internes Docker-Netzwerk.
Warum Caddy?
Caddy ist ein moderner Webserver, der eine vereinfachte Konfiguration mit automatischem HTTPS bietet. Ein Reverse Proxy-Block in einem Caddyfile sieht beispielsweise so aus:
|
|
Mehr braucht es nicht. Caddy generiert automatisch die TLS-Zertifikate über Let’s Encrypt und setzt die üblichen Header eines Reverse Proxys, was es zu einer einfachen, aber mächtigen Alternative zu den bekannten Lösungen wie Nginx macht.
Caddy-Modul: Caddy-Docker-Proxy
Ein nützliches Modul zur Verwendung mit Docker-Diensten ist Caddy-Docker-Proxy. Es scannt Metadaten und sucht nach Labels, die darauf hinweisen, dass der Dienst von Caddy bedient werden soll. Aus diesen Labels wird ein Caddyfile mit den entsprechenden Einträgen erstellt, was die manuelle Verwaltung für Docker-Container überflüssig macht. Einträge für Dienste außerhalb von Docker können weiterhin über ein Caddyfile verwaltet werden.
Anweisungen, wie die Anweisungen eines Caddyfiles in Label umgewandelt werden können, findet man im Repository.
Beispiel
Dieses Beispiel startet traefik/whoami und fügt es zu einem bestehenden Proxy-Netzwerk hinzu. Nach dem Start ist der Container über https://whoami.dev.internal
erreichbar, gesichert mit einem von Caddys interner Root CA signierten TLS-Zertifikat.
|
|
Caddy mit Docker Compose bereitstellen
Zuerst erstellen wir ein Proxy-Netzwerk. Dieses Netzwerk wird extern erstellt, um sicherzustellen, dass der Dienst diesem beitreten kann, auch wenn der Caddy-Stack nicht läuft.
Info
Mit einem geteilten Proxy-Netzwerk können die Dienste direkt miteinander kommunizieren. Wenn dieses Verhalten verhindert werden soll, sollte für jeden Dienst ein eigenes Proxy-Netzwerk erstellt werden.
|
|
Anschließend wird folgenden Stack in einer Datei gespeichert, der Standardname ist docker-compose.yml
. Wenn ein anderer Name für die Datei verwendet wird, muss dieser beim Aufruf von docker compose
explizit angegeben werden.
|
|
Schließlich kann der Stack mit docker compose up -d
gestartet werden.
Caddys Root-Zertifikat vertrauen
Damit der Computer dem von Caddy ausgestellten Zertifikat vertrauen kann, muss er der gesamten Zertifikatskette vertrauen. Mit einem lokalen Caddy könnte man caddy trust
ausführen, um das Root-Zertifikat in den Vertrauensspeicher des Systems zu installieren.
Mit Docker ist der Container vom System isoliert und hat keinen direkten Zugriff darauf. Das Root-Zertifikat muss manuell kopiert werden und es muss in den Vertrauensspeicher des Systems oder des Browsers installiert werden. Anleitungen für Linux, Mac und Windows können in der Dokumentation von Caddy gefunden werden.
Für die meisten Linux-Systeme lauten die Befehle beispielsweise:
|
|
Arch Linux
Die Art, wie lokale vertrauenswürdige Zertifikate gehandhabt werden, hat sich 2014 geändert. Die entsprechenden Befehle für Arch Linux wären beispielsweise:
|
|
Ein Docker-Volume mit Caddys Root-Zertifikat erstellen
Wenn ein Container mit anderen Diensten über Caddy kommunizieren muss und dabei die Gültigkeit des Zertifikats überprüft, muss auch er der Zertifikatskette vertrauen.
Die folgenden Befehle erstellen ein Docker-Volume namens caddy_root_ca
, das nur das Root-Zertifikat enthält und in andere Container gemountet werden kann. Dort muss dann nur der Vertrauensspeicher aktualisiert werden, was entweder manuell oder durch Überschreiben von entrypoint
oder command
getan werden kann.
|
|
Damit der Container über Caddy auf den anderen Dienst zugreifen kann, muss ein Alias gesetzt werden.
Verkürztes, unvollständiges Beispiel für einen Dienst, der über https://service.dev.internal
erreichbar ist und von einem anderen Container über Caddy angesprochen werden kann:
|
|
Nach der Aktualisierung des Vertrauensspeichers kann ‘client’ nun mit ‘web’ über eine vertraute HTTPS-Verbindung über Caddy kommunizieren.
Lokale Domain
Mit unserem Reverse Proxy kann für jeden Dienst eine eigene Subdomain verwendet werden.
Statt in /etc/hosts
jeden Eintrag manuell hinterlegen zu müssen, werden wir einen Wildcard-DNS-Eintrag für die Subdomain dev.internal
verwenden. Da .internal
von der ICANN als Domain für private Anwendungen reserviert wurde, ist die Nutzung problemlos möglich. Durch die Reservierung ist garantiert dass die Domain nicht als Top-Level-Domain im DNS des Internets installiert wird.
Durch diesen Eintrags werden die Domain selbst und alle Subdomains nach localhost
aufgelöst.
Info
Generell kann jede beliebige Domain verwendet werden. Die Verwendung einer bestehenden, global gerouteten Domain kann zu Problemen bei der Namensauflösung und damit der Erreichbarkeit führen.
Aber auch eine TLD, die noch nicht im DNS des Internets registriert ist, sollte nur mit Vorsicht verwendet werden, solange sie nicht wie .internal
explizit reserviert wurde.
Linux mit NetworkManager
- dnsmasq installieren
- DNS-Resolver von NetworkManager änden
|
|
- DNS-Einträge hinzufügen
|
|
- NetworkManager neu laden
|
|
MacOS
- Wenn noch nicht geschehen: Homebrew installieren
- dnsmasq installieren
|
|
- DNS-Einträge hinzufügen
|
|
- Autostart aktivieren
|
|
- Zu Resolvern hinzufügen
|
|
Setup testen
Zunächst sollte getestet werden, ob die Domain-Auflösung wie vorgesehen funktioniert. Dazu ein Terminal öffnen und dig <domain>
(Linux) oder dscacheutil -q host -a name <domain>
(MacOS) verwenden.
Sowohl für die Domain selbst (z.B. dev.internal
) als auch für eine beliebige Subdomain davon (z.B. a.dev.internal
) sollten 127.0.0.1
für IPv4 und ::1
für IPv6 zurückkommen.
Dann kann das oben erwähnten Beispiel „whoami“ gestartet werden. Nach dem Start des Containers sollte der Dienst über die angegebene URL (wie z.B. https://whoami.dev.internal) mit gültigen HTTPS ohne Sicherheitswarnungen bedient werden.