Files
craftvia/docs/_certvia-archiv/DEPLOY-COOLIFY.md
T
msolarczekandClaude Opus 5 cadaedc6cc L10b Betrieb & Aufräumen: Deploy – craftvia-worker, CI-Testjob, DEPLOY.md, Certvia-Doku archiviert
- docker-compose.coolify(.prebuilt).yml: Service craftvia-worker (Target worker, Chromium,
  shm_size 1gb, gleiche Härtung), Craftvia-Variablen für app und worker.
- Dockerfile: worker-Stage mit HOME=/home/app (Chromium-Profil für non-root); lokaler
  docker build der Targets runner und worker erfolgreich, PDF-Erzeugung im Image geprüft.
- .env.example/.env.prod.example/.env.coolify.example: alle Craftvia-Variablen inkl. RLS,
  KI-Provider, PDF_CHROMIUM_PATH, OFFLINE_MAX_DAYS, API_RATE_LIMIT_*, AI_GENERATION_RETENTION_DAYS,
  AI_MONTHLY_TOKEN_LIMIT.
- CI (.github, .gitea): Job gate mit Postgres (pgvector) und Redis als Service: migrate deploy,
  seed, Passwort für craftvia_app, tsc, lint, build, npm run test.
- docs/craftvia/DEPLOY.md (aus den Certvia-Deploy-Docs abgeleitet): Architektur, Domains, Secrets,
  Worker, Migrationen, RLS-Aktivierung, Backup/Restore, KI, Rate Limits, Aufbewahrung, Smoke,
  Update/Rollback. build-and-push-images.sh baut craftvia-worker.
- Certvia-/ISMS-Dokumente aus docs/ nach docs/_certvia-archiv/ (mit README); Verweise in README.md
  und Skript-Kommentaren angepasst.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 18:19:19 +02:00

7.7 KiB
Raw Blame History

Deployment: Testserver via Coolify (intern)

Zielumgebung: interner Coolify-Host (192.168.1.207), Docker-Compose-Stack, HTTP über eine *.sslip.io-Domain (kein öffentliches TLS). Ergänzt docs/HANDOVER-DEVOPS.md. Deploy-Datei: docker-compose.coolify.yml.

