# 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: ```bash # 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 ```bash 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 ```bash # 1) Login + Token holen (LB-IP oder Domain) TOKEN=$(curl -s -X POST http:///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:///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@ '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@ '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).