Files
craftvia/docs/DEPLOY-COOLIFY.md
T
msolarczekandClaude Opus 5 c8e6f30a27
CI / build-and-check (push) Canceled after 0s
CI / audit (push) Canceled after 0s
CI / sbom (push) Canceled after 0s
Basis: Certvia dev@a48c5fb als Fundament für Craftvia
Unveränderter Stand von certvia/dev (a48c5fb) plus Craftvia-Spezifikation
und Brandbook unter docs/craftvia/. ISMS-Module werden im Folgecommit entfernt.

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

124 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`.