DevOps: Coolify-Testserver-Deployment (Compose, Migrations-Job, Runbook)
- docker-compose.coolify.yml: Coolify-taugliche Variante (kein host-port, Env via Coolify-Variablen, migrate-Init-Job vor app, app-Healthcheck) - .env.coolify.example: Referenz der Coolify-Env-Variablen (nur Platzhalter) - docs/DEPLOY-COOLIFY.md: Runbook (Gitea-Deploy-Key, Ressource, Env, Domain, Seed) - .gitignore: .env.coolify.example whitelisten Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,39 @@
|
|||||||
|
# Referenz für die Coolify-Environment-Variablen (Testserver, intern).
|
||||||
|
# ECHTE Secrets NUR in Coolify eintragen — diese Datei enthält nur Platzhalter.
|
||||||
|
# In Coolify: Ressource -> Environment Variables (Bulk-Paste möglich).
|
||||||
|
# Hostnamen sind die Compose-Service-Namen (postgres/redis/minio), NICHT localhost.
|
||||||
|
|
||||||
|
# --- Datenbank (Service "postgres") ---
|
||||||
|
POSTGRES_USER=isms
|
||||||
|
POSTGRES_PASSWORD=CHANGE_ME_db_password
|
||||||
|
POSTGRES_DB=isms
|
||||||
|
DATABASE_URL=postgresql://isms:CHANGE_ME_db_password@postgres:5432/isms?schema=public
|
||||||
|
|
||||||
|
# --- Redis (Service "redis") ---
|
||||||
|
REDIS_URL=redis://redis:6379
|
||||||
|
|
||||||
|
# --- Objektspeicher (Service "minio") ---
|
||||||
|
# S3_ACCESS_KEY == MINIO_ROOT_USER und S3_SECRET_KEY == MINIO_ROOT_PASSWORD!
|
||||||
|
S3_ENDPOINT=http://minio:9000
|
||||||
|
S3_ACCESS_KEY=isms
|
||||||
|
S3_SECRET_KEY=CHANGE_ME_minio_password
|
||||||
|
S3_BUCKET=isms-documents
|
||||||
|
MINIO_ROOT_USER=isms
|
||||||
|
MINIO_ROOT_PASSWORD=CHANGE_ME_minio_password
|
||||||
|
|
||||||
|
# --- Auth (NextAuth) ---
|
||||||
|
# AUTH_SECRET: openssl rand -base64 32
|
||||||
|
# AUTH_URL: exakt die Coolify-Domain des app-Service (http:// für intern)
|
||||||
|
AUTH_SECRET=CHANGE_ME_openssl_rand_base64_32
|
||||||
|
AUTH_URL=http://REPLACE-WITH-COOLIFY-SSLIP-DOMAIN
|
||||||
|
|
||||||
|
# --- KI-Provider (optional, aktuell ungenutzt) ---
|
||||||
|
AI_PROVIDER=anthropic
|
||||||
|
AI_API_KEY=
|
||||||
|
|
||||||
|
# --- E-Mail (optional, im Test ungenutzt) ---
|
||||||
|
SMTP_HOST=
|
||||||
|
SMTP_PORT=1025
|
||||||
|
SMTP_USER=
|
||||||
|
SMTP_PASSWORD=
|
||||||
|
SMTP_FROM=isms@example.com
|
||||||
@@ -33,6 +33,7 @@ yarn-error.log*
|
|||||||
# env files (can opt-in for committing if needed)
|
# env files (can opt-in for committing if needed)
|
||||||
.env*
|
.env*
|
||||||
!.env.example
|
!.env.example
|
||||||
|
!.env.coolify.example
|
||||||
|
|
||||||
# vercel
|
# vercel
|
||||||
.vercel
|
.vercel
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
# Coolify-Deployment (Testserver, intern) — abgeleitet von docker-compose.yml.
|
||||||
|
# Unterschiede zum lokalen Dev-Compose:
|
||||||
|
# - keine host "ports": Coolify-Proxy routet die Domain intern auf app:3000
|
||||||
|
# - Konfiguration über Coolify-Env-Variablen statt env_file: .env
|
||||||
|
# - Service "migrate": Init-Job (prisma migrate deploy), läuft einmalig VOR app
|
||||||
|
# - kein mailhog/worker (Dev bzw. noch nicht lauffähig)
|
||||||
|
# In Coolify als "Docker Compose Location" -> docker-compose.coolify.yml setzen.
|
||||||
|
services:
|
||||||
|
# Einmaliger Migrations-Job. Nutzt die builder-Stage (volle node_modules inkl.
|
||||||
|
# Prisma-CLI/tsx), da die runner-Stage kein Prisma-CLI enthält. Idempotent.
|
||||||
|
migrate:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
target: builder
|
||||||
|
command: sh -c "npx prisma migrate deploy"
|
||||||
|
environment:
|
||||||
|
DATABASE_URL: ${DATABASE_URL}
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
restart: "no"
|
||||||
|
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
target: runner
|
||||||
|
environment:
|
||||||
|
DATABASE_URL: ${DATABASE_URL}
|
||||||
|
REDIS_URL: ${REDIS_URL}
|
||||||
|
AUTH_SECRET: ${AUTH_SECRET}
|
||||||
|
AUTH_URL: ${AUTH_URL}
|
||||||
|
S3_ENDPOINT: ${S3_ENDPOINT}
|
||||||
|
S3_ACCESS_KEY: ${S3_ACCESS_KEY}
|
||||||
|
S3_SECRET_KEY: ${S3_SECRET_KEY}
|
||||||
|
S3_BUCKET: ${S3_BUCKET}
|
||||||
|
AI_PROVIDER: ${AI_PROVIDER}
|
||||||
|
AI_API_KEY: ${AI_API_KEY}
|
||||||
|
SMTP_HOST: ${SMTP_HOST}
|
||||||
|
SMTP_PORT: ${SMTP_PORT}
|
||||||
|
SMTP_USER: ${SMTP_USER}
|
||||||
|
SMTP_PASSWORD: ${SMTP_PASSWORD}
|
||||||
|
SMTP_FROM: ${SMTP_FROM}
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
redis:
|
||||||
|
condition: service_started
|
||||||
|
minio:
|
||||||
|
condition: service_started
|
||||||
|
migrate:
|
||||||
|
condition: service_completed_successfully
|
||||||
|
healthcheck:
|
||||||
|
# Dependency-frei (node ist im Image vorhanden); alles < 500 gilt als gesund
|
||||||
|
test: ["CMD", "node", "-e", "require('http').get('http://127.0.0.1:3000/',r=>process.exit(r.statusCode<500?0:1)).on('error',()=>process.exit(1))"]
|
||||||
|
interval: 15s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
start_period: 30s
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
image: pgvector/pgvector:pg16
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: ${POSTGRES_USER}
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
POSTGRES_DB: ${POSTGRES_DB}
|
||||||
|
volumes:
|
||||||
|
- pgdata:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
volumes:
|
||||||
|
- redisdata:/data
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
minio:
|
||||||
|
image: minio/minio:latest
|
||||||
|
command: server /data --console-address ":9001"
|
||||||
|
environment:
|
||||||
|
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
||||||
|
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
||||||
|
volumes:
|
||||||
|
- miniodata:/data
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
pgdata:
|
||||||
|
redisdata:
|
||||||
|
miniodata:
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
# 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`, `minio`.
|
||||||
|
- **Migrationen** laufen als eigener Init-Job `migrate` (`prisma migrate deploy`, builder-Image) **vor** `app` — der App-Container migriert selbst nicht.
|
||||||
|
- **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`, `minio` (nicht `localhost`).
|
||||||
|
- `AUTH_SECRET` frisch: `openssl rand -base64 32`.
|
||||||
|
- `AUTH_URL` = exakt die in Schritt 4 vergebene App-Domain (`http://…`).
|
||||||
|
- `MINIO_ROOT_USER/PASSWORD` müssen `S3_ACCESS_KEY/S3_SECRET_KEY` entsprechen.
|
||||||
|
|
||||||
|
## 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). Ebenso `miniodata`, `redisdata`.
|
||||||
|
|
||||||
|
## 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` und DB-/MinIO-Passwörter neu/aus Secret-Store; eigene Domain + gültiges TLS.
|
||||||
|
- **Kein** Demo-Seed; Erst-Superadmin per Bootstrap-Skript.
|
||||||
|
- Backups (`pg_dump`/Volume-Snapshots) + Restore-Test, Monitoring/Alerting.
|
||||||
|
- Ggf. ≥ 2 `app`-Replicas hinter dem Coolify-Proxy.
|
||||||
Reference in New Issue
Block a user