Docker Services erreichen mittels Traefik

  Claudia Fischering   Lesezeit: 5 Minuten  🗪 4 Kommentare Auf Mastodon ansehen

Wem Traefik zu kompliziert ist, findet mit Caddy und Caddyfile eine einfachere Lösung, um Docker-Images unter einer Domain zu binden. Damit hat man eine saubere Trennung zwischen den Containern und dieser Traefik-Verwaltung.

docker services erreichen mittels traefik

Bei GNU/Linux.ch gibt es die Möglichkeit, Artikelvorschläge einzureichen. Vor einiger Zeit habe ich dort diesen Vorschlag gepostet:

Jetzt habe ich eine viel einfachere Methode entdeckt, um meine Docker-Images unter einer Domain oder mehreren zu binden. Da ich Traefik zu kompliziert finde, da man an jedem Docker-Image Traefik-spezifische Zeilen hinzufügen muss, was man bei Caddyfile nicht machen muss. Somit hat man eine saubere Trennung zwischen dem Container und dieser Traeffic-Verwaltung.

Nun habe ich zu meinem Artikelvorschlag selbst einen Artikel geschrieben.

Caddy image

Dieses Image managed alle TLS-Abfragen automatisch. Dabei kann man verschiedene Varianten wählen:

  • Let's Encrypted (Default)
  • Self-Signed-Key
  • DuckDNS (Um keine Portweiterleitung von Port 80/443 haben zu müssen)

Alle, die sich das erst auf Docker Hub anschauen möchten: Hub-Docker.

Vollständige Ordnerstrucktur

Daraus resultiert folgende Ordnerstruktur (in alter Manier: tree-output):

/ Domainname
├ docker-compose.yml
├ Caddyfile
├ / caddy_data
│ └ ...
└ / caddy_config
  └ ...

Hier noch einige One-Liner zur Erstellung der Ordnerstruktur und der zwei Dateien:

# Ordner kurze Schreibweise (Flavor 01)
mkdir -p domainname/{caddy_data,caddy_config}

# Ordner lange Schreibweise (Flavor 02)
mkdir -p domainname/caddy_data domainname/caddy_config

# Dateien
touch domainname/docker-compose.yml domainname/Caddyfile

Konfigurationsdatei Caddyfile

Die Struktur ist sehr einfach gehalten.

# domain.de abändern in die gewünschte Adresse
domain.de {

  # Zugriff beschränken
  @allowed_ip {

    # Ganze lokales Netzwerk zugriff geben
    remote_ip 192.168.0.0/24

    # Einzelne IP zugriff gewähren
    remote_ip 192.168.0.1

    # Oder falls es eine öffentliche IP sein soll
    # dann diese eintragen. Ist es eine wechselnde öffentliche IP,
    # dann immer vorher anpassen, bevor der Container gestartet wird!
  }

  # Richtige IP klopft an, dann weiter reichen an Webserver - Docker Container
  handle @allow_ip {
    reverse_proxy dockerName:dockerPort {
      # Wenn man verschleiern möchte, dass man einen alten
      # apache2 oder nginx Webserver nutzt.
      header_down -Server
    }
  }

  # Alle anderen IP's die nicht in der Liste stehen, landen hier:
  handle {
    respond "Access denied! (Forbidden - 403)" 403
  }

  # Falls der Service nicht läuft kann man eine eindeutige Nachricht geben:
  handle_errors {
    @unavailable expression `{err.status_code} >= 500`
    respond @unavailable "Sorry, the website is currently unavailable due to maintenance. Please try again later." 503
  }

  # komprimierter Aufruf
  encode gzip

  # Fake Webserver Informationen zeigen
  header {
    -Server
    Server "Web-Server-Name Versionsnummer"
    Strict-Transport-Security "max-age=31536000;"
  }
}

Dies wäre eine mögliche Konfiguration für eine Let's Encrypted Variante.

Konfigurationsdatei Docker-compose Yaml

caddy:
    image: caddy:latest
    restart: always
    ports:
      # HOST_PORT:DOCKER_PORT
      # Sollten Ports schon verwendet werden, dann 80 in 8080
      # das selbe mit dem SSL-Port 443 in 8443
      #- "80:80"
      - "8080:80"
      #- "443:443"
      - "8443:443"
    volumes:
      # Caddyfile neben der docker-compose.yml Datei
      - ./Caddyfile:/etc/caddy/Caddyfile

      # Einen Ordner 'caddy_data' neben der docker-compse.yml Datei
      - ./caddy_data:/data

      # Einen weiteren Ordner 'caddy_config' neben der docker-compose.yml Datei
      - ./caddy_config:/config

    networks:
      - caddy_net

    # Achtung: Sicherheitsrisiko! Steht nur hier als Vervollständigung
    #extra_hosts:
       # Ermöglicht Zugriff auf lokale Netzgeräte vom Container aus
       # - "host.docker.internal:host-gateway"

networks:
  caddy_net:
    driver: bridge

Möchte man hingegen einen Self-Sign-Key verwenden

