# Migration & Deploy – Designprinzipien (Extraktion) **Status:** Analyse / Arbeitspapier **Stand:** 2026-07-04 **Geltungsbereich:** DB-Migrationen, Container-Startup, CI/CD-Deploy — keine Anwendungsdomäne **Serie:** Designprinzipien für Produktfamilie · Dokument 9 von n (Abschluss) **Vorgänger:** [NAVIGATION_IA_DESIGN_PRINCIPLES.md](./NAVIGATION_IA_DESIGN_PRINCIPLES.md) **Kernkomponenten:** | Bereich | Pfade | |---------|-------| | DB-Init & Migrationen | `backend/db_init.py`, `backend/startup.sh` | | Migrationen | `backend/migrations/XXX_*.sql` | | Basis-Schema (Greenfield) | `backend/schema.sql` | | Tracking | Tabelle `schema_migrations` | | Compose Prod/Dev | `docker-compose.yml`, `docker-compose.dev-env.yml` | | CI/CD | `.gitea/workflows/deploy-dev.yml`, `deploy-prod.yml`, `test.yml` | | Versionierung | `backend/version.py` (`APP_VERSION`, `DB_SCHEMA_VERSION`, `MODULE_VERSIONS`) | | Doku (operativ) | `MIGRATIONS.md` | | Architektur-Regeln | `.claude/rules/ARCHITECTURE.md` §2, §7 | --- ## Modul **Migration & Deploy** Automatische **PostgreSQL-Schema-Evolution** beim Container-Start plus **Git-getriebene Deploy-Pipeline** (develop → Dev, main → Prod) auf selbst-gehosteter Infrastruktur (Docker auf Raspberry Pi). --- ## Fachliche Verantwortung Das Modul übernimmt: 1. **Schema-Migrationen** — Nummerierte SQL-Dateien, idempotent wo möglich, getrackt in `schema_migrations`. 2. **Startup-Orchestrierung** — Postgres ready → Schema/Migrationen → optional SQLite-Import → Uvicorn. 3. **Umgebungstrennung** — Dev (`3099`/`8099`) vs. Prod (`3002`/`8002`), getrennte DBs/Volumes. 4. **Deploy-Automatisierung** — Push auf Branch → Runner → `git reset --hard` → `docker compose build --no-cache` → Health-Check. 5. **Post-Deploy-Tests** — Pytest/Lint/Frontend-Build gegen **deployed** Container auf dem Runner. 6. **Versions-Metadaten** — App-/Modul-Version und dokumentierte `DB_SCHEMA_VERSION`. Es übernimmt **nicht**: - Fachliche Datenberechnungen (→ Data Layer) - Automatisches Downgrade/Rollback von Schema - Blue-Green oder Multi-Region-Deploy ### Deploy-Pipeline (Happy Path) ``` Entwickler: commit → push develop → Gitea Runner: deploy-dev.yml → cd /home/lars/docker/bodytrack-dev → git fetch + reset --hard origin/develop → docker compose -f docker-compose.dev-env.yml build --no-cache && up -d → backend startup.sh → db_init.py (Migrationen) → curl localhost:8099/api/auth/status → test.yml (push + nach Deploy): pytest im Container, py_compile, npm run build Prod: PR develop → main → deploy-prod.yml (Port 8002, bodytrack/) ``` --- ## Administrierte vs. code-definierte Konfiguration | Konfiguration | Speicherort | Wer pflegt? | |---------------|-------------|-------------| | Migration-SQL | `backend/migrations/` | Entwickler | | Welche Migrationen angewendet | `schema_migrations` (DB) | Automatisch | | Greenfield-Basis | `schema.sql` | Entwickler (selten) | | Compose/Ports/Env | `docker-compose*.yml`, `.env` auf Server | Betrieb | | Deploy-Workflow | `.gitea/workflows/*.yml` | Entwickler | | App-Version / Changelog | `backend/version.py` | Entwickler (pro Release) | | Prod-Geheimnisse | Server-`.env`, nicht im Repo | Betrieb | --- ## Designprinzipien ### 1. Migrationen beim Container-Start ( nicht manuell in Prod) | | | |---|---| | **Prinzip** | `startup.sh` ruft `db_init.py` auf **bevor** Uvicorn startet; pending Migrationen werden automatisch angewendet. | | **Begründung** | Kein vergessenes Schema-Update; Deploy und DB-Stand bleiben gekoppelt. | | **Quelle** | `startup.sh`, `db_init.py` | | **Tragfähigkeit** | **hoch** | | **Einschränkung** | Fehlgeschlagene Migration blockiert API-Start (`sys.exit(1)`). | ### 2. Nummeriertes Datei-Pattern als Gate | | | |---|---| | **Prinzip** | Nur `\d{3}_*.sql` wird ausgeführt (z. B. `054_activity_session_metrics_eav.sql`); alles andere wird ignoriert. | | **Begründung** | Sortierbare Reihenfolge; Ad-hoc-Skripte (`check_features.sql`, `v9c_*.sql`) verunreinigen nicht den Lauf. | | **Quelle** | `run_migrations()` Regex | | **Tragfähigkeit** | **hoch** | | **Einschränkung** | Legacy-Dateien ohne Nummer liegen noch im Ordner (historischer Ballast). | ### 3. Tracking-Tabelle als Single Source of „applied“ | | | |---|---| | **Prinzip** | `schema_migrations(filename)` — jede erfolgreiche Datei genau einmal eingetragen; Pending = Dateien minus Applied. | | **Begründung** | Idempotenter Startup; wiederholter Container-Start wendet nichts doppelt an. | | **Quelle** | `ensure_migration_table`, `apply_migration` | | **Tragfähigkeit** | **hoch** | | **Einschränkung** | Kein checksum — geänderte Datei nach Apply wird nicht erneut ausgeführt. | ### 4. Alphabetische Reihenfolge = Migrations-Reihenfolge | | | |---|---| | **Prinzip** | `sorted(glob)` — dreistellige Präfixe (`001`, `054`, `061`) definieren die Apply-Order. | | **Begründung** | Einfach, git-freundlich, keine separate Migrations-Registry. | | **Quelle** | `run_migrations()` | | **Tragfähigkeit** | **hoch** | | **Einschränkung** | Nummern-Kollisionen oder nachträgliches Einfügen erfordern Disziplin (immer nächste freie Nummer). | ### 5. Fail-Fast bei Migrationsfehler | | | |---|---| | **Prinzip** | Schlägt eine Migration fehl → kein Commit in Tracking (bei Exception vor INSERT), Prozess exit 1, Container unhealthy. | | **Begründung** | API läuft nicht mit halb angewendetem Schema. | | **Quelle** | `apply_migration`, `main` in `db_init.py` | | **Tragfähigkeit** | **hoch** | | **Einschränkung** | Manueller Recovery-Prozess nötig (siehe MIGRATIONS.md Rollback). | ### 6. Greenfield: schema.sql, Bestand: nur Migrationen | | | |---|---| | **Prinzip** | Existiert `profiles` nicht → einmalig `schema.sql` laden; danach nur noch nummerierte Migrationen. | | **Begründung** | Frische Instanz schnell bootstrapped; langlebige DBs evolvieren incremental. | | **Quelle** | `check_table_exists`, `load_schema` | | **Tragfähigkeit** | **hoch** | | **Einschränkung** | `schema.sql` kann hinter Migrationen zurückfallen wenn nicht gepflegt. | ### 7. Idempotente DDL bevorzugen | | | |---|---| | **Prinzip** | `ADD COLUMN IF NOT EXISTS`, `CREATE INDEX IF NOT EXISTS`, defensive UPDATEs — Migration soll mehrfach ausführbar sein ohne Schaden. | | **Begründung** | Recovery nach partiellem Apply; manuelles Re-Run sicherer. | | **Quelle** | `MIGRATIONS.md` Best Practices | | **Tragfähigkeit** | **hoch** | | **Einschränkung** | Nicht alle Änderungen sind idempotent (DROP, irreversible Datenmigration). | ### 8. Kein psql-Meta in Migrationsdateien | | | |---|---| | **Prinzip** | Nur SQL — kein `\echo`, `\i`, `\connect`; Ausführung via psycopg2, nicht interaktiv. | | **Begründung** | Parser/Runner versteht nur SQL-Statements. | | **Quelle** | `MIGRATIONS.md`, `apply_migration` | | **Tragfähigkeit** | **hoch** | | **Einschränkung** | — | ### 9. Schema-Änderung = nummerierte Migration (nie ad-hoc in Prod) | | | |---|---| | **Prinzip** | Neue Tabellen/Spalten **nur** via `backend/migrations/XXX_*.sql`; nicht direkt in laufender Prod-DB editieren. | | **Begründung** | Reproduzierbarkeit Dev→Prod; Review im Git-Diff. | | **Quelle** | ARCHITECTURE.md, CLAUDE.md | | **Tragfähigkeit** | **hoch** | | **Einschränkung** | Agent-Regel — technisch nicht erzwungen. | ### 10. DB_SCHEMA_VERSION als dokumentierter Marker | | | |---|---| | **Prinzip** | `backend/version.py` → `DB_SCHEMA_VERSION` bei Schema-Änderung manuell bumpen (Format z. B. `YYYYMMDD` + Suffix). | | **Begründung** | API `/api/version` und Changelog zeigen Schema-Stand unabhängig von App-Minor. | | **Quelle** | ARCHITECTURE.md §2.6 | | **Tragfähigkeit** | **mittel–hoch** | | **Einschränkung** | Nicht automatisch aus `schema_migrations` abgeleitet — Drift möglich. | ### 11. Branch → Umgebung (develop / main) | | | |---|---| | **Prinzip** | `develop` → Dev-Deploy automatisch; `main` → Prod-Deploy automatisch; Prod nur nach expliziter Freigabe/Merge. | | **Begründung** | Klare Promotion; Dev als Integrationsumgebung. | | **Quelle** | Workflows, CLAUDE.md Deployment | | **Tragfähigkeit** | **hoch** | | **Einschränkung** | Kein Staging-Branch zwischen Dev und Prod. | ### 12. Deploy-Arbeitskopie = exakt Remote-Branch | | | |---|---| | **Prinzip** | Runner: `git fetch` + `git reset --hard origin/` — keine `pull`-Merge-Konflikte, kein schmutziger `package-lock` auf dem Pi. | | **Begründung** | Reproduzierbarer Deploy-Baum; Fix aus GUI-IA-Abnahme 2026-04-05. | | **Quelle** | `deploy-prod.yml`, `deploy-dev.yml` | | **Tragfähigkeit** | **hoch** | | **Einschränkung** | Lokale Hotfixes auf dem Server werden beim Deploy überschrieben. | ### 13. Immutabler Build pro Deploy (`--no-cache`) | | | |---|---| | **Prinzip** | `docker compose build --no-cache` bei jedem Deploy — frisches Image aus Dockerfile + Repo-Stand. | | **Begründung** | Keine veralteten Layer; Migrationen und Code garantiert im Image. | | **Quelle** | Deploy-Workflows | | **Tragfähigkeit** | **hoch** | | **Einschränkung** | Langsamere Deploys; kein Registry-basiertes Image-Promotion. | ### 14. Health-Check nach Deploy | | | |---|---| | **Prinzip** | Nach `up -d`: kurz warten, dann `curl -sf …/api/auth/status` (8099 Dev / 8002 Prod). | | **Begründung** | Minimale Smoke-Verification dass API antwortet (inkl. DB-Init durchlaufen). | | **Quelle** | Deploy-Workflows | | **Tragfähigkeit** | **mittel–hoch** | | **Einschränkung** | Prüft nicht fachliche Endpoints oder Migration-Inhalt. | ### 15. Persistente Volumes für Daten und Fotos | | | |---|---| | **Prinzip** | Postgres-Daten, `/app/data`, `/app/photos` in benannten/external Volumes — überleben Container-Rebuild. | | **Begründung** | Deploy = neues Image, nicht Datenverlust. | | **Quelle** | `docker-compose*.yml` | | **Tragfähigkeit** | **hoch** | | **Einschränkung** | Volume-Backup/Restore ist Betriebsaufgabe außerhalb Repo. | ### 16. Postgres Healthcheck vor Backend-Start | | | |---|---| | **Prinzip** | `depends_on: condition: service_healthy` — Backend startet erst wenn DB `pg_isready`. | | **Begründung** | `wait_for_postgres` in db_init ist zweite Absicherung; reduziert Race beim ersten Start. | | **Quelle** | Compose-Files | | **Tragfähigkeit** | **hoch** | | **Einschränkung** | — | ### 17. Tests gegen deployed Stack (Self-Hosted Runner) | | | |---|---| | **Prinzip** | `test.yml` führt pytest **im laufenden Backend-Container** auf dem Pi aus, nicht in isolierter GitHub-Cloud. | | **Begründung** | Tests laufen gegen echte Dev/Prod-Compose-Umgebung des Projekts. | | **Quelle** | `.gitea/workflows/test.yml` | | **Tragfähigkeit** | **mittel** | | **Einschränkung** | Prod-Deploy triggert Tests auf Prod-Pfad — Risiko wenn Tests schreibend; `-m 'not slow'` begrenzt Laufzeit. | ### 18. Feste Ports pro Umgebung | | | |---|---| | **Prinzip** | Dev `3099/8099`, Prod `3002/8002` — nicht ändern (Reverse Proxy/Fritz!Box hängen daran). | | **Begründung** | Externe URLs (`dev.mitai.jinkendo.de`, `mitai.jinkendo.de`) stabil. | | **Quelle** | CLAUDE.md, Compose | | **Tragfähigkeit** | **hoch** (betriebsspezifisch) | | **Einschränkung** | Andere Projekte brauchen eigene Port-Matrix. | ### 19. Prod-Schutz: Deploy nur über Git | | | |---|---| | **Prinzip** | Keine direkten Prod-Container-/DB-Schreibzugriffe für Automation; Prod-Änderung = Merge `main` → Workflow. | | **Begründung** | Audit-Trail, Review, keine Drift. | | **Quelle** | ARCHITECTURE.md §7.1, `/deploy` Command | | **Tragfähigkeit** | **hoch** | | **Einschränkung** | Menschlicher SSH-Zugriff bleibt möglich — Prozess, nicht Technik. | ### 20. Versions-Bump als Release-Disziplin | | | |---|---| | **Prinzip** | Jede lieferbare Änderung: `APP_VERSION`, betroffene `MODULE_VERSIONS`, `CHANGELOG` in `version.py`; bei Schema auch `DB_SCHEMA_VERSION`. | | **Begründung** | `/api/version`, Support, Korrelation Deploy ↔ Code. | | **Quelle** | ARCHITECTURE.md §2.5, `deploy.md` | | **Tragfähigkeit** | **hoch** | | **Einschränkung** | Frontend-`version.js` in Spec erwähnt, im Repo teils nicht vorhanden — Dual-Bump unvollständig. | --- ## Nicht übernehmen 1. **Unnummerierte Migrationsdateien** — `v9c_*.sql`, `check_*.sql` werden nicht auto-applied; nicht als Vorbild. 2. **Migration-Datei nach Apply ändern** — Tracking verhindert Re-Run; neue Nummer statt Edit. 3. **Automatischer Downgrade** — nicht implementiert; Rollback manuell + Tracking-Eintrag löschen. 4. **Direktes Schema in Prod** — immer Git-Migration + Deploy. 5. **Breaking DROP ohne Koordination** — App-Code und Migration in einem Release. 6. **psql-Metacommands in `.sql`** — bricht Python-Runner. 7. **`git pull` auf Deploy-Server** — Merge-Schmutz; `reset --hard` ist das Muster. 8. **Prod-Deploy ohne Dev-Validierung** — develop-First ist implizite Policy. 9. **Schema-Drift ohne `DB_SCHEMA_VERSION`-Bump** — dokumentarische Lücke. 10. **Cached Docker-Build als Default** — Mitai wählt Reproduzierbarkeit über Geschwindigkeit. 11. **Migrationen außerhalb Container-Start vergessen** — manuelles psql in Prod als Normalfall. 12. **Hardcoded Seed-Daten in Migration** — produktive User/Secrets nicht in SQL. 13. **Port-Änderung „nebenbei“** — Infrastruktur-Kopplung. 14. **Tests nur lokal, nie auf Runner-Stack** — Mitai testet bewusst post-deploy im Pi-Container (Trade-off verstehen). 15. **Transaktionssteuerung in SQL-Datei** — Runner committet pro Datei; komplexe multi-step Rollbacks nicht eingebaut. --- ## Modul-Inventar (Ist-Stand) ``` backend/ ├── db_init.py # wait, schema, run_migrations, sqlite import ├── startup.sh # db_init → uvicorn ├── schema.sql # Greenfield ├── migrations/ # 001–061+ nummeriert (+ Legacy ohne Nummer) └── version.py # APP_VERSION, DB_SCHEMA_VERSION, MODULE_VERSIONS docker-compose.yml # Prod: 3002/8002 docker-compose.dev-env.yml # Dev: 3099/8099 .gitea/workflows/ ├── deploy-dev.yml # push develop ├── deploy-prod.yml # push main └── test.yml # pytest, lint, npm build on Pi Server (Pi): /home/lars/docker/bodytrack-dev/ # develop /home/lars/docker/bodytrack/ # main ``` **Migrationen (Stand):** 60+ nummerierte Dateien (`001` … `061`); höchste Nummer im Repo prüfen vor neuer Migration. --- ## Verwandte Dokumentation - Operativ: [MIGRATIONS.md](../../technical/MIGRATIONS.md) - Architektur: `.claude/rules/ARCHITECTURE.md` §2 (Versionierung), §7 (Prod-Schutz) - Deploy-Command: `.claude/commands/deploy.md`, `merge-to-prod.md` - Import/Migration-Grenze: [UNIVERSAL_IMPORT_DESIGN_PRINCIPLES.md](./UNIVERSAL_IMPORT_DESIGN_PRINCIPLES.md) - Auth auf Prod: [AUTH_SESSION_DESIGN_PRINCIPLES.md](./AUTH_SESSION_DESIGN_PRINCIPLES.md) --- ## Serie – Übersicht (abgeschlossen) | # | Modul | Status | |---|-------|--------| | 1 | Prompt Engine | ✅ `PROMPT_ENGINE_DESIGN_PRINCIPLES.md` | | 2 | Data Layer | ✅ `DATA_LAYER_DESIGN_PRINCIPLES.md` | | 3 | Feature & Entitlement | ✅ `FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md` | | 4 | Registry / Plugin (Meta) | ✅ `REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md` | | 5 | Auth & Session | ✅ `AUTH_SESSION_DESIGN_PRINCIPLES.md` | | 6 | Universal Import | ✅ `UNIVERSAL_IMPORT_DESIGN_PRINCIPLES.md` | | 7 | Dashboard Widgets | ✅ `DASHBOARD_WIDGETS_DESIGN_PRINCIPLES.md` | | 8 | Navigation / IA | ✅ `NAVIGATION_IA_DESIGN_PRINCIPLES.md` | | 9 | Migration & Deploy | ✅ dieses Dokument |