-
Notifications
You must be signed in to change notification settings - Fork 0
Deployment
Dieses Runbook beschreibt den vollständigen hocX-Deployment-Prozess: lokale Entwicklung, Erstellung eines unveränderlichen Release-Kandidaten, Verifikation auf Test, Promotion desselben Images nach Produktion, Rollback, Host-Provisionierung sowie den laufenden Backup- und Cleanup-Betrieb.
Änderung auf main
↓
PR-CI erfolgreich
↓
Candidate-Images bauen und signieren
↓
Candidate auf Test deployen
↓
automatische und manuelle Verifikation
↓
getestete Images ohne Rebuild zum Release promoten
↓
finalen Release-Tag auf Produktion deployen
↓
Verifikation, Monitoring und bei Bedarf Rollback
- Änderungen sind auf
mainund die CI ist grün. - Der Candidate-Tag wurde aus der Workflow-Summary notiert.
- Genau dieser Candidate wurde auf Test deployt.
-
verify_release.sh testund die manuelle Prüfung waren erfolgreich. - Der getestete Candidate wurde ohne Rebuild zum finalen Semver-Tag promotet.
- Produktion verwendet den finalen Release-Tag, keinen Candidate-Tag.
-
verify_release.sh prodund die manuelle Produktionsprüfung waren erfolgreich.
| Dev | Test | Prod | |
|---|---|---|---|
| Wo | dieser Server | eigener Test-Server | eigener Prod-Server |
| Domain | hocx.example.com | test.hocx.ch | hocx.ch |
| Woher kommt der Code | lokal, build: aus Source |
Docker-Image von GHCR | Docker-Image von GHCR |
| Verzeichnis | /docker/hocX |
Repo-Checkout auf dem Test-Server | Repo-Checkout auf dem Prod-Server |
Lokale Entwicklung (Laptop/Workspace, nicht hocx.example.com) läuft jetzt bewusst separat
über den Overlay docker-compose.dev.yml:
cp .env.example .env
docker compose -f docker-compose.yml -f docker-compose.dev.yml up --buildOptional dazu: --profile scan (ClamAV), --profile docs (Docs auf localhost:3002),
--profile edge (lokaler Traefik).
Bequemer Wrapper dafuer:
./scripts/dev.sh
./scripts/dev.sh stop
./scripts/dev.sh down
./scripts/dev.sh up --profile docs --profile scanWichtig vor dem allerersten Start einer neuen Domain (Test wie Prod): DNS-Eintrag zuerst setzen, dann erst den Stack starten. Traefik versucht bei jedem Container-Start sofort ein Let's-Encrypt-Zertifikat zu beziehen; schlägt die HTTP-01-Challenge fehl (weil DNS noch nicht auf den Server zeigt), zählt das als "failed authorization" gegen Let's Encrypt's Rate-Limit (5 Fehlversuche/Domain/Stunde). Mehrfaches Neustarten des Stacks vor gesetztem DNS kann dieses Limit auslösen - dann muss man bis zu 1h warten, bevor ein neuer Zertifikatsversuch klappt. Betrifft nur die neue Domain, nicht die bereits laufenden Zertifikate der anderen Umgebungen.
Alternative für Hosts, die gar nicht öffentlich erreichbar sein sollen (z.B. ein privater
Test-Host, analog zum Admin-Zugang): TRAEFIK_WEB_BIND/TRAEFIK_WEBSECURE_BIND=127.0.0.1
und TRAEFIK_CERTRESOLVER=letsencryptdns in .env setzen, siehe
Konfiguration.
Dann entfällt die HTTP-01-Rate-Limit-Problematik komplett, da die Zertifikate über
DNS-01 bezogen werden - der Host braucht dann aber einen eigenen OpenZiti-Tunnel (oder
vergleichbar) für jeglichen Zugriff, auch für Tester.
- Alle Änderungen sind auf
maingemerged und die PR-CI ist gruen. - In GitHub → Actions den Workflow
Build test candidate imagesstarten. - In der Actions-UI den gewuenschten Ref auswaehlen (normalerweise
main). Der Workflow baut automatisch den neuesten Commit dieses Refs. - Der Candidate-Tag wird dabei automatisch erzeugt, Format:
test-<UTC-Datum>-<shortsha>-r<run_number>, z.B.test-20260825-abc1234-r42. - Der Workflow baut+pusht alle Release-Images nach
ghcr.io/<namespace>/hocx-{backend,frontend,abgabebox-backend,abgabebox-frontend,docs}:<image_tag>. - Diesen Candidate-Tag aus der Workflow-Summary notieren; genau derselbe Tag wird auf dem Test-Host deployed.
Einmalig nach der Umstellung auf den sicheren Env-Loader sicherstellen, dass nur der
Eigentuemer die vorhandene Secret-Datei lesen kann: chmod 600 .env. Symlinks und
gruppen- oder weltlesbare .env-Dateien werden vom Deploy bewusst abgelehnt.
# Auf dem Test-Host im Repo-Root .env pflegen. Fehlt sie beim ersten Deploy,
# fragt deploy.sh die externen Werte ab und erzeugt alle Secrets automatisch.
vim .env
# HOCX_VERSION auf den eben gebauten Candidate-Tag setzen, z.B.:
# HOCX_VERSION=test-20260825-abc1234-r42
./scripts/deploy.sh test
./scripts/verify_release.sh testdeploy.sh test macht automatisch: Preflight + exklusiver Deploy-Lock → DB-Backup
(backups/) → Images pullen → Cosign-Signaturen pruefen → Digest-Manifest schreiben →
Alembic explizit ausfuehren → Container neu starten → Smoke-Checks
(Backend, Frontend, Abgabebox, Docs, ClamAV).
verify_release.sh test prueft danach zusaetzlich:
- Backend / Abgabebox-Backend lokal erreichbar
- Frontend / Website / Docs lokal erreichbar
- Alembic
current == heads -
https://test...-Domains antworten ueber Traefik
Verifizieren (mit einem Wegwerf-Testaccount, danach wieder löschen):
- https://test.hocx.ch/login erreichbar, Branding lädt korrekt
- Login funktioniert, mindestens eine Tabellen-Seite lädt Daten
- Abgabebox: https://abgabe-test.hocx.ch lädt (sofern ein Test-Mandant mit Abgabebox-Konfiguration existiert)
-
docker compose -p hocx-test logs backend --tail=50zeigt keine Fehler, insbesondere keine Alembic-Fehler beim Start
Wenn Test erfolgreich war:
- In GitHub → Actions den Workflow
Promote tested release imagesstarten. - Eingaben:
-
source_tag: genau der getestete Candidate-Tag, z.B.test-20260825-abc1234-r42 -
release_tag: finaler Semver-Tag, z.B.v1.2.0 -
update_latest: in der Regeltrue -
confirm_production: zur Fehlklick-Sicherung exaktDEPLOY
-
- Der Workflow verlangt einen erfolgreichen, von
verify_release.sh testerzeugten GitHub-Testnachweis fuer genau densource_tag. Danach laeuft er in der GitHub- Umgebungproduction. Fuer deinen Solo-Workflow muss dort kein Required Reviewer konfiguriert werden. - Der Workflow baut nicht neu, sondern setzt die finalen GHCR-Tags auf dieselben bereits getesteten Images.
- Optional danach ein GitHub-Release fuer Release Notes / Changelog anlegen. Das ist rein dokumentarisch; Images sind zu diesem Zeitpunkt schon gepromoted.
Die GitHub-Umgebung wird beim ersten Workflow-Lauf automatisch angelegt. In
Settings → Environments → production keine Reviewer-Regel aktivieren, solange du
allein arbeitest. Die technische Test-Gate- und DEPLOY-Pruefung bleiben aktiv.
Erst wenn Test erfolgreich verifiziert und der Promotion-Workflow erfolgreich war. Auf dem Prod-Server:
vim .env # HOCX_VERSION auf die neue Version setzen
./scripts/deploy.sh prod
./scripts/verify_release.sh prodAuf Prod immer den finalen Release-Tag pinnen, nie einen Candidate-Tag.
Schlagen Containerstart oder Smoke-Checks fehl, startet deploy.sh automatisch das
letzte erfolgreiche Image-Set aus .releases/current.env. Das Manifest enthaelt
unveraenderliche Image-Digests, nicht nur Tags. Eine bereits erfolgreiche
Datenbankmigration wird dabei bewusst nicht automatisch zurueckgerollt.
Falls ein Problem erst spaeter auffaellt:
vim .env # HOCX_VERSION auf die vorherige, bekannt gute Version zuruecksetzen
./scripts/deploy.sh prod
./scripts/verify_release.sh prodDas rollt den Code zurück. Falls die Migration der neuen Version das Schema destruktiv verändert hat (Spalte gelöscht, Typ geändert), reicht ein Code-Rollback nicht - dann muss zusätzlich das vor dem Update gezogene Backup eingespielt werden:
gunzip -c backups/<timestamp>-pre-vX.Y.Z.sql.gz | docker compose -p hocx exec -T db psql -U hocx hocxDeshalb: bei riskanten Schema-Änderungen (Spalte umbenennen/löschen, Typ ändern) über zwei Releases gehen statt in einem Schritt - z.B. neue Spalte hinzufügen und befüllen in Release A, alte Spalte erst in Release B entfernen. Das hält jeden einzelnen Schritt rückwärtskompatibel und Rollback ohne Backup-Restore möglich.
Bekannte Ausnahme (Audit I6, 2026-08-16): backend/alembic/versions/0018_cycle_config.py
migriert Daten und entfernt die alten Spalten im selben Schritt (plus ein
downgrade-unfähiges DELETE verwaister Zeilen) - verstösst gegen diese Regel, ist aber
bereits produktiv angewendet und wird nicht nachträglich umgeschrieben. Nur als Beleg
stehen gelassen, dass die Regel oben nicht rückwirkend gilt, aber für alle künftigen
Migrationen bindend bleibt.
docker compose -p hocx-test -f docker-compose.release.yml -f docker-compose.clamav.yml \
-f docker-compose.traefik.yml -f docker-compose.test.yml --env-file .env \
--project-directory "$(pwd)" down -v
./scripts/deploy.sh test
./scripts/verify_release.sh test-v löscht auch das Postgres-Volume - Test startet dann wieder mit leerer DB und
durchläuft beim nächsten Start die komplette Alembic-Historie von Anfang an.
- Test-Server provisionieren, Docker + Docker Compose installieren.
- DNS:
test.hocx.ch,abgabe-test.hocx.ch, optionaldocs-test.hocx.chundweb-test.hocx.chauf die Test-Server-IP zeigen lassen. - Repo als root klonen:
git clone git@github.com:FrissBrot/hocX.git. - Im Repo als root
./scripts/provision_deploy_user.sh testausfuehren. Das Skript erstellthocx-deploy, installiert bei Debian/Ubuntu fehlende Werkzeuge (gh,jq,curl), richtet Docker-Zugriff und alle Besitz-/Runtime-Rechte ein und zeigt danach den erforderlichen Benutzerwechsel an./etc/hocx/environmentbindet den Host dauerhaft antest; Prod- und Dev-Starts werden auf diesem Host abgelehnt. - Mit
sudo -iu hocx-deploywechseln, ins Repository gehen und./scripts/deploy.sh teststarten. Falls.envfehlt, fragt das Skript die nicht automatisch erzeugbaren Werte interaktiv ab, legt die Datei mit zufaelligen Secrets und Dateirechten 600 an und startet danach direkt die Umgebung. - Bei spaeteren Deploys in
.envHOCX_VERSIONauf den neuen Candidate-Tag setzen. - Nach dem ersten Deploy und nach Updates
./scripts/verify_release.sh testausfuehren. Bei erfolgreichen Checks schreibt das Skript automatisch einen maschinenlesbaren GitHub-Deployment-Status fuer exakt diesen Candidate-Tag. Dieser Nachweis ist die technische Voraussetzung fuer eine spaetere Prod-Promotion. - Test-Admin-Login mit
INITIAL_ADMIN_EMAIL/INITIAL_ADMIN_PASSWORDpruefen, danach weitere Test-Admins anlegen und das Bootstrap-Passwort aendern.
Beim ersten Deploy fragt deploy.sh zwei getrennte Tokens verdeckt ab. Empfohlen sind:
- ein Fine-grained PAT, ausschliesslich fuer
FrissBrot/hocX, mitContents: read,Actions: readundDeployments: read and write; - ein klassischer PAT mit ausschliesslich
read:packagesfuer GHCR.
Die Trennung verhindert, dass der Registry-Token auch Repository-Rechte erhaelt. Beide
werden mit Modus 600 unter .tools/ statt in .env gespeichert. Das Skript prueft
Repository-, Actions-, Deployment- und GHCR-Anmeldung bei jedem Deploy erneut.
Der Status kann bei Bedarf manuell kontrolliert werden:
sudo -iu hocx-deploy
gh auth statusDer Token wird von gh im geschuetzten Benutzer-Credential-Speicher verwaltet und
gehoert nicht in .env. Die Deployment-Schreibberechtigung kann ohne einen kuenstlichen
Testeintrag nicht vorab geprueft werden; sie wird spaetestens beim ersten erfolgreichen
verify_release.sh test real validiert. Scheitert dieser Eintrag, bleibt die
Prod-Promotion gesperrt.
- Server provisionieren, Docker + Docker Compose installieren.
- DNS:
hocx.chundabgabe.hocx.ch(oder analog) auf die Server-IP zeigen lassen. - Repo als root klonen (nur für die Compose-Dateien und
infra/traefik/nötig, kein Source-Build):git clone git@github.com:FrissBrot/hocX.git. - Im Repo als root
./scripts/provision_deploy_user.sh prodausfuehren. Danach mitsudo -iu hocx-deployzum dedizierten Deploy-Benutzer wechseln. Direktedeploy.sh-Aufrufe als root werden bewusst abgelehnt. Die Root-eigene Markierung/etc/hocx/environmentblockiert auf diesem Host Test- und Dev-Starts. - Im Repo
./scripts/deploy.sh prodstarten. Falls.envfehlt, fragt das Skript Domains, Image-Version und externe Zugangsdaten interaktiv ab. Ableitbare Werte und sichere Zufalls-Secrets erzeugt es selbst; die neue.enverhaelt Dateirechte 600. - Das Skript zieht die in
.envgepinnte Version und startet den kompletten Stack inkl. eigenem Traefik (Let's-Encrypt-Zertifikate werden beim ersten Start automatisch bezogen, dauert ein paar Minuten). Vor dem Start verifiziert es jedes Image gegen die Signatur desbuild-test-images.yml-Workflows. Eine fest gepinnte Cosign-Version wird bei Bedarf nach.tools/geladen und gegen die im Skript hinterlegte SHA-256-Pruefsumme geprueft; eine systemweite Installation ist nicht erforderlich. - Bootstrap-Admin-Login mit
INITIAL_ADMIN_EMAIL/INITIAL_ADMIN_PASSWORDaus.envprüfen, danach im Admin-Panel weitere Admins anlegen und das Bootstrap-Passwort ändern.
Ein normaler deploy.sh-Lauf aktualisiert Skripte und Compose-Dateien niemals selbst.
Als hocx-deploy wird ein Update bewusst separat ausgefuehrt:
cd /docker/hocX
./scripts/update_deploy_code.sh
./scripts/deploy.sh prod # auf dem Testhost entsprechend: testOptional kann das erfolgreiche Fast-Forward-Update direkt den an den Host gebundenen Deploy starten:
./scripts/update_deploy_code.sh --deployDer Updater akzeptiert nur die fest hinterlegte hocX-GitHub-Remote, den Branch main,
einen sauberen tracked Worktree und einen reinen Fast-Forward auf origin/main. Vor dem
Fast-Forward lädt er den zum Commit gehoerenden CI-Nachweis, prueft dessen keyless
Cosign-Signatur gegen den festen build-test-images.yml-Workflow und verifiziert die
Hashes aller Deploy-Skripte, Compose- und Traefik-Dateien. Damit reicht ein blosses
Manipulieren von Git oder origin/main nicht mehr aus. Hierfuer wird dieselbe einmalige
GitHub-Anmeldung wie fuer den Testnachweis benoetigt; fehlt sie, wird der Token auch hier
verdeckt abgefragt und geprueft. Der Updater nutzt denselben
exklusiven Lock wie deploy.sh. .env, Storage, Backups, .tools und .releases sind
ignoriert und werden nicht veraendert.
Zwei eigenständige Skripte in scripts/, gedacht für periodische Ausführung per Cron
(zusätzlich zum automatischen Pre-Deploy-Backup, das deploy.sh bei jedem Update ohnehin
macht). Beide sind idempotent, loggen mit Zeitstempel nach stdout/stderr und brechen bei
Fehlern mit Exit-Code ≠ 0 ab (wichtig für Cron-Fehlerbenachrichtigung/Monitoring).
-
Zweck: nächtlicher Postgres-Dump, unabhängig vom Deploy-Zyklus. Nutzt denselben
pg_dump-Mechanismus wie der Pre-Deploy-Backup indeploy.sh(docker compose exec db pg_dump ... | gzip) - ein Cron-Backup lässt sich also genauso zurückspielen wie ein Pre-Deploy-Backup (siehe Abschnitt 5, Rollback). -
Braucht: keine Argumente, läuft immer gegen den Checkout, in dem es liegt (
.envim Repo-Root mussPOSTGRES_USER/POSTGRES_DBenthalten; derdb-Container muss laufen, sonst bricht das Skript kontrolliert ab). -
Env-Var:
RETENTION_DAYS(Default 14) - Dumps, die älter sind, werden nach jedem Lauf automatisch gelöscht. -
Schreibt nach:
backups/<timestamp>-cron.sql.gz(derselbe Ordner wie die Pre-Deploy-Backups, gut unterscheidbar am-cron-Suffix). -
Empfohlener Cron-Zeitplan: täglich 03:15 Uhr, mit Logdatei im selben
backups/-Ordner:15 3 * * * /docker/hocX/scripts/backup_db.sh >> /docker/hocX/backups/backup_db.log 2>&1 -
Manuell testen:
./scripts/backup_db.shdirekt im Repo-Root ausführen und danachls -lh backups/prüfen. -
⚠️ Offenes Risiko: kein Offsite-Backup aktiv (Audit I5, 2026-08-16).backups/liegt auf derselben Partition wie das Docker-Volume der Live-DB (postgres_data) - bei Festplatten- oder Hostausfall sind Live-Daten und alle lokalen Backups gleichzeitig weg. Das Skript unterstützt seit diesem Fund einen optionalen rclone-Sync (siehe Kommentar am Ende vonbackup_db.sh):OFFSITE_BACKUP_REMOTEin.envsetzen (z.B.s3:mein-bucket/hocx-backups) undrcloneinstallieren + konfigurieren (rclone config, siehe rclone.org/docs) - ohne das bleibt der Sync ein No-op und dieses Risiko besteht weiter. Noch nicht eingerichtet - eine bewusste Entscheidung dazu (welcher Anbieter, wer die Kosten trägt) steht noch aus.
-
Zweck: räumt ausschliesslich
storage/exports/(generierte PDF/LaTeX-Exportdateien) nach Alter auf. Bewusst nicht angefasst werdenstorage/tenant_imports,storage/tenant_clonesundstorage/abgabebox-uploads- das sind laut Code- und Live-DB-Prüfung permanente Speicherorte für echte Mandantendaten, keine Temp-Dateien (Details dazu direkt im Skript-Kommentar). -
Braucht: keine Pflicht-Argumente. Optionales
--dry-runzeigt nur an, was gelöscht würde, ohne etwas zu löschen. -
Env-Var:
RETENTION_DAYS(Default 30). -
Schreibt/löscht: Dateien älter als
RETENTION_DAYSTage unterstorage/exports/(ausser.gitkeep) sowie danach leer gewordene Unterverzeichnisse; Ausgabe geht nach stdout/stderr (kein eigenes Logfile im Skript, daher beim Cron-Eintrag umleiten). -
Empfohlener Cron-Zeitplan: täglich 03:45 Uhr, also nach
backup_db.sh, damit ein Backup immer vor einer Cleanup-Runde derselben Nacht liegt:45 3 * * * /docker/hocX/scripts/cleanup_storage.sh >> /docker/hocX/backups/cleanup_storage.log 2>&1 -
Manuell testen: erst
./scripts/cleanup_storage.sh --dry-run(zeigt betroffene Dateien, löscht nichts), danach bei Bedarf ohne Flag fuer den echten Lauf.
Die Tabelle im Abschnitt "Umgebungen" oben führt hocx.example.com als "Dev" -
das beschreibt korrekt, wie die Umgebung technisch betrieben wird (lokaler Build aus
Source, kein GHCR-Image, kein HOCX_VERSION-Pinning), sagt aber nichts darüber aus, wer
davon abhängt. In Wirklichkeit laufen dort echte Mandanten mit echten Daten - die Instanz
wird faktisch wie Prod genutzt, auch wenn sie technisch wie Dev aufgesetzt ist. Ein
Update dort ohne Vorsicht (kein vorheriges Backup, kein Health-Check danach) ist damit
ein echtes Ausfallrisiko für echte Nutzer, nicht nur für einen Wegwerf-Testaccount.
Das echte lokale Entwickeln ist davon inzwischen bewusst getrennt: dafür ist der
Overlay docker-compose.dev.yml gedacht (siehe Abschnitt oben), nicht die Server-Instanz
hocx.example.com.
Deshalb gilt für jedes Update von hocx.example.com (der lokale Source-Build-Mechanismus selbst bleibt unverändert - das ist eine bewusste, hier nicht revidierte Entscheidung, keine Pipeline-Umstellung auf GHCR-Images ist im Rahmen dieses Punkts vorgesehen):
-
Vor jedem Update: Backup ziehen, unabhängig vom nächtlichen Cron-Lauf aus
Abschnitt 10 -
./scripts/backup_db.shim Repo-Root ausführen und den Erfolg (neue Datei unterbackups/) prüfen, bevor der Code aktualisiert wird. -
Update durchführen:
git pull+docker compose up -d --build(Alembic migriert die DB dabei automatisch, wie bei den anderen Umgebungen auch). -
Nach dem Update verifizieren (analog zur Test-Verifikation in Abschnitt 2, mit
einem Wegwerf-Testaccount, danach wieder löschen):
- https://hocx.example.com/login erreichbar, Branding lädt korrekt
- Login funktioniert, mindestens eine Tabellen-Seite lädt Daten
-
docker compose logs backend --tail=50zeigt keine Fehler, insbesondere keine Alembic-Fehler beim Start
- Bei Problemen: Rollback wie in Abschnitt 5 beschrieben, mit dem in Schritt 1 frisch gezogenen Backup statt eines älteren Cron-Backups.
- Startseite
-
Entwicklerdokumentation
- Entwickler-Onboarding
- Architektur
- Big Picture
- Code-Architektur
- API-Konventionen
- Datenbank und Migrationen
- Platform-Admin-Panel
- Mandanten-Export und -Import
- Testprozess
- Konfiguration
- Debugging und Observability
- Beitragen und Qualität
- Dokumentationspflege
- Deployment-Runbook
- Sicherheit
- Bekannte offene Punkte