gwoe-antragspruefer/docs/adr/0004-deployment-workflow.md

136 lines
4.9 KiB
Markdown
Raw Permalink Normal View History

#62 Phase 1+3: ADRs + Doku-Struktur in webapp/docs/ Architektur-Entscheidung aus Issue #62: Diátaxis-Framework für Doku- Pflege ohne Drift. Pflege im Repo, ADRs immutable, Stale-Snapshots explizit als Archiv markiert. Phase 1 — Architecture Decision Records: - docs/README.md — Diátaxis-Index, Erklärung was wo dokumentiert wird - docs/adr/README.md — ADR-Workflow + Index - docs/adr/template.md — Vorlage für neue ADRs - docs/adr/0001-llm-citation-binding.md — Issue #60 Doppel-Fix-Story (A=ENUM-Anker, B=server-seitige Rekonstruktion, warum Option C verworfen) - docs/adr/0002-adapter-architecture.md — ParlamentAdapter-Basisklasse + Registry, Klassen vs. Strategy vs. Modul-pro-Adapter - docs/adr/0003-citation-property-tests.md — Sub-D Strategie, warum Property-Test gegen echte PDFs statt Schema-Tests oder Online-Verify - docs/adr/0004-deployment-workflow.md — Docker-Compose + Volumes Standard-Workflow + SN-XML-Sonderpfad + Container-UTC-Gotcha Phase 3 — Stale Doku archiviert: - DOKUMENTATION.md (24.März, Skript-Architektur vor Webapp-Migrate) → docs/archive/DOKUMENTATION-2026-03-24.md - STATUS-2026-03-28.md (Tagesstand-Snapshot) → docs/archive/STATUS-2026-03-28.md - README.md (28.März, listet nur NRW-Adapter, vor 16 weiteren BLs) → docs/archive/README-2026-03-28.md - docs/archive/README.md erklärt warum die Files da sind und warum niemand sie überschreiben oder ersetzen sollte Plus neue Top-Level-README.md im Project-Root (außerhalb git, da project-root kein Repo ist) als Folder-Index für den User. CLAUDE.md ergänzt um Doku-Sektion mit Verweis auf docs/adr/. Phase 2 (mkdocs Setup) folgt separat — braucht eine Docker-Image- Erweiterung, die ich nicht autark einrollen will ohne Decision. Tests: 194/194 grün (keine Code-Änderung). Refs: #62
2026-04-10 01:38:03 +02:00
# 0004 — Docker Compose Deploy mit DB-/Reports-Volume und SN-XML-Sonderpfad
| | |
|---|---|
| **Status** | accepted |
| **Datum** | 2026-04-10 |
| **Refs** | CLAUDE.md "Deployment", `docker-compose.yml`, Issue #5, project_sn_xml_export |
## Kontext
Der GWÖ-Antragsprüfer läuft als Docker-Container `gwoe-antragspruefer` auf
einem VServer hinter Traefik (Let's Encrypt SSL, Domain
`gwoe.toppyr.de`). Code wird via Git aus `repo.toppyr.de` gezogen.
Drei Subsysteme haben unterschiedliche Lebenszyklen:
1. **Code** (`app/`) — wird bei jedem Deploy neu kopiert.
2. **SQLite-Daten** (`data/gwoe-antraege.db`, `data/embeddings.db`) —
Source of Truth, MUSS persistent über Deploys hinweg.
3. **PDF-Reports** (`reports/*.pdf`) — sind generierte Artefakte, könnten
theoretisch regeneriert werden, sind aber teuer (LLM-Calls + WeasyPrint),
also auch persistent.
Issue #5 hatte einen frühen Schmerzpunkt: der Container-Build hat die
DB überschrieben, weil `data/` nicht aus dem Build-Context exkludiert war.
## Optionen
### Option A — Alles im Image, manuelle Backups
Code, DB, Reports zusammen im Image. Backups via `docker cp` vor jedem
Deploy.
**Nachteile:** Image wird gigantisch, jeder Deploy ist ein Risk-Event,
kein automatischer Persistenz-Mechanismus.
### Option B — Docker-Volumes für DB und Reports
`docker-compose.yml` mountet `./data` und `./reports` vom Host als Volumes.
Build kopiert nur `app/` und `requirements.txt`.
**Vorteile:** Host-Volumes überleben Container-Restart und Image-Rebuild.
Backups via Standard-Linux-Tools auf dem Host.
**Nachteile:** Build-Context muss `data/`, `reports/`, `.env` exkludieren
(siehe `.dockerignore`), sonst werden sie versehentlich ins Image kopiert
und überschreiben das gemountete Volume bei Container-Start.
### Option C — Externe DB (Postgres)
Postgres als separater Container, gwoe-antragspruefer verbindet per asyncpg.
**Vorteile:** standard, robust, Backups via pg_dump.
**Nachteile:** Migration aller existierenden Queries, neue Abhängigkeit,
mehr Operational Surface. SQLite reicht für die aktuelle Last (~30 Anträge,
selten parallele Writes).
## Entscheidung
**Option B**. Docker-Compose mit Host-Volumes für `data/` und `reports/`.
`.dockerignore` exkludiert beide aus dem Build-Context.
### Standard-Deploy
```bash
ssh vserver 'cd /opt/gwoe-antragspruefer && git pull && docker compose up -d --build'
```
### Manueller Tar-Upload (falls Git-Workflow blockiert ist)
```bash
cd webapp
tar czf /tmp/gwoe-webapp.tar.gz \
--exclude='venv' --exclude='__pycache__' \
--exclude='data' --exclude='reports' --exclude='.env' .
scp /tmp/gwoe-webapp.tar.gz vserver:/tmp/
ssh vserver 'cd /opt/gwoe-antragspruefer && tar xzf /tmp/gwoe-webapp.tar.gz && docker compose up -d --build'
```
Beachte: `--exclude='data'` ist **zwingend**, sonst überschreibt der Tar
die Live-DB.
### SN-XML-Sonderpfad
Sachsen hat keinen scrape-baren Endpoint und liest stattdessen wöchentlich
manuell exportierte XML-Dumps aus EDAS. Workflow:
1. User exportiert XML aus EDAS (manuell, im Browser).
2. `cp dokumente_export.xml gwoe-antragspruefer:/app/data/sn-edas/`
3. `scp` aus dem Container ins Host-Volume — kein Container-Restart nötig.
4. SNEdasXmlAdapter liest die XML beim nächsten Search-Call.
Details in `~/.claude/projects/<projekt>/memory/project_sn_xml_export.md`.
### Container-Zeitzone
Der Container läuft **UTC**, nicht CEST. DB-Timestamps in `assessments.created_at`
sind UTC (kein TZ-Suffix, aber UTC). Beim Korrelieren mit Commit-Zeiten
(lokal CEST = UTC+2) muss konvertiert werden, sonst fließen falsche
Schlussfolgerungen ein. Detailliert in
`~/.claude/projects/<projekt>/memory/reference_container_utc.md`.
## Konsequenzen
### Positiv
- **DB überlebt jeden Deploy** — verifiziert seit Issue #5 (Fix in
`.dockerignore`-Update).
- **Backups sind trivial**: `tar czf gwoe-data-$(date +%F).tar.gz data/`
auf dem Host.
- **Build ist schnell** (~30s für nicht-cached Layers), weil das Image
nur Code + Dependencies enthält.
### Negativ
- **`.dockerignore` ist ein Foot-Gun** — wenn jemand vergisst, `data/` neu
hinzuzufügen nach einem Refactor, kann es passieren dass der Build die
Live-DB überschreibt. Mitigation: ein dedicated Sub-D-style Test, der
nach dem Build prüft, dass die DB die erwarteten Tabellen hat — steht
noch aus.
- **SN-XML braucht manuelle Pflege** wöchentlich. Akzeptiert weil es kein
scrape-baren Endpoint gibt.
- **SQLite skaliert nicht über parallele Writes** — bei mehr als ~5
gleichzeitigen Analyses würde es Lock-Contention geben. Aktuell läuft
alles seriell durch den Background-Task-Mechanismus, also kein Problem.
Bei Wachstum auf >50 Analyses/Tag wäre ein Postgres-Migration ein
separater ADR.
### Folgen für andere ADRs
- ADR 0002 (Adapter-Architektur) ist davon unabhängig — Adapter sind reine
Code-Klassen ohne State.
- Ein zukünftiger Postgres-Migration-ADR würde diesen ADR partial superseden
(DB-Persistenz, nicht Reports).