Tailscale Logo

DockTail: Docker-Container mit Tailscale Services erreichbar machen

Mit Tailscale Services lassen sich Anwendungen unabhängig von ihrem Host über eigene Adressen innerhalb eines Tailnets bereitstellen. Das Open-Source-Werkzeug DockTail automatisiert die Einrichtung für Docker-Container. Nachdem wir das Konzept bereits vorgestellt haben, zeigen wir dieses Mal, wie sich DockTail auf einem Linux-Host mit Docker Compose installieren und anschließend eine Anwendung über HTTPS bereitstellen lässt.

Voraussetzungen

Die Anleitung setzt einen Linux-Rechner voraus, auf dem Docker Engine einschließlich Docker Compose und Tailscale bereits installiert sind. Der Rechner muss Mitglied eines bestehenden Tailnets sein. Zudem wird administrativer Zugriff auf die Tailscale-Verwaltungsoberfläche benötigt, um Tags, Berechtigungen und einen OAuth-Client einzurichten. Wenn Tailscale noch nicht installiert ist, vermittelt dieser Artikel die Grundlagen.

In unserem Praxistest kam ein Debian Unstable zum Einsatz, auf dem mehrere Anwendungen über Docker Compose betrieben werden. Tailscale läuft dabei direkt auf dem Host und nicht in einem eigenen Container. DockTail verwendet den vorhandenen Tailscale-Daemon und dessen Unix-Socket. Zunächst prüfen wir, ob Docker und Tailscale funktionieren:

docker compose version
docker info --format '{{.ServerVersion}}'
tailscale status
ls -l /var/run/tailscale/tailscaled.sock

Der letzte Befehl sollte den Socket des laufenden Tailscale-Daemons anzeigen. Fehlt dieser, muss zunächst die Tailscale-Installation überprüft werden. Für Rootless Docker sowie Systeme ohne direkt nutzbaren Tailscale-Socket gelten abweichende Installationsverfahren, die in der DockTail-Dokumentation beschrieben sind.

Tailscale vorbereiten

Damit ein Rechner Tailscale Services bereitstellen darf, benötigt er einen geeigneten Tag. Außerdem müssen die Zugriffsrichtlinien des Tailnets die automatische Registrierung der Services erlauben. Wir verwenden dafür zwei Tags: tag:server kennzeichnet den Linux-Host, während tag:container den später angelegten Services zugewiesen wird.

In der Tailscale-Verwaltung öffnen wir den JSON-Editor der Zugriffsrichtlinien und ergänzen folgende Abschnitte:

"tagOwners": {
  "tag:server": ["autogroup:admin"],
  "tag:container": ["tag:server"],
},

"autoApprovers": {
  "services": {
    "tag:container": ["tag:server"],
  },
},

Die beiden Abschnitte gehören auf die oberste Ebene der Policy. Sind Einträge für tagOwners oder autoApprovers bereits vorhanden, werden nur die benötigten Einträge ergänzt. Bestehende Berechtigungen dürfen dabei nicht überschrieben werden.

Anschließend weisen wir dem Linux-Host unter Machines über die Tag-Verwaltung tag:server zu. Ob der Tag übernommen wurde, lässt sich kontrollieren:

tailscale status --json | jq '.Self.Tags'

Die Antwort sollte "tag:server" lauten. Verwendet das Tailnet bereits restriktive Grants, muss zusätzlich der Zugriff der vorgesehenen Clients auf die neuen Services erlaubt sein. Für einen HTTPS-Dienst mit dem Namen dawarich kann beispielsweise folgende Regel verwendet werden:

"grants": [
  {
    "src": ["autogroup:member"],
    "dst": ["svc:dawarich"],
    "ip": ["tcp:443"]
  }
],

Sie erlaubt den Mitgliedern des Tailnets den Zugriff auf diesen Service über HTTPS. Weitere Berechtigungen lassen sich bei Bedarf gezielt ergänzen. Existieren bereits weitere Grants, wird die Regel als zusätzlicher Eintrag in das Array aufgenommen:

"grants": [
  {
    "src": ["autogroup:member"],
    "dst": ["svc:jellyfin"],
    "ip": ["tcp:443"]
  },
  {
    "src": ["autogroup:member"],
    "dst": ["svc:dawarich"],
    "ip": ["tcp:443"]
  }
],

