Hetzner DynDNS

Weboberfläche für mehrere Hetzner-DNS-Zonen, eine Pi-hole-v6-Verbindung, Subdomains und Benutzer. Aktivierte A- und AAAA-Records werden regelmäßig auf die aktuelle öffentliche IP gesetzt; lokale DNS-Einträge lassen sich direkt über die Pi-hole-API verwalten.

Starten

Die Datei compose.yml in Portainer, Dockge oder einem anderen Docker-Webinterface als Stack einfügen und starten.

Alternativ im ausgecheckten Repository:

docker compose up -d

Danach aufrufen:

http://IP-DES-DOCKER-HOSTS:9090

Beim ersten Start werden Datenbank und Verschlüsselungsschlüssel automatisch in getrennten Docker-Volumes angelegt. Es werden keine .env-Datei und kein manuell erzeugtes Secret benötigt.

Das Compose

services:
  dyndns:
    image: &image git.j-mahler.de/admin/domain-control-webinterface-prod:v1.5.2
    restart: unless-stopped
    stop_grace_period: 20s
    ports: ["9090:8080"]
    volumes: ["dyndns-data:/data", "dyndns-secrets:/secrets", "dyndns-update-control:/run/domain-control-update"]
    labels: {de.j-mahler.domain-control.role: app}
    read_only: true
    tmpfs: ["/tmp:rw,noexec,nosuid,nodev,size=16m,mode=1777"]
    security_opt: [no-new-privileges:true]
    cap_drop: [ALL]

  update-agent:
    image: *image
    restart: unless-stopped
    user: "0:10001"
    command: [update-agent]
    healthcheck: {disable: true}
    network_mode: none
    volumes: ["/var/run/docker.sock:/var/run/docker.sock", "dyndns-update-control:/run/domain-control-update"]
    read_only: true
    tmpfs: ["/tmp:rw,noexec,nosuid,nodev,size=4m,mode=1777"]
    security_opt: [no-new-privileges:true]
    cap_drop: [ALL]
    pids_limit: 32
    mem_limit: 64m

volumes: {dyndns-data: {}, dyndns-secrets: {}, dyndns-update-control: {}}

Soll ein anderer Port verwendet werden, muss nur die linke Zahl geändert werden.

Aktualisieren

Eine vollständige Schritt-für-Schritt-Anleitung für WebUI, Docker Compose, Portainer/Dockge und Rollbacks steht in UPDATE.md.

Administratoren können im Menü Update nach stabilen Releases suchen und eine neue Version direkt installieren. Dabei dürfen auch mehrere Versionen bis hin zu einem kompatiblen Major-Release übersprungen werden. Vor dem Neustart prüft der Agent Versionsgrenze, Agent-Protokoll, Datenbankvertrag, Laufzeitkonfiguration und Healthcheck des Ziel-Images. Öffentliche Releases und öffentliche Container-Images funktionieren ohne Anmeldung. Für private Repositories oder Images können Gitea-Benutzername und Passwort oder Token einmalig hinterlegt werden; das Geheimnis wird mit dem Master-Key verschlüsselt gespeichert und weder an Templates noch an den Browser zurückgegeben.

v1.4.0 ist die sichere Basis für diesen Update-Weg. Eine ältere Installation wird einmalig mit Docker Compose auf v1.4.0 gebracht; danach kann der Agent auch spätere kompatible Releases direkt installieren. Web-App und Agent dürfen dabei unterschiedliche Versionsnummern haben: Maßgeblich ist das ausdrücklich ausgewiesene Protokoll. Verlangt ein Ziel-Image einen neueren Vertrag, bleibt der laufende Container unverändert und die Oberfläche fordert ein Compose-Update an.

Nur der separate, netzwerklose update-agent erhält Zugriff auf den Docker-Socket. Er kann ausschließlich das fest konfigurierte Anwendungs-Image ersetzen: Das neue Image wird vollständig geladen, bevor der laufende Container beendet wird, und erst nach erfolgreichem Healthcheck übernommen. Bei einem Fehler wird die vorherige Instanz automatisch wiederhergestellt. Ist Gitea oder die Registry nicht erreichbar, läuft der DNS-Updater unabhängig weiter und die zuletzt bekannte Update-Benachrichtigung bleibt erhalten.

Auf derselben Seite kann ein Administrator den vollständigen Anwendungscontainer geordnet neu starten. Dabei bleiben Daten-, Secret- und Steuer-Volumes unverändert. Der Neustart beendet den Prozess im Container; die in der unterstützten Compose-Konfiguration gesetzte Richtlinie restart: unless-stopped startet ihn anschließend automatisch wieder. Die Oberfläche erkennt die neue Prozessinstanz und verbindet sich selbstständig neu.

