Files
watermaps/infra/opentofu/README.md
T
BuTzZ 2064570913
Test and publish container images / test (push) Successful in 2m43s
Test and publish container images / publish (push) Successful in 2m56s
Automate immutable production deployments
2026-07-29 13:00:50 +02:00

4.4 KiB

Watermaps auf Hetzner Cloud

Diese OpenTofu-Konfiguration erstellt einen einzelnen Ubuntu-24.04-Server mit fester IPv4-Adresse, vorgeschalteter Hetzner-Firewall und einem persistenten ext4-Volume für Routingdaten, Geofabrik-Rohdaten und die PostGIS-Datenbank mit Häfen, Schleusen, Brücken und Kontaktdaten. Kartenkacheln, DNS-Einträge und konkrete Anwendungsversionen sind bewusst nicht Teil dieses Infrastrukturmoduls. Die Anwendung wird anschließend als commitgenaues Container-Image aus der Gitea Registry deployt.

Konfiguration

Die lokale Datei terraform.tfvars wird aus der versionierten Vorlage angelegt und durch die lokale .gitignore ausgeschlossen:

cp terraform.tfvars.example terraform.tfvars

Vor dem ersten Plan müssen dort mindestens folgende Werte ersetzt werden:

  • hcloud_token: Read/Write-API-Token des richtigen Hetzner-Cloud-Projekts
  • ssh_public_key: vollständiger Inhalt des öffentlichen SSH-Schlüssels
  • admin_cidrs: öffentliche Administrator-IP mit /32 beziehungsweise ein bewusst gewähltes Netz

terraform.tfvars.example bleibt als geheimnisfreie Vorlage versioniert. Da terraform.tfvars den Token im Klartext enthält, sollte sie nur für den lokalen Benutzer lesbar sein:

chmod 600 terraform.tfvars

Auch terraform.tfstate bleibt lokal und wird nicht nach Git oder auf den Anwendungsserver übertragen. Nach jedem Import oder Apply muss die State-Datei mit Dateimodus 0600 verschlüsselt gesichert werden, weil OpenTofu sie für spätere Änderungen an denselben Ressourcen benötigt. Ist ein älterer State verloren, werden vorhandene Ressourcen vor jedem Apply anhand ihrer Hetzner-IDs importiert; ein paralleles Neuerzeugen gleichnamiger Ressourcen ist nicht zulässig.

Infrastruktur erzeugen

cd infra/opentofu
tofu init
tofu fmt -check
tofu validate
umask 077
tofu plan -out=watermaps.tfplan
tofu apply watermaps.tfplan

Gespeicherte Plan-Dateien enthalten trotz als sensibel markierter Variablen unter Umständen den Hetzner-Token. Sie müssen deshalb wie terraform.tfvars und der State immer Modus 0600 haben und dürfen nicht weitergegeben werden.

Die feste IPv4-Adresse und den erforderlichen manuellen DNS-Eintrag zeigt OpenTofu anschließend an:

tofu output server_ipv4
tofu output dns_a_record
tofu output ssh_command

Der A-Record watermaps.incoso.eu kann direkt nach dem Apply auf server_ipv4 gesetzt werden. Das eigentliche SSL-Livegehen erfolgt erst durch das separate Deployment-Skript, nachdem DNS propagiert ist und die Routingdaten bereitstehen.

Auf dem Server

Cloud-init installiert Docker einschließlich Compose v2, curl, jq und rsync. Der Benutzer deploy erhält Zugriff per SSH und auf Docker. Es wird kein Git-Repository auf den Server geklont. Das persistente Volume wird nach dem Anhängen unter /srv/watermaps-data gemountet und enthält die vom Produktions-Stack verwendeten Verzeichnisse geofabrik, local und certbot. Ein systemd-Drop-in lässt Docker bei jedem Start auf den erfolgreichen Volume-Mount warten, bevor bestehende Container neu gestartet werden.

Der Status des ersten Starts lässt sich so prüfen:

ssh deploy@"$(tofu output -raw server_ipv4)" \
  "cloud-init status --wait && systemctl status watermaps-volume-setup --no-pager"

Cloud-init bindet ausschließlich das von OpenTofu erzeugte Volume über den stabilen Hetzner-Gerätepfad /dev/disk/by-id/scsi-0HC_Volume_<volume-id> ein. Es wird nicht heuristisch das erste Blockgerät mit dem Modellnamen Volume ausgewählt. Nach dem Mount wird zusätzlich die UUID des tatsächlich eingehängten Geräts geprüft. Ein Volume ohne lesbares Dateisystem wird standardmäßig nicht formatiert; nur bei einem garantiert neuen, leeren Volume darf initialize_empty_routing_volume = true für die einmalige Initialisierung gesetzt werden. Bei Importen und Serverwechseln bleibt der Wert false.

Der Löschschutz ist getrennt konfiguriert:

  • enable_server_protection für den austauschbaren Host,
  • enable_primary_ip_protection für die feste öffentliche Adresse,
  • enable_volume_protection für Routing-, Zertifikats- und PostGIS-Daten.

Bei einem kontrollierten Servertausch bleiben IP- und Volume-Schutz immer aktiv. Ein Plan, der hcloud_primary_ip.main oder hcloud_volume.routing_data ersetzen beziehungsweise löschen will, darf nicht angewendet werden. Zusätzlich verhindert OpenTofu für beide Ressourcen mit prevent_destroy eine versehentliche Löschung durch den IaC-Plan.