Die .env bei jedem Start lesen, statt sie in den Container einzufrieren #6

Merged
nexus merged 1 commit from feature/config-file-at-start into main 2026-08-16 20:05:43 +02:00
Owner

Was ändert dieser PR?

Die .env wird jetzt vom Programm selbst gelesen, bei jedem Start, aus dem
Mount unter /etc/hetzner-ddns/.env. Eine Konfigurationsänderung braucht damit
nur noch docker compose restart.

Vorher gab Compose die Datei über env_file: beim Erzeugen des Containers
an die Engine weiter. Die Werte frieren dort ein: Wer die .env bearbeitet und
neu startet, bekommt schweigend die alte Konfiguration — kein Fehler, keine
Warnung, nur ein Prozess, der nicht das tut, was in der Datei steht. Genau so
ist ein Apex-Eintrag über Tage unbemerkt nicht aktualisiert worden, obwohl er
in der .env stand.

Im Einzelnen:

  • docker-compose.yml mountet die .env statt sie über env_file:
    einzubinden. Der Pfad im Container steht fest und ist nicht konfigurierbar —
    dieselbe Konvention wie beim Token unter /run/secrets/hetzner_api_token.
  • Echte Umgebungsvariablen gewinnen weiterhin gegen die Datei. Tun sie es, wird
    das beim Start als Warnung protokolliert und nennt die betroffenen
    Einstellungen: das ist der Fall, in dem eine Änderung wirkungslos aussieht.
  • Der Startsatz nennt mit config_from, woher die Konfiguration stammt;
    token_from unterscheidet jetzt .env und Umgebung.
  • Der Parser meldet unlesbare Zeilen mit Zeilennummer und weist einen doppelt
    gesetzten Schlüssel ab, statt die spätere Kopie still gewinnen zu lassen.
  • Fehlt die Datei auf der Host-Seite des Mounts, legt die Engine ein
    Verzeichnis an — dieser Fall benennt sich jetzt selbst.
  • Ohne Datei kommt weiterhin alles aus der Umgebung (docker run -e,
    Kubernetes), unverändert.

Checkliste

  • make lint test läuft durch
  • make check-domains ist grün — keine echten Domains im Diff, nur
    example.com / example.org / example.net
  • Keine .env und keine Zugangsdaten im Diff
  • Neue Logik hat Tests
  • README bzw. .env.example nachgezogen, falls sich Konfiguration ändert
  • CHANGELOG.md ergänzt, falls die Änderung nach außen sichtbar ist

Getestet mit

  • make lint, go test -race ./...
  • hetzner-ddns check gegen eine Beispiel-.env: einmal nur aus der Datei
    (config_from=.env, vier Targets), einmal mit gesetzter Umgebung (Warnung
    mit settings="DDNS_RECORDS, DDNS_TTL", Werte der Umgebung wirksam), einmal
    mit doppeltem Schlüssel und kaputter Zeile (beide Fehler mit Zeilennummer).
# Was ändert dieser PR? Die `.env` wird jetzt vom Programm selbst gelesen, bei **jedem Start**, aus dem Mount unter `/etc/hetzner-ddns/.env`. Eine Konfigurationsänderung braucht damit nur noch `docker compose restart`. Vorher gab Compose die Datei über `env_file:` beim **Erzeugen** des Containers an die Engine weiter. Die Werte frieren dort ein: Wer die `.env` bearbeitet und neu startet, bekommt schweigend die alte Konfiguration — kein Fehler, keine Warnung, nur ein Prozess, der nicht das tut, was in der Datei steht. Genau so ist ein Apex-Eintrag über Tage unbemerkt nicht aktualisiert worden, obwohl er in der `.env` stand. Im Einzelnen: - `docker-compose.yml` mountet die `.env` statt sie über `env_file:` einzubinden. Der Pfad im Container steht fest und ist nicht konfigurierbar — dieselbe Konvention wie beim Token unter `/run/secrets/hetzner_api_token`. - Echte Umgebungsvariablen gewinnen weiterhin gegen die Datei. Tun sie es, wird das beim Start als Warnung protokolliert und nennt die betroffenen Einstellungen: das ist der Fall, in dem eine Änderung wirkungslos aussieht. - Der Startsatz nennt mit `config_from`, woher die Konfiguration stammt; `token_from` unterscheidet jetzt `.env` und Umgebung. - Der Parser meldet unlesbare Zeilen mit Zeilennummer und weist einen doppelt gesetzten Schlüssel ab, statt die spätere Kopie still gewinnen zu lassen. - Fehlt die Datei auf der Host-Seite des Mounts, legt die Engine ein Verzeichnis an — dieser Fall benennt sich jetzt selbst. - Ohne Datei kommt weiterhin alles aus der Umgebung (`docker run -e`, Kubernetes), unverändert. ## Checkliste - [x] `make lint test` läuft durch - [x] `make check-domains` ist grün — keine echten Domains im Diff, nur `example.com` / `example.org` / `example.net` - [x] Keine `.env` und keine Zugangsdaten im Diff - [x] Neue Logik hat Tests - [x] README bzw. `.env.example` nachgezogen, falls sich Konfiguration ändert - [x] `CHANGELOG.md` ergänzt, falls die Änderung nach außen sichtbar ist ## Getestet mit - `make lint`, `go test -race ./...` - `hetzner-ddns check` gegen eine Beispiel-`.env`: einmal nur aus der Datei (`config_from=.env`, vier Targets), einmal mit gesetzter Umgebung (Warnung mit `settings="DDNS_RECORDS, DDNS_TTL"`, Werte der Umgebung wirksam), einmal mit doppeltem Schlüssel und kaputter Zeile (beide Fehler mit Zeilennummer).
Read the .env on every start instead of freezing it into the container
All checks were successful
CI / test (push) Successful in 1m38s
CI / test (pull_request) Successful in 1m46s
CI / image (push) Successful in 1m14s
CI / image (pull_request) Successful in 41s
47b02978cc
Compose hands `env_file` to the engine when the container is created, and the
values are frozen into it from then on. Editing the .env afterwards changes
nothing: `docker compose restart` re-runs the same process with the same
settings, and nothing fails or says so. The tell is a startup that does not
match the file — records configured but never checked.

So the program now reads the file itself, from a fixed /etc/hetzner-ddns/.env,
on every start. A configuration change is `docker compose restart`, and the
compose file mounts the .env rather than declaring env_file.

Real environment variables still win, which is what an `environment:` block or
a Kubernetes deployment needs — but when they do, startup says so by name,
because that is exactly the case where an edit appears to be ignored. The
startup line also reports config_from, and the token source now distinguishes
the .env from the environment.

The parser reports what it cannot read with a line number, and treats a key
set twice as an error rather than letting the later copy win silently: the one
that wins is rarely the one that was edited. A mount whose host-side file is
missing arrives as a directory, and that case names itself.
nexus merged commit 6c4e3e6af5 into main 2026-08-16 20:05:43 +02:00
nexus deleted branch feature/config-file-at-start 2026-08-16 20:05:43 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
nexus/hetzner-ddns!6
No description provided.