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>
98 lines
4.8 KiB
Markdown
98 lines
4.8 KiB
Markdown
# 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.
|