compose.yaml overrode the image's own entrypoint to start "tsx watch" and "next dev" with CHOKIDAR_USEPOLLING and WATCHPACK_POLLING enabled. The built dist/ and .next from the image were never used, and the polling watchers kept both containers busy around the clock whether or not anyone used the application. Split the two use cases: compose.yaml now runs node dist/server/index.js and next start with no source mounts and no watchers, and the watching setup moves to compose.dev.yaml. next build writes the rewrite destinations from next.config.mjs into .next/routes-manifest.json, so next start cannot pick up API_INTERNAL_URL at runtime the way next dev does. Pass it as a build argument. Give the web container its own probe. The healthcheck hit "/", which redirects to "/projects", and fetch follows redirects, so every probe rendered two pages every five seconds. "/health" could not serve as the probe because next.config.mjs rewrites it to the API. Add "/web-health" as a route handler, exclude it from the proxy matcher, and relax both intervals to 30s. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
113 lines
5.6 KiB
Markdown
113 lines
5.6 KiB
Markdown
# Deployment und Betrieb
|
||
|
||
## Aktueller Status
|
||
|
||
Es gibt zwei Compose-Stacks.
|
||
|
||
`compose.yaml` startet den gebauten Stand: `node dist/server/index.js` und
|
||
`next start`, ohne Quellcode-Mounts und ohne Datei-Watcher. Das ist der Stack
|
||
für einen Server.
|
||
|
||
`compose.dev.yaml` startet `tsx watch` und `next dev` und bindet Quellcode vom
|
||
Host ein. Die Watcher laufen im Polling-Modus, weil Bind-Mounts unter Windows
|
||
und macOS keine inotify-Events durchreichen; das kostet dauerhaft CPU, auch
|
||
ohne Benutzeraktivität. Dieser Stack gehört deshalb nur auf einen
|
||
Entwicklungsrechner.
|
||
|
||
Beides enthält weder TLS, Authentifizierung, Reverse Proxy, Prozesshärtung noch
|
||
ein zentral betriebenes Datenbanksystem. Der Stack darf deshalb nicht öffentlich
|
||
erreichbar gemacht werden.
|
||
|
||
## Topologie
|
||
|
||
| Komponente | Port | Healthcheck | Persistenz |
|
||
| --- | ---: | --- | --- |
|
||
| Next.js Web | 3001 | `GET /web-health` | 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` – nur in
|
||
`compose.dev.yaml`, für Dateibeobachtung über Bind-Mounts hinweg
|
||
- `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 die Migrationen und die Schemaprüfung
|
||
(`scripts/run-migrations.js` und `scripts/db-verify-circuit-schema.js`, im
|
||
Entwicklungsstack über `npm run db:migrate` und
|
||
`npm run db:verify:circuit-schema`).
|
||
|
||
`API_INTERNAL_URL` wirkt für `next start` zur **Build-Zeit**: `next build`
|
||
schreibt die Rewrite-Ziele aus `next.config.mjs` fest in
|
||
`.next/routes-manifest.json`. `compose.yaml` reicht den Wert deshalb als
|
||
Build-Argument an das Image durch, nicht nur als Laufzeit-Variable.
|
||
|
||
## 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.
|