vcard4reseller/deploy
Thomas Peterson c3e05257cb Deployment: Hetzner Cloud via Terraform (Multi-Node, skalierbar)
Infrastruktur als Code für den Skalierungs-Test auf Hetzner:
- deploy/terraform: privates Netz, Firewalls, 2 App-Nodes, DB-Node, Load
  Balancer (Health-Check /health); cloud-init bootet Docker + Stack je Node
- deploy/compose/docker-compose.prod.yml + nginx.prod.conf: App-Node-Stack
  (PHP-FPM + Nginx) routet /api,/p,/t,/css,/health → Symfony, Rest → Vue-SPA
- App-Anpassungen: HealthController (/health für LB), brand.css nach /css
  verschoben (kein Pfad-Clash mit SPA-Assets im Prod-Routing)
- deploy/README.md: Anleitung inkl. JWT-Key-Verteilung & Cross-Node-Test
- reference.php (auto-generiert) aus Versionierung entfernt

Terraform validiert (terraform validate), Prod-Compose-Syntax geprüft.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 21:20:58 +02:00
..
compose Deployment: Hetzner Cloud via Terraform (Multi-Node, skalierbar) 2026-05-31 21:20:58 +02:00
terraform Deployment: Hetzner Cloud via Terraform (Multi-Node, skalierbar) 2026-05-31 21:20:58 +02:00
README.md Deployment: Hetzner Cloud via Terraform (Multi-Node, skalierbar) 2026-05-31 21:20:58 +02:00

Deployment auf Hetzner Cloud (Terraform)

Provisioniert einen Multi-Node-Test mit nachgewiesener Skalierbarkeit:

                 Hetzner Load Balancer (lb11, :80 → /health)
                 /                       \
          vcard4-app-1            vcard4-app-2      (zustandslose App-Nodes)
                 \                       /
        ┌─────────┴───────────┐   ┌─────┴──────────────────┐
   vcard4-db (MariaDB, Volume)     Hetzner Object Storage (S3, Assets)

App-Nodes sind zustandslos (Code + Docker), State liegt in DB (eigene VM) und Object Storage (Hintergrund-PDFs/Schriften). Dadurch beliebig horizontal skalierbar.

Voraussetzungen (einmalig, manuell)

  1. Terraform ≥ 1.6 + Hetzner API-Token (Projekt → Security → API Tokens, Read&Write).
  2. Object Storage in der Hetzner Console anlegen: Bucket + Access-Key/Secret (hcloud/Terraform verwalten Object Storage derzeit nicht).
  3. Git-Repo erreichbar für die Nodes (öffentlich oder Deploy-Token in der repo_url).
  4. JWT-Schlüsselpaar einmal erzeugen — auf allen Nodes identisch:
    # lokal im backend/-Container oder mit openssl
    docker compose exec php php bin/console lexik:jwt:generate-keypair --overwrite
    # → backend/config/jwt/private.pem & public.pem + Passphrase aus .env (JWT_PASSPHRASE)
    
    Inhalt der beiden PEM-Dateien + Passphrase in terraform.tfvars eintragen.
  5. DNS: nach terraform apply einen A-Record domain → load_balancer_ip setzen (Hetzner DNS ist separat; A-Record auch für die Custom-Domain-Funktion, KONZEPT §11).

Deploy

cd deploy/terraform
cp terraform.tfvars.example terraform.tfvars   # ausfüllen
terraform init
terraform plan
terraform apply
terraform output                               # load_balancer_ip etc.

cloud-init installiert auf jedem App-Node Docker, klont das Repo, schreibt .env.prod.local + JWT-Keys, baut das SPA und startet deploy/compose/docker-compose.prod.yml. Migrationen laufen nur auf app-1. Deploy-Log auf dem Node: /var/log/vcard4-deploy.log.

Skalierbarkeit verifizieren

# 1) Login + Token holen (LB-IP oder Domain)
TOKEN=$(curl -s -X POST http://<LB-IP>/api/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"admin@vcard4reseller.de","password":"admin"}' | jq -r .token)

# 2) Health über den LB (verteilt auf beide Nodes)
for i in $(seq 1 6); do curl -s http://<LB-IP>/health; echo; done

# 3) Cross-Node-Beweis: Hintergrund über den LB hochladen, dann
#    auf BEIDEN Nodes rendern (per SSH)  identische PDFs aus Object Storage:
ssh root@<app-1-ip> 'cd /opt/vcard4 && docker compose -f deploy/compose/docker-compose.prod.yml exec -T php php bin/console app:render-card erika-mustermann'
ssh root@<app-2-ip> 'cd /opt/vcard4 && docker compose -f deploy/compose/docker-compose.prod.yml exec -T php php bin/console app:render-card erika-mustermann'

Horizontal skalieren: app_count erhöhen → terraform apply (LB-Targets werden automatisch ergänzt).

Noch offen / Hinweise

  • TLS: aktuell HTTP am LB. Für HTTPS entweder Hetzner Managed Certificate am LB (DNS bei Hetzner) oder Caddy auf den Nodes (On-Demand-TLS) — letzteres ist auch der Weg für die Custom-Domains der Firmenkunden (§11).
  • Trusted Proxies: für korrekte absolute URLs hinter dem LB framework.trusted_proxies auf %env(TRUSTED_PROXIES)% setzen.
  • Seed: optional einmalig app:seed auf app-1 für Demo-Daten.
  • Updates: neuen Stand ausrollen = auf den App-Nodes git pull + docker compose ... up -d --build (später per CI/Skript).