Traefik SSL Reverse Proxy für Docker Container
Traefik Proxy ist super geeignet um diverse Docker Container von außen erreichbar zu machen, ohne jeden einzelnen mittels Port Weiterleitung freizugeben. Man muss so nicht Unmengen von Ports auf dem localhost managen, sondern kann einfach mittel HTTP Reverse Proxy die Webapplikationen nach außen freigeben. Zusätzlich bietet Traefik Proxy auch eine SSL Lösung mit LetsEncrypt Zertifikaten. Man kann aber auch einfach für interne Zwecke ein Self Signed Zertifikat verwenden. Ich zeige hier, wie man mittels docker compose einen Traefik Proxy deployt und verschiedene Wege, wie man andere Docker Applikationen damit für die Außenwelt freigibt.
Traefik Docker-Compose Projekt
Zuerst legen wir unser Docker Compose Projekt an. Dafür erstellen wir die folgende Ordner Struktur und wechseln in das Projekt Verzeichnis.
mkdir -p /opt/traefik/data/conf/certs
mkdir -p /opt/traefik/data/conf/dynamic
cd /opt/traefik/
Da wir ein Docker Netzwerk brauchen, welches Traefik mit den einzelnen Containern verbinden, sozusagen das Transfer Netz, erstellen wir ein persistentes Netzwerk mittels docker
Befehl. Das Netzwerk heißt dann traefik-nw
:
docker network create --driver=bridge --attachable --internal=false -o "com.docker.network.bridge.name"="br-traefik" traefik-nw
Nun erstellen wir unser docker-compose.yml
:
- Traefik latest Image
- Ports 80 / 443 und 8080 werden zum Host freigegeben
- Der Container soll im Falle eines Ausfalles neugestartet werden
- Mapping der Docker Socket Datei um Informationen zu den Container zu bekommen
- Configurations Dateien und der Ordner für die SSL Zertifikate werden ebenfalls in den Container gemappt
- Das oben angelegte
traefik-nw
Netzwerk wird verwendet (external
ist hier sehr wichtig!) - Optional: das Label
"com.centurylinklabs.watchtower.enable=true"
sorgt dafür das watchtower den Container aktualisiert sollte es ein neues Image vontraefik:latest
geben.
/opt/traefik/docker-compose.yml
version: '3.7'
services:
traefik:
image: traefik:latest
restart: always
ports:
- "80:80"
- "443:443"
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./data/conf/traefik.yml:/etc/traefik/traefik.yml:ro
- ./data/conf/dynamic:/etc/traefik/dynamic:ro
- ./data/certs:/etc/traefik/certs:ro
environment:
- TZ=Europe/Berlin
labels:
- "com.centurylinklabs.watchtower.enable=true"
networks:
- traefik-nw
networks:
traefik-nw:
external:
name: traefik-nw
Self Signed SSL Zertifikat
Nun erstellen wir uns für unseren Host ein Self Signed SSL Zertifikat mittels openssl
.
Sollte openssl noch nicht installiert sein:
apt install openssl -y
Tauscht hier bitte yourFQDN mit eurem FQDN des Servers/Rechners/Raspberry Pi aus (zum Beispiel: traefik.testdomain.local):
cd /opt/traefik
openssl req -new -newkey rsa:4096 -x509 -days 3650 -nodes -out data/certs/yourFQD.crt -keyout data/certs/yourFQDN.key
dann die Fragen einfach ausfüllen. Das einzig wichtige ist, tragt den richtigen FQDN ein:
Generating a RSA private key ........................................................................++++ ................................................................................................................................++++ writing new private key to 'data/certs/yourFQDN.key' ----- You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [AU]:DE State or Province Name (full name) [Some-State]:BaWu Locality Name (eg, city) []:Karlsruhe Organization Name (eg, company) [Internet Widgits Pty Ltd]:laub-home Organizational Unit Name (eg, section) []: Common Name (e.g. server FQDN or YOUR name) []:traefik.testdomain.local Email Address []:
Traefik Konfiguration
Nun kommen wir zu den Konfigurationsdateien traefik.yml
und dynamic_conf.yml
.
/opt/traefik/data/conf/traefik.yml
global:
checkNewVersion: true
sendAnonymousUsage: false
entryPoints:
web:
address: :80
http:
redirections:
entryPoint:
to: websecure
scheme: https
permanent: true
websecure:
address: :443
http:
tls: true
api:
insecure: true
providers:
docker:
exposedByDefault: false
file:
directory: /etc/traefik/dynamic
watch: true
Hier passiert das folgende:
- Entrypoints 80 (HTTP, Name: web) und 443 (HTTPS, Name: websecure) werden angelegt
- Entrypoint 80/web wird immer auf 443/websecure umgeleitet, somit ist alles immer über HTTPs erreichbar
- Die API wird insecure freigegeben
- Wir binden Docker und die Datei dynamic_conf.yml als Provider ein
- Docker Container werden nicht standardmäßig eingelesen, nur wenn diese das Label
"traefik.enable=true"
haben.
/opt/traefik/data/conf/dynamic/dynamic_conf.yml
# Dynamic configuration
# SSL Certs
tls:
certificates:
- certFile: /etc/traefik/certs/yourFQDN.crt
keyFile: /etc/traefik/certs/yourFQDN.key
Hier werden nun die Zertifikate eingebunden.
Mehr Informationen zu den Konfigurationsmöglichkeiten:
Starten des Traefik Proxy
nun ist es soweit, den ersten Start von Traefik auszuführen und zu testen ob das Traefik Dashboard erreichbar ist:
docker-compose up -d
Wenn alles passt sollte nun das Dashboard unter:
- http://yourFQDN:8080
erreichbar sein.
Whoami Test Container
Nun wollen wir natürlich testen ob wir einen Container erfolgreich über den Traefik Proxy laufen lassen können. Hierfür verwende ich whoami
. Ein einfacher NGINX Webserver der den Containernamen Ausspuckt.
mkdir -p /opt/whoami
cd /opt/whoami
/opt/whoami/docker-compose.yml
version: '3.7'
services:
whoami-traefik-test:
# Raspberry Pi Image
image: hypriot/rpi-whoami
# i386 image
#image: containous/whoami:latest
restart: always
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami-tls.rule=PathPrefix(`/whoami`)"
- "traefik.http.routers.whoami-tls.entrypoints=websecure"
- "traefik.http.routers.whoami-tls.tls=true"
networks:
- traefik-nw
networks:
traefik-nw:
external:
name: traefik-nw
Was passiert hier:
- Wir nehmen ein whoami Docker Image und starten es
- Wir aktivieren den Traefik:
"traefik.enable=true"
- Wir wollen das die Applikation unter dem Port 443/TLS und dem Subpfad /whoami verfügbar ist
- somit also unter https://yourFQDN/whoami erreichbar ist
- Auch hier wird dem Kontainer unser Traefik Netzwerk (
traefik-nw
) mitgegeben
nun starten wir das Docker Compose Projekt:
docker-compose up -d
Nun sollten wir beim Aufruf von:
- http://yourFQDN/whoami/
direkt eine Weiterleitung nach https geschehen, was sicherlich ersteinmal zu einem Zertifikatsfehler im Browser führt da es ein Self Signed Zertifikat ist. Deshalb das Zertifikat akzeptieren. Dann solltet ihr aber folgenden Output im Browser sehen:
I'm 24c89d83bb4d
Im Traefik dashboard sollte nun auch unter HTTP der Status angezeigt werden:
Weitere Beispiele für Subpfad Freigaben
hier ein paar weitere Label Beispiele für Applikationen die ich per Subpfad freigegeben habe.
Grafana
Die folgende Konfiguration bringt Grafana dazu vom Traefik als Subpath /grafana
freigegeben zu werden. Hierzu müssen ebenfalls die Environment Variablen von Grafana erweitert werden, da man hier ebenfalls den subpath einrichten muss.
environment:
- GF_RENDERING_CALLBACK_URL=http://grafana:3000/grafana/
- GF_SERVER_SERVE_FROM_SUB_PATH=true
- GF_SERVER_ROOT_URL=https://yourFQDN/grafana
labels:
- "traefik.enable=true"
- "traefik.http.routers.grafana.rule=PathPrefix(`/grafana`)"
- "traefik.http.routers.grafana.entrypoints=websecure"
- "traefik.http.routers.grafana.tls=true"
- "traefik.http.routers.grafana.middlewares=addtrailingslash@file"
- "traefik.docker.network=traefik-nw"
Auch der Renderer muss wissen das Grafana nun unter /grafana
lauscht.
Erreichbar ist das nun unter:
- http://yourFQDN/grafana/
Chronograf
Auch bei Chronograf muss ähnlich wie bei Grafana, der Subpath auch als Pfad Variable chronograf mitgegeben werden. Die geschieht mittel Environment Variable:
environment:
- BASE_PATH=/chronograf
labels:
- "traefik.enable=true"
- "traefik.http.routers.chronograf.rule=PathPrefix(`/chronograf`)"
- "traefik.http.routers.chronograf.entrypoints=websecure"
- "traefik.http.routers.chronograf.tls=true"
- "traefik.http.routers.chronograf.middlewares=addtrailingslash@file"
- "traefik.docker.network=traefik-nw"
Erreichbar ist das nun unter:
- http://yourFQDN/chronograf/
Portainer
Dies sind die Labels mit denen man Portainer via Traefik und Subpath freigeben kann.
lables:
- "traefik.enable=true"
- "traefik.http.routers.portainer.rule=PathPrefix(`/portainer`)"
- "traefik.http.routers.portainer.entrypoints=websecure"
- "traefik.http.routers.portainer.tls=true"
- "traefik.http.routers.portainer.middlewares=portainer-stripprefix"
- "traefik.http.middlewares.portainer-stripprefix.stripprefix.prefixes=/portainer"
- "traefik.docker.network=traefik-nw"
Aufrufbar ist das Ganze nun unter:
- http://yourFQDN/portainer/
Smokeping
Dies sind die Labels mit denen man Smokeping via Traefik und Subpath freigeben kann.
labels:
- "traefik.enable=true"
- "traefik.http.routers.smokeping.rule=PathPrefix(`/smokeping`)"
- "traefik.http.routers.smokeping.entrypoints=websecure"
- "traefik.http.routers.smokeping.tls=true"
- "traefik.docker.network=traefik-nw"
Aufrufbar ist das Ganze nun unter:
- http://yourFQDN/smokeping/
Pi-hole
Dies sind die Labels mit denen man Pi-hole via Traefik und Subpath freigeben kann.
labels:
- "traefik.enable=true"
- "traefik.http.routers.pihole.rule=PathPrefix(`/pihole`)"
- "traefik.http.routers.pihole.entrypoints=websecure"
- "traefik.http.routers.pihole.tls=true"
- "traefik.http.routers.pihole.middlewares=pihole-stripprefix, pihole-addprefix"
- "traefik.http.middlewares.pihole-stripprefix.stripprefix.prefixes=/pihole"
- "traefik.http.middlewares.pihole-stripprefix.stripprefix.forceSlash=false"
- "traefik.http.middlewares.pihole-addprefix.addprefix.prefix=/admin"
- "traefik.http.services.pihole.loadbalancer.server.port=80"
- "traefik.docker.network=traefik-nw"
Aufrufbar ist das Ganze nun unter:
- http://yourFQDN/pihole/
Sickbeard
Docker Labels:
labels:
- "traefik.enable=true"
- "traefik.http.routers.sickbeard.rule=PathPrefix(`/sickbeard`)"
- "traefik.http.routers.sickbeard.entrypoints=websecure"
- "traefik.http.routers.sickbeard.tls=true"
Neben den Docker Labels muss die Sickbeard config.ini noch angepasst werden:
[General]
..
web_root = "/sickbeard"
..
Aufrufbar ist das Ganze nun unter:
- http://yourFQDN/sickbeard
Sonarr
Docker Labels:
labels:
- "traefik.enable=true"
- "traefik.http.routers.sonarr.rule=PathPrefix(`/sonarr`)"
- "traefik.http.routers.sonarr.entrypoints=websecure"
- "traefik.http.routers.sickbeard.tls=true"
Neben den Docker Labels muss die Sickbeard config.xml noch angepasst werden:
<UrlBase>/sonarr</UrlBase>
Aufrufbar ist das Ganze nun unter:
- http://yourFQDN/sonarr
AdGuard Home
Dies sind die Labels mit denen man AdGuard Home via Traefik und Subpath freigeben kann.
labels:
- "traefik.enable=true"
- "traefik.http.routers.adguardhome.rule=PathPrefix(`/adguardhome`)"
- "traefik.http.routers.adguardhome.entrypoints=websecure"
- "traefik.http.routers.adguardhome.tls=true"
- "traefik.http.routers.adguardhome.middlewares=addtrailingslash@file, adguardhome-stripprefix"
- "traefik.http.middlewares.adguardhome-stripprefix.stripprefix.prefixes=/adguardhome"
- "traefik.http.middlewares.adguardhome-stripprefix.stripprefix.forceSlash=false"
- "traefik.http.services.adguardhome.loadbalancer.server.port=80"
- "traefik.docker.network=traefik-nw"
Leider ist das Ganze nicht ganz so gut benutzbar. Man muss beim ersten Login /login.html mit aufrufen. Ansonsten landet man wieder auf /
Aufrufbar ist das Ganze nun unter:
- http://yourFQDN/adguardhome/login.html
Automatischen Slash (/) am Ende einfügen
Möchte man automatisch einen Slash an den Subpfad einfügen, also den sogenannten Trailing Slash, kann man dies über eine Traefik Middleware konfigurieren, hierfür gibt es zwei Möglichkeiten, via Docker Label oder Global in der Konfigurationsdatei.
Die Docker Labels, die man hinzufügen muss sehen so aus:
docker-compose.yml
- "traefik.http.routers.whoami-tls.middlewares=whoami-addtrailingslash"
- "traefik.http.middlewares.whoami-addtrailingslash.redirectregex.regex=^(https?://[^/]+/[a-z0-9_]+)$$"
- "traefik.http.middlewares.whoami-addtrailingslash.redirectregex.replacement=$${1}/"
tauscht hier einfach whoami
durch euren Applikationsnamen aus.
Via Konfigurationsdatei sieht das ganze so aus:
/opt/traefik/data/conf/dynamic/dynamic_conf.yml
# Middleware
http:
middlewares:
addtrailingslash:
redirectregex:
regex: "^(https?://[^/]+/[a-z0-9_]+)$"
replacement: "${1}/"
permanent: true
dann einfach der gewünschten Applikation das folgende Label hinzufügen:
docker-compose.yml
- "traefik.http.routers.whoami-tls.middlewares=addtrailingslash@file"
nun kann man einfach beim Aufrufen der URL den letzten / weglassen:
- http://yourFQDN/whoami
und wird dann automatisch auf
- http://yourFQDN/whoami/
umgeleitet.
man muss beim Aufruf der Middleware auf die Reihenfolge achten. Am besten addtrailingslash
an den Anfang stellen, hier das Beispiel von Pi-hole:
- "traefik.http.routers.pihole.middlewares=addtrailingslash@file, pihole-stripprefix, pihole-addprefix"
Basic Auth
Über folgende Middleware Konfiguration können Host/ Pfade auch mit einer Basic-Auth versehen werden:
- "traefik.http.middlewares.<rulename>.basicauth.users=<user>:<password>"
- "traefik.http.routers.<routername>.middlewares=<rulename>"
Passwörter müssen hier als Hash (MD5, SHA1 oder BCrypt) angegeben werden.
Bei der Verwendung von docker-compose ist zu beachten, dass das Zeichen $
in docker-compose zu Interpolation von Variabeln genutzt wird und daher überall mit einem zusätzlich Vorangestellten $
maskiert werden muss - das Ganze lässt sich als Einzeiler direkt bei der Passworterstellung umsetzen:
openssl passwd -apr1 | sed -E "s:[\$]:\$\$:g"
Komplettes Beispiel:
- "traefik.enable=true"
- "traefik.http.routers.dashboard.entrypoints=dashboard"
- "traefik.http.routers.dashboard.rule=Host(`dashboard.myhost.local`)"
- "traefik.http.routers.dashboard.service=api@internal"
- "traefik.http.routers.dashboard.middlewares=dashboard-auth"
- "traefik.http.middlewares.dashboard-auth.basicauth.users=admin:$$apr1$$REa/w1.F$$WxhZAj.S74o8IPLDn4r0d/"
Plugins
rewritebody
Plugin um mittels RegEx Response Content umzuschreiben.
- Traefik Pilot und Plugins aktivieren, z.B. in der statischen Konfiguration traefik.yml:
pilot: token: "PILOT_INSTANCE_KEY" #your own pilot key experimental: plugins: rewritebody: modulename: "github.com/traefik/plugin-rewritebody" version: "v0.3.1"
- Konfiguration im Docker Container als Label:
- "traefik.http.routers.mycontainer-tls.middlewares=my-rewrite"
- "traefik.http.middlewares.my-rewrite.plugin.rewritebody.lastModified=true"
- "traefik.http.middlewares.my-rewrite.plugin.rewritebody.rewrites[0].regex=css/"
- "traefik.http.middlewares.my-rewrite.plugin.rewritebody.rewrites[0].replacement=path/css/"