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
4.9 KiB
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:
- Code (
app/) — wird bei jedem Deploy neu kopiert. - SQLite-Daten (
data/gwoe-antraege.db,data/embeddings.db) — Source of Truth, MUSS persistent über Deploys hinweg. - 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
ssh vserver 'cd /opt/gwoe-antragspruefer && git pull && docker compose up -d --build'
Manueller Tar-Upload (falls Git-Workflow blockiert ist)
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:
- User exportiert XML aus EDAS (manuell, im Browser).
cp dokumente_export.xml gwoe-antragspruefer:/app/data/sn-edas/scpaus dem Container ins Host-Volume — kein Container-Restart nötig.- 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
.dockerignoreist 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).