docs: Anleitungen fuer Kollegen — Anwender, Administration, Betrieb, Entwicklung
Bisher gab es fuer Kollegen keine Dokumentation: im Projekt lagen nur das CI/CD-Runbook und die Arbeitsanweisungen fuer die Entwicklung. Diese Luecke schliessen vier Anleitungen plus eine Einstiegsseite unter docs/. Alle vier wurden gegen den Quelltext geschrieben, nicht aus der Planung abgeleitet, und anschliessend unabhaengig gegengeprueft: jede zitierte Beschriftung ist woertlich aus de.json belegt, jede beschriebene Funktion im Code nachgewiesen, alle Befehle und Pfade gegen die echten Compose-Dateien, Dockerfiles und package.json-Skripte verifiziert. Die Gegenpruefung fand keine falsche Aussage. Die Einstiegsseite hebt die drei Punkte hervor, die in der Praxis am meisten Zeit gekostet haben: Anmeldung ueber den Benutzernamen statt der E-Mail, der Unterschied zwischen aktiviert und freigegeben, und dass ein blosses 'up -d' die laufenden Container nicht ersetzt. Nebenbefund beim Schreiben des Betriebshandbuchs, als #17 im Ledger erfasst: user-files/ ist in keiner Compose-Datei als Volume eingebunden — hochgeladene Profilbilder und DKV-Exporte ueberleben kein --force-recreate. Noch ohne Schaden, da bisher kein Nutzer ein Profilbild hinterlegt hat. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
This commit is contained in:
@@ -0,0 +1,330 @@
|
||||
<!-- generated-by: gsd-doc-writer -->
|
||||
# Tessera – Betriebshandbuch
|
||||
|
||||
Anleitung für den laufenden Betrieb von Tessera: Erstinstallation, neue Fassungen
|
||||
einspielen, Datenbank-Migrationen, Sicherung/Wiederherstellung und Fehlersuche.
|
||||
Richtet sich an Kolleg:innen, die die Docker-Container auf dem Server betreiben.
|
||||
|
||||
Für den Aufbau der CI/CD-Pipeline (Gitea Actions, act_runner) siehe
|
||||
[`docs/ci-cd-setup.md`](./ci-cd-setup.md) – dieses Dokument behandelt nur den
|
||||
Betrieb der bereits laufenden Installation, nicht deren automatisierten Build.
|
||||
|
||||
## Inhaltsverzeichnis
|
||||
|
||||
1. [Überblick der Dienste](#1-überblick-der-dienste)
|
||||
2. [Erstinstallation](#2-erstinstallation)
|
||||
3. [Konfiguration](#3-konfiguration)
|
||||
4. [Neue Fassung einspielen](#4-neue-fassung-einspielen)
|
||||
5. [Datenbank-Migrationen](#5-datenbank-migrationen)
|
||||
6. [Sicherung und Wiederherstellung](#6-sicherung-und-wiederherstellung)
|
||||
7. [Protokolle und Fehlersuche](#7-protokolle-und-fehlersuche)
|
||||
8. [Abgrenzung zur CI/CD-Pipeline](#8-abgrenzung-zur-cicd-pipeline)
|
||||
|
||||
---
|
||||
|
||||
## 1. Überblick der Dienste
|
||||
|
||||
Tessera besteht aus drei Containern, definiert in `docker-compose.yml` (Basis) und
|
||||
`docker-compose.prod.yml` (Produktionsvariante mit fertigen Images statt lokalem Build):
|
||||
|
||||
| Dienst | Image / Build | Host-Port | Zweck |
|
||||
|--------|---------------|-----------|-------|
|
||||
| `web` | `apps/web/Dockerfile` (Basis) bzw. `git.vicolab.de/schalli/tessera-ctl/web:latest` (Prod) | 3000 | Next.js-Frontend (Portal, Dashboard) |
|
||||
| `api` | `apps/api/Dockerfile` (Basis) bzw. `git.vicolab.de/schalli/tessera-ctl/api:latest` (Prod) | 3001 | NestJS-Backend (REST-API, Prisma/PostgreSQL-Zugriff) |
|
||||
| `db` | `postgres:16-alpine` | kein Host-Port | PostgreSQL-Datenbank |
|
||||
|
||||
Zusätzlich existiert `docker-compose.dev.yml` (Bind-Mounts für Live-Reload,
|
||||
Mailhog, OpenLDAP, phpLDAPadmin) für die lokale Entwicklung sowie
|
||||
`docker-compose.ci.yml` für den Gitea `act_runner` – beide sind für den
|
||||
Produktivbetrieb ohne Belang und werden hier nicht weiter behandelt.
|
||||
|
||||
**Netzwerke** (siehe `docker-compose.yml`):
|
||||
|
||||
- `frontend-net` – nur `web`, darüber ist der Browser-Zugriff erreichbar (Port 3000).
|
||||
- `backend-net` – `web` und `api`, Kommunikation zwischen Frontend-Server und Backend.
|
||||
- `data-net` – `api` und `db`, als `internal: true` markiert. Die Datenbank ist damit
|
||||
aus dem Host-Netzwerk grundsätzlich nicht erreichbar; Zugriff nur über
|
||||
`docker compose exec db ...` von innen.
|
||||
|
||||
**Startreihenfolge:** `db` → `api` → `web`, erzwungen über `depends_on` mit
|
||||
`condition: service_healthy`:
|
||||
|
||||
- `db`: Healthcheck `pg_isready -U tessera` (Intervall 5 s, 5 Versuche).
|
||||
- `api`: Healthcheck `wget --spider http://localhost:3001/health` (Intervall 10 s,
|
||||
3 Versuche, `start_period` 10 s in der Basis- / 20 s in der Prod-Variante). Der
|
||||
`/health`-Endpunkt (`apps/api/src/health/health.controller.ts`) prüft nur, dass
|
||||
der NestJS-Prozess antwortet – **keine** Datenbankverbindung. Ein "healthy"
|
||||
API-Container sagt also nichts darüber aus, ob Prisma tatsächlich verbunden ist.
|
||||
- `web`: hat selbst **keinen** Healthcheck. `docker compose ps` zeigt `web` daher nie
|
||||
als "healthy" an, nur als laufend – das ist normal.
|
||||
|
||||
Der Browser spricht ausschließlich mit `web` (Port 3000). Aufrufe unter
|
||||
`/api-proxy/*` werden von Next.js serverseitig auf `API_INTERNAL_URL`
|
||||
(`http://api:3001` im Docker-Netz) umgeschrieben (siehe `apps/web/next.config.ts`).
|
||||
Der Browser erreicht die API also nie direkt und muss auch keine eigene CORS-Freigabe
|
||||
für die API-Origin haben – Port 3001 muss von außen in der Regel nicht erreichbar
|
||||
sein, ist es in den mitgelieferten Compose-Dateien aber (Host-Port 3001 ist
|
||||
gemappt).
|
||||
|
||||
## 2. Erstinstallation
|
||||
|
||||
Voraussetzung: Docker und Docker Compose (Plugin, `docker compose ...`) sind auf dem
|
||||
Zielsystem installiert.
|
||||
|
||||
1. Repository auf den Server holen:
|
||||
|
||||
```bash
|
||||
git clone <gitea-url>/schalli/tessera-ctl.git
|
||||
cd tessera-ctl
|
||||
```
|
||||
|
||||
2. `.env` aus der Vorlage anlegen:
|
||||
|
||||
```bash
|
||||
cp .env.prod.example .env
|
||||
```
|
||||
|
||||
Die Vorlage `.env.prod.example` enthält die für den Produktivbetrieb relevanten
|
||||
Variablennamen; `.env.example` ist die schlankere Entwicklungsvariante. Welche
|
||||
Variablen zu setzen sind, steht in Kapitel [3. Konfiguration](#3-konfiguration).
|
||||
Mindestens erforderlich, bevor irgendetwas startet:
|
||||
|
||||
- `DB_PASSWORD` und dazu passend `DATABASE_URL`
|
||||
- `JWT_SECRET`
|
||||
- `TESSERA_ADMIN_EMAIL`, `TESSERA_ADMIN_PASSWORD`
|
||||
- `TESSERA_ENCRYPTION_KEY` (siehe Warnhinweis unten – ohne diesen Wert startet
|
||||
nichts)
|
||||
|
||||
3. Encryption-Key erzeugen und eintragen:
|
||||
|
||||
```bash
|
||||
openssl rand -hex 32
|
||||
```
|
||||
|
||||
Das Ergebnis (64 Hex-Zeichen) als `TESSERA_ENCRYPTION_KEY=` in die `.env`
|
||||
eintragen. Dieser Schlüssel verschlüsselt alle bei Tessera hinterlegten
|
||||
Zugangsdaten (LDAP-Bind-Passwort, SMTP, Kalender- sowie DKV-/Ausschreibungs-Postfächer).
|
||||
**Ohne diesen Wert startet die API nicht** – `docker-compose.prod.yml` verwendet
|
||||
die Compose-Syntax `${TESSERA_ENCRYPTION_KEY:?...}`, wodurch bereits `docker
|
||||
compose` selbst mit einer Fehlermeldung abbricht, bevor ein Container startet,
|
||||
falls die Variable fehlt. Zusätzlich prüft `CryptoService`
|
||||
(`apps/api/src/crypto/crypto.service.ts`) beim Hochfahren der API die Länge
|
||||
(muss genau 64 Hex-Zeichen sein) und wirft andernfalls einen Fehler.
|
||||
|
||||
Diesen Schlüssel getrennt von jedem Datenbank-Dump aufbewahren (siehe Kapitel 6)
|
||||
– geht er verloren, sind alle gespeicherten Zugangsdaten unwiderruflich
|
||||
unbrauchbar und müssen von Hand neu eingegeben werden.
|
||||
|
||||
4. Container starten:
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.prod.yml pull
|
||||
docker compose -f docker-compose.prod.yml up -d
|
||||
```
|
||||
|
||||
Beim ersten Start seedet die API (`AdminSeedService`,
|
||||
`apps/api/src/user/admin-seed.service.ts`) automatisch den initialen
|
||||
Super-Admin-Account aus `TESSERA_ADMIN_USER` / `_EMAIL` / `_PASSWORD` sowie
|
||||
den Mandanten `default`. Das passiert nur, wenn noch **kein** Benutzer mit
|
||||
diesem Nutzernamen existiert – bei einer bereits befüllten Datenbank wird der
|
||||
Seed-Schritt stillschweigend übersprungen, auch wenn sich die Passwort-Variable
|
||||
in der `.env` danach ändert.
|
||||
|
||||
5. Prüfen, ob alles läuft:
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.prod.yml ps
|
||||
curl -s http://localhost:3001/health
|
||||
```
|
||||
|
||||
`api` sollte als `healthy` markiert sein, die `/health`-Abfrage liefert
|
||||
`{"status":"ok",...}`. Danach ist die Oberfläche unter Port 3000 erreichbar.
|
||||
|
||||
## 3. Konfiguration
|
||||
|
||||
Alle Variablen werden über `.env` im Projektverzeichnis eingelesen (Docker Compose
|
||||
liest diese Datei automatisch). Werte unten sind **Platzhalter**, keine echten
|
||||
Zugangsdaten.
|
||||
|
||||
| Variable | Pflicht in Prod? | Default (Prod-Compose) | Zweck |
|
||||
|----------|:---:|---|---|
|
||||
| `DB_PASSWORD` | ja | – | Passwort des PostgreSQL-Benutzers `tessera`, an den `db`-Container durchgereicht. |
|
||||
| `DATABASE_URL` | ja | – | Vollständiger Prisma-Connection-String, z. B. `postgresql://tessera:<passwort>@db:5432/tessera`. Muss zum `DB_PASSWORD` passen. |
|
||||
| `JWT_SECRET` | ja | – | Signiert/prüft die JWT-Auth-Cookies. Wird **sowohl** an `api` **als auch** an `web` durchgereicht – beide müssen denselben Wert erhalten, sonst schlägt die Anmeldung fehl. In der Basis-Compose (Dev) gibt es einen unsicheren Default (`tessera-dev-jwt-secret-change-in-production`), in Prod nicht. |
|
||||
| `TESSERA_ENCRYPTION_KEY` | ja | – | AES-256-GCM-Schlüssel (64 Hex-Zeichen) für gespeicherte Drittanbieter-Zugangsdaten. Siehe Kapitel 2. `CALENDAR_ENCRYPTION_KEY` ist der alte Variablenname und wird weiterhin akzeptiert (mit Warnung im Log). |
|
||||
| `TESSERA_ADMIN_USER` | nein | `admin` | Benutzername des initialen Super-Admin, nur beim allerersten Start relevant. |
|
||||
| `TESSERA_ADMIN_EMAIL` | ja (für den Seed) | – | E-Mail des initialen Super-Admin. |
|
||||
| `TESSERA_ADMIN_PASSWORD` | ja (für den Seed) | – | Initiales Passwort des Super-Admin. |
|
||||
| `TESSERA_FORCE_CHANGE` | nein | `true` | Erzwingt Passwortwechsel beim ersten Login des geseedeten Admin-Accounts. |
|
||||
| `TESSERA_SMTP_HOST` / `_PORT` / `_SECURE` / `_USER` / `_PASSWORD` / `_FROM` | nein (aber ohne Host kein Mailversand) | Host leer, Port `587`, `_SECURE=false` | SMTP-Relay für ausgehende Mails (Passwort-Reset, Benachrichtigungen). |
|
||||
| `APP_URL` | empfohlen | `http://localhost:3001` (für `NEXT_PUBLIC_API_URL`) / `http://localhost:3000` (für `TESSERA_APP_URL`) | Öffentliche Basis-URL der Web-Oberfläche. Wird serverseitig u. a. für in E-Mails generierte Links verwendet (`TESSERA_APP_URL`). |
|
||||
| `API_INTERNAL_URL` | fest verdrahtet | `http://api:3001` | Adresse, unter der `web` die API **innerhalb** des Docker-Netzes erreicht; dorthin schreibt Next.js die `/api-proxy/*`-Rewrites um. In der Regel nicht ändern. |
|
||||
|
||||
Hinweis zu `NEXT_PUBLIC_API_URL`: Diese Variable wird beim Image-Build bereits fest
|
||||
auf `/api-proxy` gesetzt (`ENV NEXT_PUBLIC_API_URL=/api-proxy` in
|
||||
`apps/web/Dockerfile`, vor `pnpm build`). Next.js baut `NEXT_PUBLIC_*`-Variablen zur
|
||||
Build-Zeit in das an den Browser ausgelieferte JavaScript ein; der zur Laufzeit über
|
||||
Compose gesetzte Wert (`${APP_URL:-http://localhost:3001}`) hat auf das bereits
|
||||
gebaute Frontend-Bundle daher keine sichtbare Wirkung mehr. Praktisch reicht es, dass
|
||||
`APP_URL` für `TESSERA_APP_URL` (serverseitig, z. B. Mail-Links) korrekt gesetzt ist.
|
||||
|
||||
<!-- VERIFY: Ob und wie CORS_ORIGIN, NODE_ENV oder weitere in main.ts gelesene Variablen auf dem tatsächlichen Server zusätzlich gesetzt sind, lässt sich aus dem Repository allein nicht feststellen. -->
|
||||
|
||||
**Wichtig – Konfigurationsdrift auf dem Server:** Auf dem Produktivserver wurde die
|
||||
laufende Compose-Datei in der Vergangenheit direkt von Hand angepasst und ist damit
|
||||
nicht mehr zwingend identisch mit `docker-compose.prod.yml` in diesem Repository.
|
||||
Änderungen an `docker-compose*.yml` im Git-Repo wirken sich **nicht** automatisch
|
||||
auf die laufende Installation aus – der Deploy-Schritt zieht ausschließlich neue
|
||||
Images, die Compose-Datei selbst muss bei Bedarf separat auf dem Server aktualisiert
|
||||
werden. Vor grösseren Änderungen an der Compose-Struktur die auf dem Server
|
||||
tatsächlich liegende Datei prüfen, nicht blind von der Repo-Version ausgehen.
|
||||
|
||||
## 4. Neue Fassung einspielen
|
||||
|
||||
Das ist der wichtigste Ablauf im Tagesgeschäft. Zwei Befehle:
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.prod.yml pull
|
||||
docker compose -f docker-compose.prod.yml up -d --force-recreate api web
|
||||
```
|
||||
|
||||
**Verifizierte Falle:** `docker compose up -d` **ohne** `--force-recreate` ersetzt
|
||||
einen bereits laufenden Container **nicht**, wenn Compose der Meinung ist, an der
|
||||
Service-Definition habe sich nichts geändert – auch wenn `pull` gerade ein neues
|
||||
Image unter demselben Tag (`:latest`) heruntergeladen hat. Der alte Container läuft
|
||||
dann unverändert mit dem alten Code weiter, ohne Fehlermeldung. Das Deployment
|
||||
wirkt erfolgreich, ist es aber nicht. Dieser Fehler ist dem Team schon mehrfach
|
||||
passiert. Deshalb: nach jedem `pull` **immer** `--force-recreate` verwenden (nur
|
||||
`api` und `web` betrifft das – `db` soll normalerweise nicht neu erstellt werden,
|
||||
sonst würde sie kurz neu starten).
|
||||
|
||||
**Kontrolle, ob es wirklich funktioniert hat:** Startzeitpunkt des Containers mit dem
|
||||
Erstellungszeitpunkt des zugehörigen Images vergleichen. Der Container muss
|
||||
**nach** dem Image erzeugt worden sein:
|
||||
|
||||
```bash
|
||||
docker inspect -f '{{.State.StartedAt}}' tessera-api-1
|
||||
docker image inspect -f '{{.Created}}' git.vicolab.de/schalli/tessera-ctl/api:latest
|
||||
|
||||
docker inspect -f '{{.State.StartedAt}}' tessera-web-1
|
||||
docker image inspect -f '{{.Created}}' git.vicolab.de/schalli/tessera-ctl/web:latest
|
||||
```
|
||||
|
||||
(Container-Namen mit `docker compose -f docker-compose.prod.yml ps` prüfen, falls
|
||||
sie auf dem Server abweichen.) Liegt `StartedAt` **vor** `Created` des Images, läuft
|
||||
noch die alte Version – dann `--force-recreate` nachholen.
|
||||
|
||||
## 5. Datenbank-Migrationen
|
||||
|
||||
Ein separater Migrationsschritt ist **nicht** nötig. Der `api`-Container führt
|
||||
Prisma-Migrationen bei jedem Start automatisch aus, bevor der eigentliche Prozess
|
||||
hochfährt (`apps/api/Dockerfile`, `CMD`):
|
||||
|
||||
```
|
||||
prisma migrate deploy --schema apps/api/prisma/schema.prisma && node apps/api/dist/main.js
|
||||
```
|
||||
|
||||
Das heisst: Sobald ein neues Image (mit neuen Migrationen im Ordner
|
||||
`apps/api/prisma/migrations/`) per `pull` + `--force-recreate` eingespielt wird,
|
||||
laufen ausstehende Migrationen beim nächsten Start automatisch. Schlägt eine
|
||||
Migration fehl, startet `node apps/api/dist/main.js` erst gar nicht – der Container
|
||||
bleibt dann im Neustart-Loop bzw. terminiert, sichtbar in `docker compose logs api`.
|
||||
|
||||
Kontrolle, ob eine Migration tatsächlich angekommen ist:
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.prod.yml exec db \
|
||||
psql -U tessera -d tessera -c \
|
||||
"SELECT migration_name, finished_at FROM _prisma_migrations ORDER BY finished_at DESC LIMIT 5;"
|
||||
```
|
||||
|
||||
Der Name der zuletzt erwarteten Migration lässt sich mit dem Ordnernamen in
|
||||
`apps/api/prisma/migrations/` abgleichen (Namensschema
|
||||
`YYYYMMDDHHMMSS_beschreibung`).
|
||||
|
||||
## 6. Sicherung und Wiederherstellung
|
||||
|
||||
Es gibt aktuell **kein** eingebautes Backup-Skript und kein Makefile-Target dafür im
|
||||
Repository. Sicherung erfolgt manuell über die Postgres-Bordmittel.
|
||||
|
||||
### Datenbank sichern
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.prod.yml exec db \
|
||||
pg_dump -U tessera -d tessera -F c -f /tmp/tessera-$(date +%Y%m%d).dump
|
||||
docker compose -f docker-compose.prod.yml cp db:/tmp/tessera-$(date +%Y%m%d).dump ./
|
||||
```
|
||||
|
||||
### Datenbank wiederherstellen
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.prod.yml cp ./tessera-YYYYMMDD.dump db:/tmp/restore.dump
|
||||
docker compose -f docker-compose.prod.yml exec db \
|
||||
pg_restore -U tessera -d tessera --clean --if-exists /tmp/restore.dump
|
||||
```
|
||||
|
||||
Vor einer Wiederherstellung `api` stoppen, damit keine Schreibzugriffe während des
|
||||
Restores stattfinden.
|
||||
|
||||
### Was sonst noch an Zustand existiert
|
||||
|
||||
- **PostgreSQL-Daten**: liegen im benannten Docker-Volume `pgdata`
|
||||
(`docker-compose.prod.yml`, Mount `pgdata:/var/lib/postgresql/data` im
|
||||
`db`-Container). Dieses Volume ist der einzige daürhafte Datenspeicher, der über
|
||||
ein `docker volume`-Backup zusätzlich gesichert werden könnte.
|
||||
- **Verschlüsselungsschlüssel** `TESSERA_ENCRYPTION_KEY`: liegt nur in `.env` auf
|
||||
dem Host, **nicht** im Datenbank-Dump. Getrennt sichern (siehe Kapitel 2/3) – ohne
|
||||
ihn sind alle per `pg_dump` gesicherten verschlüsselten Zugangsdaten wertlos.
|
||||
- **Hochgeladene Dateien** (Avatare unter `user-files/avatars/`, generierte
|
||||
DKV-Exporte unter `user-files/`, siehe `apps/api/src/user/user.controller.ts` und
|
||||
`apps/api/src/dkv/dkv-export.service.ts`): Diese Dateien landen im Verzeichnis
|
||||
`user-files/` **innerhalb** des `api`-Containers. Weder `docker-compose.yml` noch
|
||||
`docker-compose.prod.yml` mounten dafür ein Docker-Volume oder ein Host-Verzeichnis
|
||||
– der Ordner existiert ausschließlich in der beschreibbaren Container-Schicht.
|
||||
**Das bedeutet: Bei jedem `--force-recreate` von `api` gehen Avatare und DKV-Exporte
|
||||
verloren**, sofern auf dem Server nicht zusätzlich (außerhalb des Repo-Standes)
|
||||
ein Bind-Mount ergänzt wurde. Vor einem Redeploy prüfen, ob auf dem Server ein
|
||||
solcher Mount existiert (`docker inspect tessera-api-1` → `Mounts`); falls
|
||||
nicht, gelten Avatare/Exporte als nicht persistent und sollten bei Bedarf vorher
|
||||
manuell aus dem Container kopiert werden (`docker compose cp api:/app/user-files
|
||||
./user-files-backup`).
|
||||
|
||||
## 7. Protokolle und Fehlersuche
|
||||
|
||||
Logs ansehen:
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.prod.yml logs -f api
|
||||
docker compose -f docker-compose.prod.yml logs -f web
|
||||
docker compose -f docker-compose.prod.yml logs -f db
|
||||
```
|
||||
|
||||
**Gesunder Start sieht so aus:** `db` wird `healthy`, danach startet `api` und
|
||||
protokolliert die durchgelaufenen Prisma-Migrationen sowie zuletzt
|
||||
`Tessera API running on port 3001` (aus `apps/api/src/main.ts`). Erst danach startet
|
||||
`web`, weil `depends_on: api: condition: service_healthy` das erzwingt.
|
||||
|
||||
| Symptom | Wahrscheinliche Ursache | Prüfen / Beheben |
|
||||
|---|---|---|
|
||||
| `docker compose up` bricht sofort ab, ohne dass ein Container startet, mit einer Meldung zu `TESSERA_ENCRYPTION_KEY` | Variable fehlt in `.env` – Compose selbst verweigert die Variablen-Interpolation (`${TESSERA_ENCRYPTION_KEY:?...}`) | `TESSERA_ENCRYPTION_KEY` setzen (siehe Kapitel 2), danach erneut starten. |
|
||||
| `api`-Container startet und beendet sich sofort wieder, Log zeigt `TESSERA_ENCRYPTION_KEY is not set` oder `must be a 64-character hex string` | Key ist zwar irgendwo gesetzt, aber leer oder falsch lang | Mit `openssl rand -hex 32` neu erzeugen, Länge prüfen (64 Zeichen). |
|
||||
| `api` wird nie `healthy`, Log zeigt Verbindungsfehler von Prisma/PostgreSQL | `db` ist noch nicht bereit, `DATABASE_URL` falsch, oder `DB_PASSWORD`/`DATABASE_URL` passen nicht zusammen | `docker compose ps` – ist `db` `healthy`? `DATABASE_URL` gegen `DB_PASSWORD` abgleichen. |
|
||||
| Login funktioniert nicht, obwohl `api` und `web` laufen | `JWT_SECRET` bei `web` und `api` unterschiedlich, z. B. nach halbherzigem Neustart nur eines Containers | Beide Container mit demselben `JWT_SECRET` neu erstellen (`--force-recreate api web`). |
|
||||
| Neue Version scheint nicht anzukommen, obwohl `pull` gelaufen ist | Klassische `up -d`-Falle ohne `--force-recreate` (siehe Kapitel 4) | `StartedAt` des Containers gegen `Created` des Images vergleichen, ggf. `--force-recreate` nachholen. |
|
||||
| Initialer Admin-Login funktioniert nicht nach Änderung von `TESSERA_ADMIN_PASSWORD` | Seed läuft nur, wenn der Benutzername noch **nicht** existiert; bestehende Accounts werden nicht überschrieben | Passwort über die Anwendung selbst (bzw. direkt in der Datenbank) ändern, nicht über die `.env`-Variable. |
|
||||
| Mails werden nicht versendet | `TESSERA_SMTP_HOST` leer (Prod-Default) | SMTP-Variablen vollständig setzen und Container neu erstellen. |
|
||||
| Avatare/DKV-Exporte nach einem Deploy verschwunden | `user-files/` ist nicht als Volume gemountet, siehe Kapitel 6 | Vor `--force-recreate` sichern, langfristig einen Bind-Mount für `user-files/` ergänzen. |
|
||||
|
||||
## 8. Abgrenzung zur CI/CD-Pipeline
|
||||
|
||||
Dieses Dokument beschreibt den manuellen Betrieb: Erstinstallation, Redeploy,
|
||||
Migrations-Kontrolle, Backup und Fehlersuche auf einer bereits eingerichteten
|
||||
Installation.
|
||||
|
||||
Wie die Gitea-Actions-Pipeline (Runner-Registrierung, `.gitea/workflows/ci.yml`,
|
||||
Berechtigungen des `act_runner`-Docker-Socket-Mounts) aufgesetzt wird, ist bewusst
|
||||
nicht Teil dieses Dokuments – das steht vollständig in
|
||||
[`docs/ci-cd-setup.md`](./ci-cd-setup.md). Die Grenze zwischen beiden Dokumenten:
|
||||
Sobald ein neues Image lokal vorliegt oder in einer Registry verfügbar ist, beginnt
|
||||
dieses Betriebshandbuch (Kapitel 4); alles davor – wie das Image entsteht – gehört
|
||||
in das CI/CD-Runbook.
|
||||
Reference in New Issue
Block a user