Dann muss im Caddyfile ganz oben stehen mit folgenden Parametern. (Es gibt noch andere Parameter, die man definieren kann. Diese können von folgender Webseite Caddyserver.com/docs/... geholt werden).:

{
  # TLS options
  #auto_https off|disable_redirects|ignore_loaded_certs|disable_certs
  #email example@mydomain.de
  local_certs
  tls internal

  #key_type ed25519|p256|p384|rsa2048|rsa4096

  # Neu erzeugen nach 90 Tagen sind 129600 Minuten ( 60 Minuten * 24 Stunden * 90 Tage )
  #renew_interval 129600m
}

Möchte man DuckDNS verwenden, benötigt man ein Konto bei DuckDNS

Folgendes Docker-Image ist nötig caddy-dns/duckdns und in der Caddyfile Datei muss folgendes stehen im globalen Bereich tls { dns duckdns DEIN_DUCKDNS_TOKEN } und folgender Eintrag darf in der docker-compose.yml auch nicht fehlen:

caddy:
    # Wichtig, da ansonsten das Plugin fehlt, was gebraucht wird für DuckDNS
    image: caddy-dns/duckdns
    ...
    environment:
      - DUCKDNS_TOKEN=DEIN_DUCKDNS_TOKEN
    ...

Dieses DuckDNS kann man nutzen, wenn man HTTPS verwenden möchte, ohne den NAG-Screen "Sicherheitsrisiko - Diese Webseite nutzt ein selbst erstelltes Zertifikat... Vertrauen?"

Dann noch ein Eintrag in die /etc/hosts Datei, um deine Domain bekannt zu machen.

# Wenn es nur lokal auf deinem Rechner laufen soll
127.0.0.1   domainname.de subdomain.domainname.de andererdomainname.de

# Ansonsten deine Netzwerk IP-Adresse nutzen
192.168.0.1   domainname.de subdomain.domainname.de andererdomainname.de

Hinweis für die Nutzung von Docker und podman

Bei Docker kann man die Namensgebung von der docker-compose.yml Datei als Hostnamen verwenden. Mit podyman als Docker-Image-Starter muss man die IP-Adresse verwenden, ganz wichtig, weil podyman keinen internen DNS-Handler besitzt, um die Namensauflösung zu steuern. Außerdem sei zu podyman zu sagen, dass es rootless (ohne Administratoren-Rechte) arbeitet, also muss man darauf achten, dass man die richtigen Docker-Images (rootless unterstützen) auswählt. Ansonsten wundert man sich über Fehlermeldungen, die man hätte im Vorfeld vermeiden können.

Weiterer Hinweis für das Anschauen der Logs innerhalb von containern

Mit folgendem Befehl kann man sich die Log-History ansehen, ob fehlerhafte Einträge in der Konfiguration sind oder andere nützliche Hinweise, die man vorher nicht gesehen hat.

# Neuste Docker Version
docker compose logs dockername

# Ältere Docker Version müssen folgendes eingeben:
docker-compose logs dockername

Fazit

Meiner Meinung nach ist Caddy ein viel einfacheres Tool als Traefik, da man nur einmal konfigurieren muss. Bei Traefik muss man bei jedem einzelnen Container immer 3-5 Zeilen extra hinzufügen, damit Traefik weiss, wie es damit umzugehen hat.

Titelbild: https://cdn.hackersandslackers.com/2019/03/caddy.jpg

Quellen:

[1] https://hub.docker.com/_/caddy - Docker Image

[2] https://caddyserver.com/docs/caddyfile/options - Caddyfile TLS Optionen Erklärungen

[3] https://caddyserver.com/docs/caddyfile - Allgemeine Konfigurationen

Tags

Docker, Container, Caddyfile, Podyman

Tobias
Geschrieben von Tobias am 24. Juli 2026 um 18:26

Bei mir hinterlässt der Abschnitt "Hinweis für die Nutzung von Docker und podman" einiges an Fragen:

  1. Was ist mit podyman gemeint? Mit ist im Umfeld von podman dieser Name noch nicht untergekommen. Auch eine Websuche nach diesem Begriff hat keine Aufklärung gebracht.
  2. > Bei Docker kann man die Namensgebung von der docker-compose.yml Datei als Hostnamen verwenden. Mit podyman als Docker-Image-Starter muss man die IP-Adresse verwenden, weil podyman keinen internen DNS-Handler besitzt, um die Namensauflösung zu steuern.

Dies gilt meines Wissens nach für Container, welche das podman default-bridge-Netzwerk nutzen: Debug Inter-Container DNS Resolution

Mit podman network create angelegte dedizierte Netzwerke haben eine funktionierende DNS-Auflösung. Ich nutze z.B. Caddy in einem rootless podman Container als reverse proxy für Nextcloud und in der Caddy-Konfiguration steht statt einer IP der Hostname des Nextcloud-Containers.

3. > Außerdem sei zu podyman zu sagen, dass es rootless (ohne Administratoren-Rechte) arbeitet, also muss man darauf achten, dass man die richtigen Docker-Images (rootless unterstützen) auswählt.