Architekturüberblick (Coolify)

  • Coolify deployt den Compose-Stack aus diesem Repo und übernimmt Reverse-Proxy + Routing — die App wird nicht per Host-Port exponiert.
  • Services: migrate (Init-Job) → app (Next.js standalone) + postgres (pgvector), redis, garage (Objektspeicher) + garage-provision (Init-Job).
  • Migrationen laufen als eigener Init-Job migrate (prisma migrate deploy, schlanke migrate-Stage ohne next build) vor app — der App-Container migriert selbst nicht.
  • Objektspeicher Garage (ersetzt MinIO, Community EOL): S3-kompatibel, Single-Node. Buckets/Keys legt NICHT die S3-API an, sondern der Init-Job garage-provision (Admin-API, idempotent) vor app. Details/Cutover: eigener Abschnitt unten.
  • Redis läuft mit requirepass (F-18): REDIS_PASSWORD als Coolify-Env setzen und in die REDIS_URL einsetzen (redis://:<pw>@redis:6379).
  • Netzsegmentierung (F-18): postgres/redis/garage/migrate liegen im internen Netz (internal: true, kein Egress); nur app ist zusätzlich im default-Netz (Coolify-Proxy). Garage wird nicht per Traefik/Host-Port exponiert.
  • Secrets/Config kommen aus den Coolify-Env-Variablen (Referenz: .env.coolify.example), nicht aus einer committeten .env.

1. Gitea mit Coolify verbinden (Deploy Key)

SSH ist erreichbar (git@…sslip.io:22), daher Deploy-Key:

  1. Coolify → Keys & Tokens → Private Keys: SSH-Key generieren (oder beim Anlegen der Ressource „Private Repository (deploy key)“ automatisch). Öffentlichen Key kopieren.
  2. Gitea → Repo msolarczek/ISMS-Tool → Settings → Deploy Keys → Add Deploy Key: Key einfügen, nur Lesezugriff.
  3. Repo-URL in Coolify (SSH): git@gitea-vkbhbn2qdkz5ppk9q4qgb0tn.192.168.1.207.sslip.io:msolarczek/ISMS-Tool.git

2. Ressource anlegen

  1. Projekt/Environment wählen → + New → Private Repository (deploy key).
  2. Repo ISMS-Tool, Branch devops/coolify-testserver (nach erfolgreichem Test → main).
  3. Build Pack: Docker Compose, Compose Location: docker-compose.coolify.yml.

3. Environment-Variablen

Werte aus .env.coolify.example in Coolify eintragen. Kritisch:

  • Hostnamen = Compose-Service-Namen: postgres, redis, garage (nicht localhost); S3_ENDPOINT=http://garage:3900.
  • AUTH_SECRET frisch: openssl rand -base64 32.
  • AUTH_URL = exakt die in Schritt 4 vergebene App-Domain (http://…).
  • Garage (siehe Abschnitt unten): GARAGE_RPC_SECRET + GARAGE_ADMIN_TOKEN (je openssl rand -hex 32), sowie S3_ACCESS_KEY = GK+24 Hex (echo "GK$(openssl rand -hex 12)") und S3_SECRET_KEY = openssl rand -hex 32. Alle literal setzen (nicht via ${…} — Coolify-Interpolationsfalle).

4. Domain & HTTP

  • Beim Service app unter Domains die *.sslip.io-Domain übernehmen, Schema http://.
  • Dieselbe Domain als AUTH_URL setzen (sonst schlägt der NextAuth-Login fehl).

5. Persistenz

Named Volumes müssen als Persistent Storage erkannt sein — v. a. pgdata (sonst Datenverlust bei Redeploy) und garage_meta (Bucket-/Key-/Layout-Definitionen — ohne meta sind die Objektdaten in garage_data nicht adressierbar). Ebenso garage_data, redisdata, backups. garage_meta in die Host-Backup-Strategie aufnehmen (analog pgdata).

6. Deploy

Deploy starten. Reihenfolge: postgres (healthy) → migrate (läuft einmalig durch) → app (startet erst nach erfolgreichem Migrate). Logs von migrate/app bei Fehlern prüfen.

7. Demo-Admin anlegen (einmalig, Testserver)

Der Demo-Seed läuft nicht automatisch. Im Coolify-Terminal des migrate-Containers (builder-Image, hat tsx):

npx tsx prisma/seed.ts

Login danach: admin@demo.example / Demo1234!.

Produktiv: kein Demo-Seed. Stattdessen einmaliger Bootstrap des Plattform-Admins (provisionTenant({ admin.isPlatformAdmin: true })) — Skript noch zu erstellen (siehe docs/HANDOVER-DEVOPS.md §4).

8. Smoke-Test

Über die App-Domain: Login, Admin-Konsole, eine Modul-Seite.

Redeploy / Migrationen-Nachziehen

Jeder Coolify-Redeploy baut neu und lässt migrate erneut laufen (migrate deploy ist idempotent — nur neue Migrationen werden angewandt). App startet erst nach erfolgreichem Migrate.

Von Test zu Produktiv (Delta)

  • AUTH_SECRET, DB-Passwörter und die Garage-Secrets (GARAGE_RPC_SECRET, GARAGE_ADMIN_TOKEN, S3_ACCESS_KEY/S3_SECRET_KEY) neu/aus Secret-Store; eigene Domain + gültiges TLS.
  • Kein Demo-Seed; Erst-Superadmin per Bootstrap-Skript.
  • Backups (pg_dump/Volume-Snapshots inkl. garage_meta) + Restore-Test, Monitoring/Alerting.
  • Ggf. ≥ 2 app-Replicas hinter dem Coolify-Proxy.

Objektspeicher (Garage) — Cutover, Provisioning & Rollback

Ablösung von MinIO (Community EOL) durch Garage (S3-kompatibel, aktiv gepflegt, Single-Node). Konzept: docs/KONZEPT-garage-migration.md. Es gibt keine produktiven Daten → Neu-Deploy statt Datenmigration (kein rclone, kein Wartungsfenster).

Konfig-Bausteine

  • deploy/garage.toml (eingecheckt, secret-frei): replication_factor=1, s3_region=us-east-1, S3-API :3900, Admin-API :3903. rpc_secret/admin_token liest der Daemon aus der Env (GARAGE_RPC_SECRET/GARAGE_ADMIN_TOKEN).
  • Volumes: garage_meta (kritisch, ins Host-Backup) + garage_data.
  • Init-Job garage-provision (scripts/garage-provision.ts, Admin-API, idempotent): Layout → Bucket isms-documents → Access-Key importieren (aus S3_ACCESS_KEY/…) → Rechte read/write. Läuft bei jedem Deploy; „already exists“ = Erfolg. Ohne GARAGE_ADMIN_TOKEN No-op. Loggt keine Secrets.

Cutover-Runbook (Test-Instanz)

  1. Compose enthält bereits garage + garage-provision; der alte minio-Service ist auskommentiert (Rollback-Netz) und wird erst nach Abnahme entfernt.
  2. Coolify-Env literal setzen (nicht ${…}): S3_ENDPOINT=http://garage:3900, S3_REGION=us-east-1, S3_BUCKET=isms-documents, GARAGE_RPC_SECRET, GARAGE_ADMIN_TOKEN, S3_ACCESS_KEY (GK+24 Hex), S3_SECRET_KEY (64 Hex).
  3. Deploy. Reihenfolge: garage (healthy: /garage status) → garage-provision (legt Layout/Bucket/Key/Rechte an) → app. Optionaler DB-Reset nur, falls alte Objekt-Referenzen stören (bei frischer/geseedeter Instanz unnötig).
  4. Abnahme (§ KONZEPT §9): Upload → Download (/files/[...key]) → Backup-Export .cvb
    • Download → DSGVO-ZIP → Restore mit Mandanten-Isolation → Backup-Historie List/ Aufräumen → Negativfall „fehlender Bucket“ = sprechender Fehler.
  5. garage_meta in der Host-Backup-Strategie bestätigen. Erst danach den minio-Block und das Volume miniodata aus docker-compose.coolify.yml entfernen.

Rollback (unkritisch, keine Prod-Daten): garage/garage-provision wieder aus-, den auskommentierten minio-Block wieder einkommentieren und die alten S3_*/ MINIO_*-Env setzen; neu deployen. Solange minio + miniodata noch existieren, ist das ein reiner Compose-/Env-Wechsel.

Provisioning erneut anstoßen / debuggen: Im Coolify-Terminal eines Containers mit tsx (migrate-Image) npx tsx scripts/garage-provision.ts erneut laufen lassen (idempotent). „Container weg ohne Logs“ (Coolify entfernt fehlgeschlagene Container sofort): Provisioning-Ausgabe zusätzlich in eine Datei spiegeln, z. B. npx tsx scripts/garage-provision.ts 2>&1 | tee /tmp/garage-provision.log.