Traefik als Reverse Proxy: vier Services hinter einer Domain mit automatischem HTTPS
Minimaler Traefik-v3-Stack mit Docker Compose: vier Services hinter einer Domain, TLS automatisch via Let's Encrypt, inkl. typischer Stolperfallen.
Wer mehrere Container auf einem Host betreibt, steht schnell vor der Frage: Wie kommen die alle hinter eine Domain, jeweils mit gültigem HTTPS? Traefik löst das elegant. Es liest Routing-Regeln direkt aus Docker-Labels, holt sich Zertifikate selbstständig von Let's Encrypt und braucht für ein Grundsetup keine einzige Konfigurationsdatei. Dieser Beitrag zeigt einen lauffähigen Minimal-Stack mit Traefik v3, der vier Services hinter einer Domain bedient – in unter 15 Minuten aufgesetzt.
Was Traefik macht – und wann es passt
Traefik ist ein Reverse Proxy, der seine Konfiguration dynamisch aus einem Provider bezieht. Beim Docker-Provider beobachtet Traefik den Docker-Socket: Startet ein Container mit passenden Labels, erscheint die Route sofort – kein Reload, kein Neustart. Das passt überall dort, wo Sie mehrere containerisierte Services hinter einer oder mehreren Domains veröffentlichen und sich nicht um TLS-Zertifikate kümmern möchten.
Wichtig ist die Trennung in zwei Ebenen. Die statische Konfiguration legt fest, wie Traefik selbst startet – Entrypoints, Provider und Zertifikats-Resolver. Sie ändert sich selten und wird hier über CLI-Flags gesetzt. Die dynamische Konfiguration beschreibt die eigentlichen Routen und wird zur Laufzeit aus den Container-Labels gelesen. Diese Trennung erklärt, warum neue Services ohne Traefik-Neustart auftauchen: Nur die dynamische Ebene ändert sich.
Der Datenfluss sieht so aus:
+-----------------------+
Internet --:443--> | Traefik |
(HTTPS) | entrypoint websecure |
+-----------+-----------+
|
+-----------+-----------+-----------+-----------+
| | | |
api.example app.example www.example www.example
| | | /docs
[ api ] [ app ] [ web ] [ docs ]
Der minimale Stack
Wir brauchen genau eine Datei. Traefik selbst wird über CLI-Flags (die statische Konfiguration) gesteuert; die Routen kommen über Labels (die dynamische Konfiguration) von den vier Services.
networks:
proxy:
services:
traefik:
image: traefik:v3.7
command:
# Docker-Provider: Routen aus Labels lesen
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=proxy"
# Entrypoints: HTTP und HTTPS
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
# HTTP global auf HTTPS umleiten
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
# Let's Encrypt via TLS-ALPN-Challenge
- "--certificatesresolvers.le.acme.email=admin@example.com"
- "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.le.acme.tlschallenge=true"
ports:
- "80:80"
- "443:443"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "./letsencrypt/acme.json:/letsencrypt/acme.json"
networks:
- proxy
api:
image: traefik/whoami
networks: [proxy]
labels:
- "traefik.enable=true"
- "traefik.http.routers.api.rule=Host(`api.example.com`)"
- "traefik.http.routers.api.entrypoints=websecure"
- "traefik.http.routers.api.tls.certresolver=le"
- "traefik.http.services.api.loadbalancer.server.port=80"
app:
image: traefik/whoami
networks: [proxy]
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`app.example.com`)"
- "traefik.http.routers.app.entrypoints=websecure"
- "traefik.http.routers.app.tls.certresolver=le"
- "traefik.http.services.app.loadbalancer.server.port=80"
web:
image: nginx:alpine
networks: [proxy]
labels:
- "traefik.enable=true"
- "traefik.http.routers.web.rule=Host(`www.example.com`)"
- "traefik.http.routers.web.entrypoints=websecure"
- "traefik.http.routers.web.tls.certresolver=le"
- "traefik.http.services.web.loadbalancer.server.port=80"
docs:
image: nginx:alpine
networks: [proxy]
labels:
- "traefik.enable=true"
- "traefik.http.routers.docs.rule=Host(`www.example.com`) && PathPrefix(`/docs`)"
- "traefik.http.routers.docs.entrypoints=websecure"
- "traefik.http.routers.docs.tls.certresolver=le"
- "traefik.http.services.docs.loadbalancer.server.port=80"
Die Labels Zeile für Zeile
Jeder Service bekommt vier zusammengehörige Labels:
routers.<name>.ruledefiniert, welche Anfragen der Service bekommt.Host(...)matcht auf den Hostnamen,&&kombiniert mitPathPrefix(...). Derdocs-Router teilt sich den Host mitweb, gewinnt aber bei/docs, weil Traefik die spezifischere (längere) Regel höher priorisiert.routers.<name>.entrypoints=websecurebindet die Route an Port 443.routers.<name>.tls.certresolver=leweist Traefik an, für diesen Host ein Zertifikat über den oben definierten Resolverlezu holen.services.<name>.loadbalancer.server.portsagt Traefik, auf welchem internen Port der Container lauscht (hier 80 fürwhoamiundnginx).
Beachten Sie exposedbydefault=false: Ohne traefik.enable=true wird ein Container nicht veröffentlicht. Das ist die sichere Variante – nichts landet versehentlich im Netz.
Starten
Die Zertifikatsdatei muss vor dem ersten Start existieren und korrekte Rechte haben:
mkdir -p letsencrypt
touch letsencrypt/acme.json
chmod 600 letsencrypt/acme.json
docker compose up -d
Sobald die DNS-A-Records von api., app. und www.example.com auf den Host zeigen, beantragt Traefik beim ersten Aufruf automatisch die Zertifikate. Ein Aufruf von https://api.example.com liefert die whoami-Ausgabe mit gültigem HTTPS. Voraussetzung für die hier genutzte TLS-ALPN-Challenge ist, dass Port 443 von außen erreichbar ist und exklusiv Traefik gehört – die Challenge läuft über genau diesen Port.
Was Traefik aus den Labels gemacht hat, sehen Sie in den Logs (docker compose logs -f traefik). Dort taucht jeder neue Router und jeder Zertifikatsantrag auf – die erste Anlaufstelle, wenn eine Route nicht greift.
Drei typische Stolperfallen
1. Gemeinsames Docker-Netz. Traefik erreicht einen Service nur, wenn beide im selben Netzwerk hängen. Hier sorgt --providers.docker.network=proxy plus networks: [proxy] an jedem Service dafür. Fehlt das, sehen Sie ein 502 Bad Gateway, obwohl die Route existiert.
2. Let's-Encrypt-Limits beim Testen. Die Produktiv-CA hat strenge Rate-Limits. Testen Sie zuerst gegen die Staging-Umgebung – ein zusätzliches Flag --certificatesresolvers.le.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory. Die Zertifikate sind dann nicht vertrauenswürdig, aber Sie verbrennen kein Kontingent. Für den Echtbetrieb das Flag entfernen und acme.json einmal leeren.
3. Rechte auf acme.json. Traefik verweigert den Start, wenn die Datei offener als 600 ist ("permissions are too open"). Legen Sie sie deshalb wie oben gezeigt als Datei an und mounten Sie sie als Datei – nicht als Verzeichnis, dessen Rechte sich unkontrolliert vererben.
Wie es weitergeht
Damit steht ein produktionsnaher Reverse Proxy. Naheliegende nächste Schritte: das Dashboard absichern, Middlewares für Header, Rate-Limiting oder Basic-Auth ergänzen und auf die HTTP-Challenge wechseln, falls Port 443 nicht exklusiv für Traefik frei ist. Die Label-Referenz für den Docker-Provider finden Sie in der offiziellen Dokumentation.