# 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//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//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).