Alternativ lässt sich die Version weiterhin vollständig über Docker Compose aktualisieren. Dafür nur den konkret gepinnten Tag in der mit &image markierten Image-Zeile ändern; Web-App und Update-Agent verwenden dadurch garantiert dieselbe Version. Anschließend:

docker compose pull
docker compose up -d --force-recreate

Compose ersetzt beide Container und bindet die vorhandenen benannten Volumes wieder ein. docker compose down -v darf nicht verwendet werden, weil -v die Daten- und Secret-Volumes löschen würde. Der Ein-Klick-Updater verändert weder compose.yml noch Volumes. Vor einem späteren manuellen docker compose up muss der dort gepinnte Tag deshalb auf die gewünschte Version gesetzt werden. Meldet die WebUI Compose aktualisieren, unterstützt der vorhandene Agent den benötigten Vertrag nicht und beide Container werden auf diesem Weg gemeinsam aktualisiert.

Benutzer, Domains und Einstellungen bleiben in dyndns-data. Der Schlüssel für gespeicherte API-Tokens liegt in dyndns-secrets. Für ein vollständiges Backup müssen beide Volumes gesichert werden.

Funktionen

  • mehrere Domains und getrennt verschlüsselte Hetzner-API-Tokens
  • automatischer Import vorhandener A-/AAAA-Records
  • DynDNS pro Subdomain und IP-Familie aktivierbar
  • lokales Archiv statt Löschen von Hetzner-Records
  • frei einstellbares Update-Intervall mit Countdown
  • Admin- und Mitgliedskonten mit Domain- oder Subdomain-Rechten
  • ausschließlich Administratoren verwalten Konten und Freigaben; Admin-Vollzugriff besitzt keine bearbeitbaren Einzelrechte
  • automatische Release-Prüfung, Update-Benachrichtigung und kontrolliertes Ein-Klick-Update mit Rollback
  • administrativer Container-Neustart mit automatischem Wiederverbinden der Weboberfläche
  • eine Pi-hole-v6-Verbindung mit verschlüsseltem Anwendungspasswort und geschützter Anzeige nach erneuter Admin-Passwortprüfung
  • lokale Pi-hole-DNS-Einträge für IPv4 und IPv6 anlegen, aktivieren/deaktivieren, bearbeiten, aus der WebUI trennen, wiederherstellen und vollständig löschen

Die Pi-hole-Seite zeigt sofort den gespeicherten Stand. Records neu einlesen aktualisiert ihn mit einer begrenzten API-Abfrage. Eine nicht mehr erreichbare Verbindung kann über Lokale Verbindung entfernen entfernt werden. Der Container muss die verbundene Pi-hole-Adresse für API-Aktionen direkt im internen Netz erreichen können. Pi-hole-Sitzungen werden nur für die jeweilige Aktion erstellt und danach aktiv beendet; dauerhaft gespeichert wird ausschließlich das mit dem Master-Key verschlüsselte Anwendungspasswort. Secret- und Datenverzeichnis werden auf 0700, Master-Key und SQLite-Datei auf 0600 gehärtet. Das Compose-Setup verwendet außerdem ein schreibgeschütztes Root-Dateisystem und ein begrenztes /tmp-Tmpfs. HTTPS mit gültigem Zertifikat ist empfohlen.

Damit ein Pi-hole-Anwendungspasswort lokale DNS-Einträge ändern darf, muss am Pi-hole webserver.api.app_sudo=true gesetzt sein; Pi-hole verwendet hierfür standardmäßig false. Zusätzlich darf der globale Schalter misc.readOnly nicht aktiviert sein. Bei einem Pi-hole-Docker-Container entspricht das beispielsweise FTLCONF_webserver_api_app_sudo: "true" und FTLCONF_misc_readOnly: "false".

Port 9090 verwendet zunächst unverschlüsseltes HTTP. Den Port daher nur in einem vertrauenswürdigen Netz freigeben und nicht offen ins Internet stellen.

Netzwerkausfälle und Diagnose

Die Übersicht zeigt unter Letzte Update-Läufe die letzten sechs Läufe mit Änderungen, Fehlern oder Teilerfolgen. Erfolgreiche Prüfungen ohne Änderungen werden ausgeblendet; die vollständige Laufhistorie bleibt für die Diagnose erhalten.

Der Build und die Container-Prüfungen verwenden Go 1.26.9. Damit sind auch die vom Sicherheitscheck gemeldeten HTTP-/Template-Probleme der bisherigen Go-1.25.13-Basis behoben (siehe etwa GO-2026-6617).

