leistungsbilanz-ts/docs/deployment.md
Julian Appel fa96be2d42 Fix crash handling and log noise in logging feature
Review of the logging work from the previous commit turned up a real
correctness issue: the uncaughtException/unhandledRejection handlers
logged the error but let the process keep running, which silently
disabled Node's default crash-on-fatal-error behavior and could leave
a zombie process serving broken requests instead of restarting. Both
processes now log and then exit(1); restart: unless-stopped is added
to both services so Docker actually brings them back up.

Also: harden the logger against JSON.stringify throwing on
non-serializable meta, log aborted (closed-before-finished) requests
in the API access log, and stop the Next.js proxy from logging the
Docker healthcheck's request to "/" every few seconds so real
navigation events aren't drowned out.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 19:58:31 +02:00

98 lines
4.8 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 und Betrieb
## Aktueller Status
Es gibt derzeit kein unterstütztes Produktionsdeployment.
`compose.yaml` ist ausschließlich für lokale Entwicklung vorgesehen. Es startet
`tsx watch` und `next dev`, bindet Quellcode vom Host ein und enthält weder TLS,
Authentifizierung, Reverse Proxy, Prozesshärtung noch ein zentral betriebenes
Datenbanksystem. Der Stack darf deshalb nicht als produktionsreif bezeichnet oder
öffentlich erreichbar gemacht werden.
## Entwicklungs-Topologie
| Komponente | Port | Healthcheck | Persistenz |
| --- | ---: | --- | --- |
| Next.js Web | 3001 | `GET /` | keine |
| Express API | 3000 | `GET /health` | `./data:/app/data` |
| SQLite | Datei | Integritäts-/FK-Prüfung via Backup und Skript | `data/leistungsbilanz.db` |
Verwendete Umgebungsvariablen:
- `PORT` API-Port, Standard `3000`
- `API_INTERNAL_URL` internes API-Ziel des Next.js-Rewrites, im Compose-Netz
`http://api:3000`
- `NEXT_TELEMETRY_DISABLED=1`
- `CHOKIDAR_USEPOLLING=true` und `WATCHPACK_POLLING=true` für lokale
Dateibeobachtung in Docker
- `LOG_LEVEL` steuert für beide Dienste die Ausgabestufe des strukturierten
JSON-Loggers (`error`, `warn`, `info`, `verbose`, `debug`), Standard `info`.
Setzbar über eine `.env`-Datei neben `compose.yaml` oder
`LOG_LEVEL=verbose docker compose up`.
Beim API-Start laufen zuerst `npm run db:migrate` und
`npm run db:verify:circuit-schema`.
## Logging
Beide Dienste schreiben strukturierte, einzeilige JSON-Log-Zeilen nach
stdout/stderr (`docker compose logs --follow`). Jede Zeile enthält
`timestamp`, `level`, `scope` und `message`. `compose.yaml` konfiguriert für
beide Dienste den `json-file`-Treiber mit Rotation (`max-size: 20m`,
`max-file: 10`, also bis zu 200 MB je Dienst); ohne diese Einstellung würde
Docker mit der Standardkonfiguration unbegrenzt in eine einzelne Datei unter
`/var/lib/docker/containers/<container-id>/` schreiben. Die Logs überleben
einen Container-Neustart (`docker compose restart`), aber nicht das Entfernen
des Containers (`docker compose down` gefolgt von `up` erzeugt neue
Container und damit neue, leere Logdateien); für ein echtes Langzeitarchiv
über Rebuilds hinweg müssten die Zeilen zusätzlich in eine Datei im
gemounteten `./data`-Verzeichnis oder an ein externes Log-System geschrieben
werden. Die Express-API protokolliert
jede abgeschlossene Anfrage (Methode, Pfad, Status, Dauer; `/health` wird
nicht mitgeloggt) sowie unbehandelte Exceptions/Promise-Rejections. Das
Next.js-Frontend protokolliert Seitenanfragen (Navigation) über
`src/proxy.ts` und unbehandelte Fehler über `src/instrumentation.ts`.
Beide Prozesse schreiben zusätzlich alle fünf Minuten einen `verbose`-Heartbeat
mit Laufzeit und Speicherverbrauch nützlich, um Speicherlecks oder Hänger vor
einem 502 über einen längeren Zeitraum nachzuvollziehen. Für die Detailsuche
`LOG_LEVEL=debug` setzen; das protokolliert zusätzlich den Start jeder
API-Anfrage und macht damit hängende (nie abgeschlossene) Requests sichtbar.
Ein `close`-Ereignis ohne vorheriges `finish` wird als `request aborted before
response finished` (`warn`) geloggt und zeigt damit vom Client oder einem
vorgeschalteten Proxy abgebrochene Verbindungen.
Eine unbehandelte Exception oder Promise-Rejection wird geloggt und beendet
den jeweiligen Prozess anschließend bewusst (`process.exit(1)`), statt in
einem unbekannten Zustand weiterzulaufen. Beide Dienste laufen deshalb mit
`restart: unless-stopped`, damit Docker sie danach automatisch neu startet;
ohne diese Policy würde ein Crash den Dienst dauerhaft unerreichbar lassen.
## Voraussetzungen für ein späteres Produktionssetup
Vor einer produktiven Installation werden mindestens benötigt:
- reproduzierbare Produktionsimages und ein Next.js-Produktionsstartskript
- TLS-Termination und Reverse Proxy
- Authentifizierung, Autorisierung und Benutzer-/Rollenmodell
- definierte Secrets- und Konfigurationsverwaltung
- persistenter, gesicherter Datenbankbetrieb
- kontrollierter einmaliger Migrationsschritt vor dem API-Rollout
- Monitoring, strukturierte Logs und Alarmierung
- getestete Backup-, Restore- und Rollback-Prozeduren
- Entscheidung, ob SQLite für einen einzelnen Prozess genügt oder PostgreSQL für
Mehrbenutzerbetrieb erforderlich ist
Bis diese Punkte umgesetzt und getestet sind, besteht die „Installation“ aus dem
lokalen Entwicklungsstart in der README.
## Backup und Wiederherstellung
`npm run db:backup` erzeugt unter `data/backups/` ein konsistentes SQLite-Backup,
öffnet es unabhängig und prüft Integrität sowie Fremdschlüssel. Diese
Dateisicherungen sind von zukünftigen logischen Projektversionen getrennt.
Eine produktive Restore-Anweisung wird erst zusammen mit einem unterstützten
Produktionsbetrieb veröffentlicht. Lokale Wiederherstellung darf nur bei
gestoppter API, gegen eine separate Datei und nach erneuter Integritätsprüfung
erfolgen.