Die Zugriffsregel wird im JSON-Editor der Tailscale-Verwaltung innerhalb des vorhandenen Arrays grants eingefügt. Bereits bestehende Regeln bleiben erhalten. Verwendet das Tailnet noch die uneingeschränkte Standardregel, ist dieser zusätzliche Eintrag nicht erforderlich.

OAuth-Zugang einrichten

DockTail kann Services automatisch über die Tailscale-API anlegen. Dafür empfiehlt sich ein OAuth-Client, dessen Zugangsdaten nicht wie gewöhnliche API-Schlüssel regelmäßig erneuert werden müssen.

Unter Settings → Trust credentials in der Tailscale-Verwaltung erstellen wir einen neuen OAuth-Client, beispielsweise mit dem Namen DockTail-Host. Als Tag wählen wir tag:server und vergeben die Schreibberechtigungen General → Services → Write sowie Devices → Core → Write. Weitere Berechtigungen sind für unsere Installation mit dem Tailscale-Daemon auf dem Host nicht erforderlich.

Nach dem Erstellen zeigt Tailscale eine Client-ID und ein Client-Secret an. Beide Werte müssen sicher gespeichert werden, da das Secret anschließend nicht erneut angezeigt wird.

DockTail mit Docker Compose installieren

Wir betreiben DockTail als eigenständiges Compose-Projekt. Dadurch bleibt die Anwendung unabhängig von den bereits vorhandenen Containern und kann später beliebig viele davon verwalten.

Zunächst legen wir ein Verzeichnis an:

mkdir -p ~/docktail
cd ~/docktail

Darin erstellen wir eine Datei compose.yaml mit folgendem Inhalt:

services:
  docktail:
    image: ghcr.io/marvinvr/docktail:latest
    container_name: docktail
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /var/run/tailscale:/var/run/tailscale
    environment:
      - TAILSCALE_OAUTH_CLIENT_ID=${TAILSCALE_OAUTH_CLIENT_ID}
      - TAILSCALE_OAUTH_CLIENT_SECRET=${TAILSCALE_OAUTH_CLIENT_SECRET}

Achtet peinlich genau auf die Einrückungen, YAML ist da sehr penibel. Die beiden Volume-Einbindungen sind entscheidend. Über den Docker-Socket erkennt DockTail die laufenden Container und liest deren Labels aus. Das Tailscale-Verzeichnis ermöglicht den Zugriff auf den Daemon des Hosts. Die Einbindung des gesamten Verzeichnisses sorgt dafür, dass der Socket auch nach einem Neustart von tailscaled erreichbar bleibt.

Die vorher erstellten OAuth-Zugangsdaten werden in einer separaten versteckten Datei .env im selben Verzeichnis hinterlegt:

TAILSCALE_OAUTH_CLIENT_ID=CLIENT-ID
TAILSCALE_OAUTH_CLIENT_SECRET=CLIENT-SECRET

Die Platzhalter ersetzen wir durch die zuvor erzeugten Zugangsdaten. Anschließend schränken wir die Dateirechte mit chmod 600 .env ein. Anschließend überprüfen wir die Compose-Konfiguration und starten DockTail:

docker compose config --quiet
docker compose up -d
docker compose ps

Der Container sollte nach kurzer Zeit laufen und den Status healthy erreichen. Mit docker compose logs --tail=40 kontrollieren wir die Initialisierung. Eine erfolgreiche Verbindung zu Docker und Tailscale wird im Log bestätigt. Dass DockTail zunächst keine freigegebenen Container findet, ist normal.

Anwendung bereitstellen

Für unseren Test verwendeten wir Dawarich, eine selbst gehostete Anwendung zur Aufzeichnung und Darstellung von Standortdaten. Sie läuft zusammen mit PostgreSQL, Redis und weiteren Komponenten in einem bestehenden Compose-Projekt. Über Tailscale soll ausschließlich die Weboberfläche von Dawarich auf Port 3000 erreichbar sein.

Dafür ergänzen wir den Service dawarich_app in der Dawarich-Compose-Datei um fünf Labels:

labels:
  docktail.service.enable: "true"
  docktail.service.name: "dawarich"
  docktail.service.port: "3000"
  docktail.service.service-protocol: "https"
  docktail.service.service-port: "443"

Die Labels aktivieren die Bereitstellung, legen den Namen und den internen Anwendungsport fest und bestimmen, dass der Tailscale Service HTTPS auf Port 443 verwendet. Zwischen Tailscale und dem Container läuft die Kommunikation weiterhin über HTTP.

Bei anderen Anwendungen sind lediglich der Service-Name und der interne Port anzupassen. Eine Veröffentlichung des Container-Ports auf dem Docker-Host ist für DockTail normalerweise nicht erforderlich. Es verwendet standardmäßig die interne IP-Adresse des Containers.

Nach dem Speichern übernehmen wir die Änderung im Verzeichnis des betreffenden Compose-Projekts:

docker compose config --quiet
docker compose up -d --no-deps dawarich_app

Docker erstellt dabei den Anwendungscontainer mit den neuen Labels neu, während die abhängigen Dienste unangetastet bleiben. DockTail erkennt den Container automatisch und registriert den neuen Service. Im Log erscheinen dann unter anderem Meldungen wie:

Found enabled containers count=1
Successfully added service
Successfully created service definition in Control Plane
Reconciliation completed successfully

In unserem Test war Dawarich anschließend über https://dawarich.<tailnet>ts.net erreichbar. Den tatsächlichen Tailnet-Namen erhält man aus der Tailscale-Verwaltung.

Stolpersteine aus dem Praxistest

Zwei Besonderheiten fielen bei der Einrichtung auf. Zunächst beantwortete Dawarich den Aufruf über den neuen Domainnamen mit HTTP 403. Die Anwendung verfügt über eine eigene Prüfung zulässiger Hostnamen. Erst nachdem wir dawarich.<tailnet>.ts.net in der Umgebungsvariable APPLICATION_HOSTS der .env-Datei von Dawarich ergänzt und den Container neu erstellt hatten, funktionierte der Zugriff.

Die zweite Beobachtung betraf die erstmalige Erreichbarkeit. Obwohl DockTail die Services bereits erfolgreich registriert hatte, meldeten Browser zunächst einen Timeout. Nach etwa ein bis zwei Minuten waren die Anwendungen erreichbar. Dieses Verhalten beobachteten wir bei mehreren Diensten auf unterschiedlichen Hosts. Nach späteren Container-Neustarts trat die Verzögerung dagegen nicht mehr auf. Die genaue Ursache konnten wir bislang nicht bestimmen.

Bei Dawarich funktionierten anschließend sowohl die Kartenansicht als auch die Synchronisierung mit der Smartphone-App. Der Login-QR-Code verwendete ebenfalls die neue Service-Adresse.

Sicherheit und Ausblick

DockTail stellt die Anwendungen standardmäßig nur innerhalb des Tailnets bereit. Sie können damit auch von unterwegs erreicht werden, sind aber nicht ohne entsprechende Berechtigung öffentlich im Internet zugänglich. Eine öffentliche Freigabe über Tailscale Funnel ist optional und für unsere Konfiguration nicht erforderlich.

Eine wichtige Sicherheitsüberlegung betrifft den Docker-Socket. Obwohl er im Compose-Beispiel mit :ro eingebunden wird, erhält der Container dadurch nicht automatisch ausschließlich lesenden API-Zugriff. Für eine stärkere Trennung empfiehlt die DockTail-Dokumentation einen entsprechend eingeschränkten Docker-Socket-Proxy. Auch die Tailscale-Zugriffsrichtlinien sollten nach dem Prinzip der minimal erforderlichen Berechtigungen gestaltet werden.

Die Einrichtung funktionierte in unserem Praxistest auch auf mehreren Unraid-Servern mit dem dort verfügbaren Community-Plugin. Eine ausführlichere Anleitung für Unraid sowie weitere Hinweise zur Absicherung könnten Gegenstand eines Folgeartikels sein. Die Dokumentation zu den Tailscale Services gilt unabhängig vom Unterbau.

Teilt den Beitrag, falls ihr mögt

Kommentar hinterlassen