Nach fehlgeschlagenen DynDNS-Läufen folgen weitere Versuche nach 5, 10, 20, 40 und höchstens 60 Sekunden; nach einem erfolgreichen Lauf gilt wieder das konfigurierte Intervall. Ein IP-Endpunkt hat einschließlich DNS, TLS und Antwort höchstens zehn Sekunden Zeit. IPv4 und IPv6 werden unabhängig und parallel ermittelt. Fehlgeschlagene Abfragen liefern keine alte IP für ein DNS-Update; vorhandene DNS-Records bleiben dann bestehen.

Hetzner-Änderungen gelten erst dann als erfolgreich, wenn der asynchrone API-Auftrag success meldet; running, spätere Fehler und Zeitüberschreitungen werden nicht als Erfolg gespeichert. Die Bestätigung wird bis zu 20 Sekunden abgewartet. Ein Fehler beim A-Record verhindert die Aktualisierung des AAAA-Records nicht und umgekehrt; bestätigte Teiländerungen bleiben sichtbar. Siehe den Hetzner-API-Vertrag für Actions.

WebUI-Änderungen an DNS-Zielen und automatische Läufe werden vollständig nacheinander ausgeführt, damit ein alter Lauf keine gelöschten Ziele neu anlegt oder feste Adressen überschreibt. Dauert ein laufender Vorgang länger als fünf Sekunden, wird die neue WebUI-Änderung mit einer Meldung zum erneuten Senden abgewiesen. Unveränderte IP-Endpunkte brauchen beim Speichern der Updater-Einstellungen keine erneute DNS-Prüfung. Neue Endpunkte werden weiter geprüft (höchstens acht je IP-Familie).

Laufhistorie und letzter Status werden gemeinsam gespeichert, auch nach einem kontrollierten Abbruch. Beim Prozessstart werden durch einen Absturz offene Läufe als unterbrochen markiert. Der aktive Updater beginnt automatisch mit einer neuen Prüfung; beim Herunterfahren wird sein Abschluss abgewartet.

Bei einem vorübergehenden Fehler des System-DNS (Timeout/SERVFAIL) werden nur api.ipify.org, api6.ipify.org, ifconfig.co und api.hetzner.cloud zusätzlich direkt über Cloudflare und Quad9 aufgelöst (1.1.1.1, 9.9.9.9, 2606:4700:4700::1111, 2620:fe::fe, Port 53). System-DNS und die parallelen Ausweichabfragen haben jeweils zwei Sekunden. Jede neue Abfrage beginnt wieder mit System-DNS. Negative DNS-Antworten (NXDOMAIN), eigene Endpunkte und lokale Pi-hole-Namen erhalten keinen solchen Ausweichweg. HTTPS-Zertifikate und öffentliche IP-Endpunkte werden weiterhin geprüft. Dieser Mechanismus ersetzt keine funktionierende Route oder Firewall-Freigabe auf dem Produktionsserver.

Unter Fehler → Diagnose-Logs herunterladen können Administratoren einen JSON-Bericht exportieren. Er enthält Version, letzte Laufstatus, IP-Abfragen, DNS-Ausweichversuche, Fehlertypen, Wartezeiten, Wiederherstellung und DNS-/IPv4-Routing-Daten. Die Ereignisse werden im bestehenden Daten-Volume in zwei Dateien zu höchstens je 4 MiB mit Dateirechten 0600 gespeichert und überleben Container-Neustarts. Bei langen Ausfällen wird der Netzwerkzustand beim ersten Fehler und anschließend alle 15 Minuten festgehalten.

Der Export enthält ausschließlich festgelegte technische Felder. Rohmeldungen, Audit-Texte, Zugangsdaten, Cookies, Sitzungs-IDs, HTTP-Header, URL-Pfade und Query-Parameter werden nicht übernommen. IP-Adressen und Netzwerkrouten sind zur Diagnose enthalten. Die Aufzeichnung beginnt mit dieser Funktion; alte Läufe sind nur mit Zeit und Status enthalten. Der lokale Healthcheck bestätigt die Erreichbarkeit der Web-App, nicht die Erreichbarkeit von Internet/Hetzner. Bei no_route müssen auf Prod Gateway, Docker-Bridge, Forwarding/NAT und Firewall geprüft werden; ein Container-Neustart allein belegt keine Behebung.

S
Description
No description provided
Readme
365 KiB
0 Stars 0 Watchers 0 Forks
Languages
Go 73.9%
CSS 11%
HTML 10.2%
JavaScript 4.6%
Dockerfile 0.3%