Zum Inhalt

Headscale + Headplane

Headscale ist die selbst-gehostete Alternative zum Tailscale-Koordinationsserver. Er verwaltet VPN-Teilnehmer, Schluessel und Zugriffsregeln (ACLs) fuer das gesamte Homelab-Netzwerk. Headplane stellt die Web-Oberflaecche fuer die Verwaltung bereit.

Zugang

Komponente URL Port (intern)
Headplane (Web-UI) https://headscale.homelab-external.robinwerner.net/admin 3000
Headscale API https://headscale.homelab-external.robinwerner.net 8080
STUN/DERP headscale.homelab-external.robinwerner.net:3478 3478 (UDP+TCP)

/ wird automatisch auf /admin weitergeleitet.

Container-Konfiguration

Headscale

Parameter Wert
Image ghcr.io/juanfont/headscale:v0.28.0
Neustart unless-stopped
Netzwerke proxy, internal
Abhaengigkeit headscale-postgres (healthcheck)

Volumes:

Quelle Ziel Typ
./headscale/config.yaml /etc/headscale/config.yaml Git-Config (ro)
./headscale/acl.json /etc/headscale/acl.json Git-Config (ro)
./headscale/dns_records.json /etc/headscale/dns_records.json Git-Config (ro)
/opt/homelab-data/headscale /var/lib/headscale Persistente Daten

Umgebungsvariablen:

Variable Beschreibung
HEADSCALE_DATABASE_POSTGRES_PASS Ueberschreibt DB-Passwort aus config.yaml (aus .env)

Headplane

Parameter Wert
Image ghcr.io/tale/headplane:0.6.2
Neustart unless-stopped
Netzwerke proxy, internal
Abhaengigkeit headscale

Volumes:

Quelle Ziel Beschreibung
./headplane/config.yaml /etc/headplane/config.yaml Headplane-Config (ro)
./headscale/config.yaml /etc/headscale/config.yaml Headscale-Config lesen (ro)
./headscale/acl.json /etc/headscale/acl.json ACL lesen/schreiben (ro)
./headscale/dns_records.json /etc/headscale/dns_records.json DNS-Records (ro)
/opt/homelab-data/headplane /var/lib/headplane Persistente Daten (SQLite)
/opt/homelab-data/secrets/cookie_secret /run/secrets/cookie_secret Session-Secret (ro)
/var/run/docker.sock /var/run/docker.sock Docker-Integration (ro)

PostgreSQL (Headscale)

Parameter Wert
Image postgres:17.9-alpine
Netzwerk internal (kein Traefik-Zugriff)
Daten /opt/homelab-data/headscale-postgres
Datenbank headscale / User headscale

Das DB-Passwort wird ueber POSTGRES_PASSWORD aus .env gesetzt — derselbe Wert gilt auch fuer die Healthchecks-Datenbank.

Netzwerk

Headscale und Headplane teilen sich das gleiche Traefik-Routing ueber headscale.homelab-external.robinwerner.net:

  • Traefik-Router headplane mit PathPrefix(/admin) und priority=200 leitet /admin-Pfade an Headplane
  • Traefik-Router headscale leitet alle anderen Pfade an Headscale
  • CORS-Middleware erlaubt Content-Type und Authorization Header (noetig fuer Headplane-API-Zugriff)
  • Port 3478 (STUN/DERP) wird direkt an Traefik weitergereicht (UDP + TCP)

Wichtige Konfiguration

Server

server_url: https://headscale.homelab-external.robinwerner.net
listen_addr: 0.0.0.0:8080

IP-Adressbereiche (Tailnet)

Protokoll Praefix Vergabe
IPv4 100.64.0.0/10 sequenziell
IPv6 fd7a:115c:a1e0::/48 sequenziell

DERP (NAT-Traversal-Relay)

Eigener eingebetteter DERP-Server in der Region homelab (ID 999). Wird automatisch allen Tailscale-Clients als bevorzugter Relay angeboten. Zusaetzlich werden die offiziellen Tailscale-DERP-Karten geladen (taeglich aktualisiert).

DNS

  • MagicDNS aktiviert, Base-Domain: tailnet
  • Globaler Nameserver: 10.10.10.3 (Pi-hole im Heimnetz)

ACL-Regeln (Zusammenfassung)

Die ACL-Policy liegt in headscale/acl.json (Policy v2, HuJSON-Format):

Regel Quelle Ziel Beschreibung
Vollzugriff autogroup:member *:* Persoenliche Geraete: voller VPN-Zugriff
Internet autogroup:member autogroup:internet:* Exit-Node-Nutzung
Monitoring tag:server homelab-network:53,80,443,3000,8080,8123,9090,9100 Hetzner-Server → Homelab (Monitoring + DNS)
ICMP * *:* Ping fuer Diagnose
SSH autogroup:member autogroup:member, autogroup:tagged Tailscale SSH

Tags: - tag:server — Server-Nodes (Hetzner); darf Monitoring-Ports erreichen - tag:gateway — Gateway-Nodes (NUC); darf Routen und Exit-Node automatisch freigeben

Routen aus dem Subnetz 10.10.10.0/24 mit tag:gateway werden automatisch genehmigt (autoApprovers).

Wartung

Ersteinrichtung nach Bootstrap

# 1. User anlegen
docker exec headscale headscale users create homelab

# 2. API-Key fuer Headplane erzeugen (365 Tage gueltig)
docker exec headscale headscale apikeys create --expiration 365d
# -> Key in .env eintragen: HEADSCALE_API_KEY=...

# 3. Pre-Auth-Key fuer Tailscale-Client erzeugen
docker exec headscale headscale preauthkeys create --user homelab --expiration 24h
# -> Key in .env eintragen: TS_AUTHKEY=...

# 4. Stack neu starten damit .env-Werte aktiv werden
docker compose up -d headplane tailscale

Geraete verwalten

# Alle Nodes anzeigen
docker exec headscale headscale nodes list

# Routen eines Nodes anzeigen
docker exec headscale headscale nodes list-routes

# Routen freigeben
docker exec headscale headscale nodes approve-routes --identifier <ID> --routes 10.10.10.0/24

# Node loeschen
docker exec headscale headscale nodes delete --identifier <ID>

Logs

docker compose logs -f headscale
docker compose logs -f headplane

Headscale loggt im JSON-Format auf INFO-Level.

ACL aendern

  1. hetzner/headscale/acl.json bearbeiten (HuJSON, Policy v2)
  2. git commit && git push
  3. Auf dem Server: git pull && docker compose restart headscale

Headplane kann ACL-Aenderungen ueber die Web-UI durchfuehren und loest dank Docker-Integration automatisch einen Headscale-Neustart aus.

Update

Images sind auf konkrete Versionen gepinnt. Renovate erstellt woechtentlich PRs fuer Updates. Nach Merge auf main wird der Stack automatisch per Cron-Job (dienstags 12:00 UTC) aktualisiert.

Versionskompatibilitaet

Headplane muss zur Headscale-Version passen. Headplane v0.6.1 funktioniert nicht mit Headscale v0.28. Immer beide Images gemeinsam aktualisieren.

Image-Tag ohne v

Das Headplane-Image auf ghcr.io verwendet keinen v-Prefix: ghcr.io/tale/headplane:0.6.2 (nicht v0.6.2).