Docker mit Traefik Hier geht es um Experimente mit Docker Traefik. Geplant war ein kleiner Server, der einen reinen HTML-Blog (Hugo), eine Wissensdatenbank (BookStack) und einen WebDav-Server zur Verfügung stellt. Alle Services sollten als Docker Services installiert werden und nur über https und Subdomains ansprechbar sein. Die Zertifikatsverwaltung sollte automatisiert erfolgen. Also das ideale Einsatzgebiet für Traefik. Die Basis Warum tue ich das? Mit Traefik hatte ich zu Beginn meiner Docker-Experimente schon einmal gearbeitet. Ursache war ein Artikel in der Zeitschrift c't, die die Möglichkeiten von Traefik einmal angerissen haben. Auf die Idee, ein neues Traefik-Experiment anzugehen, brachte mich eine Information meines Lieblings-Cloud-Hosters Hetzner. Dort gibt es seit kurzem Server auf Basis der ARM64 Architektur zu mieten. Als Besitzer eines der MacBooks Pro mit Apple M1 Max Chip, der auf eben dieser ARM-Architektur basiert, fand ich das Thema spannend. Zumal ich gelernt habe, dass gerade im Docker Umfeld lange nicht alles als ARM-Ready zu bezeichnen ist. Und das, obwohl ARM-CPUs nicht nur im Serversektor auf enorme Zuwachsraten schauen können. Welche Ziele möchte ich erreichen? Um dem Experiment einen tieferen Sinn zu geben, habe ich mir Ziele gesteckt, die bei erfolgreicher und zufriedenstellender Umsetzung zu einer Umgestaltung meiner derzeitigen Web-Infrastuktur führen könnten. Konkret lauten diese: Der Server soll über die Internet-Protokolle IPv4 und IPv6 angesprochen werden können. Der Server wird standardmäßig abgesichert. Es werden ausschließlich sichere und verschlüsselte Protokolle (HTTPS, DAVS, ...) verwendet. Notwendige Zertifikate sollen über Let's Encrypt automatisch bezogen und verwaltet werden. Auf dem Server läuft neben den für das Betriebssystem erforderlichen Anwendungen ausschließlich Docker. Traefik übernimmt die Zertifikatsverwaltung und Funktion des Proxy. Das Traefik-Dashbord ist über die Subdomain traefik.domain.tld erreichbar und mit BasicAuth gesichert. Die offizielle Webseite, ein mit hugo erstellter reiner HTML-Blog, wird über Nginx bereitgestellt. Die offizielle Webseite ist über https://domain.tld oder https://www.domain.tld gleichermaßen erreichbar. Eine WebDav-Anbindung ist über die Subdomain davs.domain.tld erreichbar. Eine BookStack-Instanz ist über die Subdomain book.domain.tld erreichbar. Die für BookStack notwendige Datenbank liegt in einem eigenen Netzwerk und ist von außen nicht zu erreichen. Der erste Schritt - Server anlegen Also gespannt ans  Traefik Reverse Proxy Verzeichnisse und Dateien anlegen mkdir -p /opt/containers/traefik/data touch /opt/containers/traefik/data/acme.json chmod 600 /opt/containers/traefik/data/acme.json touch /opt/containers/traefik/data/traefik.yml Wichtig: Nicht vergessen, die Datei acme.json (oder acme_letsencrypt.json) anzulegen ./docker-compose.yml erstellen # # /opt/containers/trefik/docker-compose.yml # Reverse Proxy Traefik installieren # version: '3.9' services: traefik: container_name: traefik image: traefik:latest volumes: # Traefik mit der Zeit vom Server syncronisieren - /etc/localtime:/etc/localtime:ro # Lesezugriff auf den UNIX Docker socket - /var/run/docker.sock:/var/run/docker.sock:ro # Traefik-Konfiguration bereitstellen - ./data/traefik.yml:/traefik.yml:ro # Datei mit Zertifikaten bereitstellen - ./data/acme_letsencrypt.json:/acme_letsencrypt.json # Datei mit dynamischer Konfiguration bereitstellen - ./data/dynamic_conf.yml:/dynamic_conf.yml labels: # Traefik durch Watchtower aktualisieren lassen # https://goneuland.de/docker-images-automatisiert-aktualisieren-mit-watchtower/ - "com.centurylinklabs.watchtower.enable=true" # Traefik für Traefik aktivieren - "traefik.enable=true" # Als Einstiegspunkt wählen wir direkt HTTPS - "traefik.http.routers.traefik.entrypoints=https" # Domain anpassen unter der Traefik erreichbar sein soll. - "traefik.http.routers.traefik.rule=Host(`traefik.die-steffens.eu`)" # Middlewares definieren, welche verwendet werden sollen. # Hier die Authentifizierung via htpasswd + alle die unter default definiert sind. - "traefik.http.routers.traefik.middlewares=traefikAuth@file,default@file" # SSL Zertifikat abrufen - "traefik.http.routers.traefik.tls=true" # Definierten Zertifikat Resolver aus traefik.yml wählen - "traefik.http.routers.traefik.tls.certresolver=http" - "traefik.http.routers.traefik.service=api@internal" - "traefik.http.services.traefik.loadbalancer.server.port=80" - "traefik.http.services.traefik.loadbalancer.sticky.cookie.httpOnly=true" - "traefik.http.services.traefik.loadbalancer.sticky.cookie.secure=true" # Traefik dem Proxy-Netzwerk hinzufügen. - "traefik.docker.network=proxy" restart: unless-stopped security_opt: - no-new-privileges:true networks: proxy: hostname: traefik ports: # Ports definieren, welche durch Traefik gemanaget werden. - "80:80" - "443:443" networks: proxy: name: proxy driver: bridge attachable: true ./data/dynamic_conf.yml füllen # # /opt/containers/trafik/data/dynamic_conf.yml # # TLS # Hier werden alle notwendigen Einstellungen für das Zertifikat getroffen. # In Kombination mit den Einstellungen unter http.middlewares.default-security-headers bekommen wir ein A+ Zertifikat. tls: options: default: minVersion: VersionTLS12 cipherSuites: - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 - TLS_AES_128_GCM_SHA256 - TLS_AES_256_GCM_SHA384 - TLS_CHACHA20_POLY1305_SHA256 curvePreferences: - CurveP521 - CurveP384 sniStrict: true # Middlewares # Optionale Optimierungen, die bei jeder Anfrage vorgenommen werden sollen bevor diese an den Zielcontainer geleitet wird. http: middlewares: # Eine grundlegende Authentifizierungs-Middleware, um das Traefik-Dashboard via htpasswd zu schützen # Um die Authentifizierung für einen Container zu nutzen können wir "traefik.http.routers.definierteRoute.middlewares=traefikAuth@file" nutzen traefikAuth: basicAuth: users: - "Kamikaze-Jim:$apr1$P/tG13aC$BdXjI6AtKysGWvRAr9aoI0" # Empfohlene Standard-Middleware für die meisten Dienste # Hinzufügbar via "traefik.http.routers.definierteRoute.middlewares=default@file" # Equivalent mit "traefik.http.routers.definierteRoute.middlewares=default-security-headers@file,gzip@file" # Die Liste kann hier auch beliebig erweitert werden default: chain: middlewares: - default-security-headers - gzip # Kompatibilität zu alten Anleitungen. Damit kann auch wieder "traefik.http.routers.definierteRoute.middlewares=secHeader@file" secHeaders: chain: middlewares: - default-security-headers - gzip # Standard Header default-security-headers: headers: browserXssFilter: true contentTypeNosniff: true forceSTSHeader: true frameDeny: true # Deprecated # sslRedirect: true #HSTS Configuration stsIncludeSubdomains: true stsPreload: true stsSeconds: 31536000 customFrameOptionsValue: "SAMEORIGIN" # Gzip Kompression gzip: compress: {} User und Passwort festlegen # # Erzeugt das für obige Datei erforderliche USR/PWD-Gespann # echo $(htpasswd -nb '') # # Zum Beispiel: # echo $(htpasswd -nb jonathan 'supersicher') # Ausgabe: jonathan:$$apr1$$J8vBlNIm$$lSwLv9iGa8KcCct3EyLD41 # Sollte es an dieser Stelle zu Problemen kommen kann man sich auch das Passwort auf Webseiten # wie zum Beispiel: https://www.redim.de/blog/passwortschutz-mit-htaccess-einrichten generieren lassen. ./data/traefik.yml füllen # # /opt/containers/traefik/data/traefik.yml # # Statische Traefik-Konfigurationsdatei # https://doc.traefik.io/traefik/getting-started/configuration-overview/#the-static-configuration # https://doc.traefik.io/traefik/reference/static-configuration/cli/ api: dashboard: true # Aktivieren des Dashboard # Certificate Resolver # Diese sind für den Abruf von Zertifikaten von einem ACME-Server zuständig # https://doc.traefik.io/traefik/https/acme/#certificate-resolvers certificatesResolvers: http: acme: email: "ralf@rastef.de" # E-Mail-Adresse für die Registrierung storage: "acme_letsencrypt.json" # Datei für die Speicherung von Zertifikate (Ich weiche hier bewusst von dem "Standard": acme.json ab) httpChallenge: entryPoint: http # EntryPoints # EntryPoints sind die Netzwerk-Eingangspunkte in Traefik. Sie definieren den Port, der die Pakete empfängt. # https://doc.traefik.io/traefik/routing/entrypoints/ entryPoints: http: address: ":80" # Erstellen des Einstiegspunkt für HTTP (Port 80) http: redirections: # Weiterleitung von HTTP auf HTTPS (Port 80 zu Port 443). entryPoint: to: "https" # Das Ziel scheme: "https" # Umleitungszielschema https: address: ":443" # Erstellen des Einstiegspunkt für HTTPS (Port 443) global: checknewversion: true # In regelmäßigen Abständen prüfen, ob eine neue Version veröffentlicht wurde. sendanonymoususage: false # Regelmäßige Übermittlung anonymer Nutzungsstatistiken. providers: docker: endpoint: "unix:///var/run/docker.sock" # Den UNIX Docker socket beobachten exposedByDefault: false # Nur Container ausstellen, die explizit aktiviert sind (mit dem Label traefik.enabled) network: "proxy" # Standardnetzwerk, das für Verbindungen zu allen Containern verwendet wird. file: filename: "./dynamic_conf.yml" # Link zur dynamischen Konfiguration watch: true # Achten auf Änderungen providersThrottleDuration: 10 # Frequenz in welchen Abständen die Konfiguration nachgeladen wird Die eigentliche Anleitung stammt von hier: https://goneuland.de/traefik-v2-reverse-proxy-mit-crowdsec-einrichten/ Webserver mit nginx Um einen nginx Webserver Container einzubinden, sind folgende Schritte durchzuführen: Anlegen des Verzeichnis /opt/containers/nginx Anlegen des Verzeichnis /opt/containers/nginx/html sudo mkdir -p /opt/containers/nginx/html Für den Containerstart ist die Datei /opt/containers/nginx/docker-compose.yml mit folgendem Inhalt anzulegen: # # /opt/containers/nginx/docker-compose.yml # Startet einen nginx Webserver # version: '3' services: nginx: image: nginx:latest container_name: nginx restart: unless-stopped networks: - proxy volumes: - ./html:/usr/share/nginx/html labels: - "traefik.enable=true" - "traefik.http.middlewares.redirect2www.redirectregex.regex=^https://die-steffens.eu/(.*)" - "traefik.http.middlewares.redirect2www.redirectregex.replacement=https://www.die-steffens.eu/$${1}" - "traefik.http.routers.nginx-www.entrypoints=https" - "traefik.http.routers.nginx-www.rule=Host(`www.die-steffens.eu`,`die-steffens.eu`)" - "traefik.http.routers.nginx-www.tls=true" - "traefik.http.routers.nginx-www.tls.certresolver=http" - "traefik.http.routers.nginx-www.service=nginx" - "traefik.http.routers.nginx-www.middlewares=redirect2www,secHeaders@file" - "traefik.http.services.nginx.loadbalancer.server.port=80" - "traefik.docker.network=proxy" networks: proxy: external: true Der Webserver kann über das unsichere Port 80 Protokoll http oder das sichere Port 443 Protokoll https angesprochen werden. In ersterem Fall wird auf das sichere https Protokoll umgelenkt. Gestartet und gestoppt wird wie üblich mit: /opt/containers/bookstack/docker compose -f /opt/containers/nginx/docker-compose.yml up -d /opt/containers/bookstack/docker compose -f /opt/containers/nginx/docker-compose.yml down Bookstack Um einen Bookstack Container einzubinden, sind folgende Schritte durchzuführen: Anlegen des Verzeichnis /opt/containers/bookstack Anlegen des Verzeichnis /opt/containers/bookstack/data/db Anlegen des Verzeichnis /opt/containers/bookstack/data/bs sudo mkdir -p /opt/containers/bookstack/data/db sudo mkdir /opt/containers/bookstack/data/bs Für den Containerstart ist die Datei /opt/containers/bookstack/docker-compose.yml mit folgendem Inhalt anzulegen: # # /opt/containers/bookstack/docker-compose.yml # Ein Bookstack Container mit MariaDB # version: '3' services: db: image: lscr.io/linuxserver/mariadb:latest container_name: bookstack-db networks: - db-network environment: - TZ=Europe/Berlin - MYSQL_ROOT_PASSWORD=RaRuRickZaubertrick - MYSQL_DATABASE=bookstack - MYSQL_USER=bookstack - MYSQL_PASSWORD=DaKommstDuNieDrauf volumes: - ./data/db:/config restart: unless-stopped labels: - traefik.enable=false app: image: lscr.io/linuxserver/bookstack:latest container_name: bookstack-app networks: - db-network - proxy depends_on: - db environment: - APP_URL=https://book.die-steffens.eu - DB_HOST=db - DB_PORT=3306 - DB_DATABASE=bookstack - DB_USERNAME=bookstack - DB_PASSWORD=DaKommstDuNieDrauf restart: unless-stopped volumes: - ./data/bs:/config labels: - "traefik.enable=true" - "traefik.http.routers.bookstack.entrypoints=https" - "traefik.http.routers.bookstack.rule=Host(`book.die-steffens.eu`)" - "traefik.http.routers.bookstack.tls=true" - "traefik.http.routers.bookstack.tls.certresolver=http" - "traefik.http.routers.bookstack.service=bookstack" - "traefik.http.routers.bookstack.middlewares=secHeaders@file" - "traefik.http.services.bookstack.loadbalancer.server.port=80" - "traefik.docker.network=proxy" networks: db-network: external: false proxy: external: true Es werden zwei Container gestartet: Die Datenbank MariaDB und der Bookstack Container. Für die Kommunikation mit der Datenbank wird ein internes Netzwerk eingerichtet, das von Außen nicht zu erreichen ist. Gestartet und gestoppt wird wie üblich mit: /opt/containers/bookstack/docker compose -f /opt/containers/bookstack/docker-compose.yml up -d /opt/containers/bookstack/docker compose -f /opt/containers/bookstack/docker-compose.yml down WebDAV Container Um einen WebDAV Zugang über ein gesichertes Protokoll bereitzustellen, sind nur wenige Schritte erforderlich:  Das Verzeichnis /opt/containers/webdav muss angelegt werden. Das Verzeichnis /opt/containers/webdav/data muss angelegt werden. Das Verzeichnis /opt/containers/webdav/config muss angelegt werden. Das Verzeichnis /opt/containers/webdav/data soll für alle zu lesen und zu beschreiben sein (chmod 777). sudo mkdir -p /opt/containers/webdav/config sudo mkdir /opt/containers/webdav/data sudo chown 777 /opt/containers/webdav/data Im Verzeichnis opt/containers/webdav ist die nachfolgend aufgezeigte Datei docker-compose.yml zu erstellen: # # /opt/containers/webdav/docker-compose.yml # Starts and initializes a WebDAV Container # version: '3' services: webdav: image: chonjay21/webdav:latest container_name: webdav restart: unless-stopped environment: APP_USER_NAME: meganeKAR8879 APP_USER_PASSWD: qAJsPWCc7YcMro2vfRWHv2tfad2hh2kfWcieMJ2VCmXKT3ggNMRKVLQTSrsEcUNT SERVER_NAME: davs.die-steffens.eu APP_UID: 1000 APP_GID: 1000 APP_UMASK: 007 TZ: Europe/Berlin FORCE_REINIT_CONFIG: true networks: - proxy security_opt: - no-new-privileges:true volumes: - ./data:/var/webdav/data - ./config:/webdav/config labels: - "traefik.enable=true" - "traefik.http.routers.webdav-secure.entrypoints=https" - "traefik.http.routers.webdav-secure.rule=Host(`davs.die-steffens.eu`)" - "traefik.http.routers.webdav-secure.tls=true" - "traefik.http.routers.webdav-secure.tls.certresolver=http" - "traefik.http.routers.webdav-secure.service=webdav" - "traefik.http.services.webdav.loadbalancer.server.port=80" - "traefik.docker.network=proxy" - "traefik.http.routers.webdav-secure.middlewares=secHeaders@file" networks: proxy: external: true Mit  docker compose -f /opt/containers/webdav/docker-compose.yml up -d kann der Container gestartet werden. Mit  docker compose -f /opt/containers/webdav/docker-compose.yml down kann der Container gestoppt werden. Das für alle User offene Datenverzeichnis ist natürlich für komplexere Szenarien mit Benutzenden in unterschiedlichen Rollen nicht zu empfehlen. Als Familiensammelverzeichnis ist es allerdings ausreichend. Weitere Informationen finden sich in der Beschreibung des Containers.