podman kann sowohl rootful als auch rootless genutzt werden. rootfol bedeutet in dem Zusammenhang, dass die podman-Container als Benutzer root betrieben werden. rootless werden podman-Container mit einer Benutzerkennung root betrieben.

Advanced containers with Podman

Nur sehr wenige Container-Images werden als rootless zur Verfügung gestellt. Wäre dies wirklich ein Problem, so dürfte keiner meiner aktuellen Container, welche alle rootless als podman quadlets laufen, aktiv sein. Auch caddy habe ich bisher nicht als rootless-Container-Image gesehen.

Keine Ahnung, worauf sich in diesem konkreten Fall bezogen wird, aber vielleicht hängt es eher an den Berechtigungen mit welchem auf das Host-Dateisystem bei Einbindung von volumes zugegriffen wird. Dies ist in der Tat bei rootless podman mitunter ein Problem, insbesondere, wenn direkt auf Dateien im Dateisystem zugegriffen werden soll. Bei mir hat sich dafür erstmal die Nutzung einges dedizierten podman volume eingespielt, um in einem laufenden Container die User-ID ermitteln zu können, unter welcher der Prozess im Container läuft.

Anhand dieser Information lässt sich in den meisten Fällen z.B. mit --userns=keep-id:uid=999,gid=999 die Berechtigung auf Dateien im Hostdateisystem so einstellen, dass die Dateien auch dem Benutzer gehören, unter welchem der rootless Container läuft. Damit ist dann ein einfacher Zugriff auf alle Dateien möglich.

podman run

Claudia
Geschrieben von Claudia am 25. Juli 2026 um 12:05

Hallo Tobias,

ich habe natürlich podman-docker gemeint gehabt. Ich habe nur noch mein Linux-Benutzernamen podyman im Kopf gehabt als ich den Artikel schrieb, der zugriff auf podman bekommen hat.

Ich habe es nur mit aufgenommen gehabt, weil ich große Probleme hatte. Es hat immer sehr lange gedauert bis der podman-container bereit war, weil die Namensfindung einfach Katastrophe war. Somit habe ich per inspect die jeweilige IP-Adresse rausgesucht und siehe da die podman-container sind unter 1 Sekunde bereit - als ich den Containernamen drin hatte, wie bei Docker, da hat es 2 Minuten gedauert. Deswegen diese Formulierung im Text. Ich hatte einige Container, die Ihre Services nicht starten konnten, weil kein root es gestartet hatte, sondern ein noraler User, der aber nicht die Berechtigung hatte es starten zu dürfen.

Zugriff auf Dateien war ebenso eine Qual, aber als ich es als Volume gemacht habe lief auf einmal alles. Ich habe es mir immer schwerer gemacht als es wirklich war. Ist halt, wenn man zu viel will und möglichst alles abgeschottet sein soll, wie möglich.

Ich hoffe, ich konnte Dir damit weiterhelfen, wie meine Gedanken dazu waren. Auch das werde ich bei den nächsten Artikeln berücksichtigen, was durch Probieren herauskam, dass es Nachvollziehbar bleibt.

Rigobert Müller
Geschrieben von Rigobert Müller am 24. Juli 2026 um 18:34

Danke für den Artikel, ich habe auch dafür gestimmt. Seit einiger Zeit benutze ich den Nginx-Proxy im Docker aber wollte seit ich davon weiß einmal Traefik ausprobieren. Es gibt zwar einen Traefik-Manager im Docker, aber das hatte nie richtig funktioniert und deshalb nutze ich Nginx-Proxy. Caddy kenne ich nur vom Namen und werde es Aufgrund Ihres Artikels einmal testen. Eine kleines Beispiel für eine Verbindung hätte ich mir noch gewünscht. Danke. Gruß Rigo

Claudia
Geschrieben von Claudia am 25. Juli 2026 um 11:42

Hallo Rigobert,

Im Caddyfile musst du die Zeile reverse_proxy dockerName:dockerPort anpassen in Beispiel mäßig Container von dein nginx lautet webserver1 und läuft auf dem default Port 8080, dann müsste die Zeile reverse_proxy webserver1:8080. Es ist nicht der Exposed-Port gemeint, sondern der interne Port vom Container.

Dann solltest du noch im Caddyfile domain.de zu deiner aktuellen Domain umbenennen Beispielhaft example.com oder example.de.

Dein Container muss dann nur noch im selben Netzwerkbereich liegen, somit in der Docker-compose.yml in der selben wie caddy, dann einfach:

nginxname:
    image: nginx_image_was_du_nutzt
    restart: always
    ...
    networks:
      - caddy_net

Falls es in einer anderen Datei steht, dann einfach external network machen, aber achte darauf, dass caddy container schon läuft, da ansonsten das Netzwerk nicht vorhanden ist:

nginxname:
    image: nginx_image_was_du_nutzt
    restart: always
    ...
    networks:
      - caddy_net

networks:
  caddy_net:
    external: true

Ist damit die Beispiel-Frage beantwortet? Ich bin davon ausgegangen das es schon fortgeschrittene Docker Kenntnisse vorhanden sind. Ich werde das nächste mal bei meinem Artikel darauf achten, dass es für jeder man ist - Nachvollziehbar.