stammtisch-hersbruck/backend/README.md
Daniel Michelberger 132ce04e0c fix: pb_migrations nicht mounten — Deployment startete ohne Collections
Der in 22cd427 ergänzte Bind-Mount ./pb_migrations:/pb/pb_migrations
überdeckt das Verzeichnis aus dem Image. Auf einem Deployment-Host, neben
dessen compose-Datei kein ausgechecktes Repo liegt, legt Docker dort ein
leeres Verzeichnis an: PocketBase findet keine Migration und startet mit
einer Datenbank ohne Collections. Genau das ist auf der neu deployten
Instanz passiert — users war da (legt PocketBase selbst an), events, teams,
riders, runs und times fehlten.

Reproduziert und beide Richtungen verifiziert: mit leerem Mount 404 auf
allen fünf Collections, ohne Mount kommen alle sechs aus dem Image.

Die Migrationen kommen damit wieder ausschließlich über COPY ins Image. Der
Preis ist der Weg zurück: Im Admin-UI erzeugte Migrationen liegen nur im
Container und müssen mit "docker compose cp" ins Repo geholt werden. Das
steht jetzt in backend/README.md und CLAUDE.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 14:06:30 +02:00

106 lines
4.4 KiB
Markdown

# stammtisch-hersbruck.de — Backend (PocketBase)
PocketBase-Instanz für Events, Läufe, Fahrer, Zeiten und Teams.
## Lokal starten
`.env` aus der Vorlage anlegen (die `.env` selbst steht in `.gitignore`):
cp .env.example .env
und die Werte eintragen — `.env.example` beschreibt jede Variable. Dann:
docker compose up -d --build
Admin-UI: http://127.0.0.1:8090/_/ — Login mit `SUPERUSER_EMAIL`/`SUPERUSER_PASSWORD`.
Damit das Frontend gegen diese Instanz läuft, in `../frontend/.env`
`PUBLIC_PB_URL=http://127.0.0.1:8090` setzen.
## Schema
Das Schema liegt als versionierte Migration in `pb_migrations/` und wird beim
Start automatisch angewendet. Es enthält die sechs fachlichen Collections
`users`, `teams`, `events`, `runs`, `riders`, `times`.
`1754400000_init_schema.js` ist ein Snapshot der produktiven Instanz. Er wurde
per `GET /api/collections` abgezogen und ruft `importCollections` mit
`deleteMissing = false` auf — die Migration legt an und aktualisiert, löscht
aber nichts, was nicht im Snapshot steht.
### Schema-Änderungen aus dem Admin-UI holen
`pb_migrations/` wird **nicht** in den Container gemountet — die Migrationen
kommen über `COPY` ins Image (siehe `Dockerfile`). Das ist Absicht: Ein
Bind-Mount überdeckt das Image-Verzeichnis, und auf einem Deployment-Host ohne
ausgechecktes Repo legt Docker dort ein leeres Verzeichnis an. PocketBase
findet dann keine Migration und startet mit einer Datenbank ohne Collections.
Die Kehrseite: Eine im Admin-UI erzeugte Migration liegt zunächst nur im
Container und muss von Hand herausgeholt werden:
# Welche Dateien sind dazugekommen?
docker compose exec pocketbase ls /pb/pb_migrations
# Die neue Datei ins Repo kopieren und committen
docker compose cp pocketbase:/pb/pb_migrations/<datei> ./pb_migrations/
Ohne diesen Schritt ist die Änderung beim nächsten `--build` verloren.
Ein frisch gestarteter Container hat damit dasselbe Schema wie die produktive
Instanz — aber **keine** Daten.
## Datenpersistenz
Sämtliche Laufzeitdaten liegen in der SQLite-DB unter `/pb/pb_data`: Datensätze,
Uploads, die SMTP-Einstellungen und der Superuser. Ohne gemountetes Volume auf
diesem Pfad sind sie nach jedem Redeploy verloren.
Das Volume-Mapping `./pb_data:/pb/pb_data` steht in der `docker-compose.yaml`,
damit die Persistenz-Konfiguration versioniert im Repo liegt.
**Trügerisches Signal:** Die Collections kommen aus der Migration und sind nach
einem Redeploy auch dann da, wenn gar kein Volume existiert. Sie taugen **nicht**
als Nachweis funktionierender Persistenz — das zeigt nur ein Datensatz, der in
keiner Migration steht.
Mount prüfen:
docker inspect <container> --format '{{json .Mounts}}' | jq
Erscheint dort kein Eintrag mit `"Destination": "/pb/pb_data"`, fehlt die Persistenz.
`pb_data/` steht in `.gitignore` und gehört dort auch hin.
## Superuser-Verhalten
`entrypoint.sh` ruft `superuser create` auf — bewusst **nicht** `upsert`.
Existiert der Account bereits, scheitert `create` mit „Value must be unique" und
das Passwort bleibt unangetastet. Eine im Admin-UI vorgenommene
Passwortänderung überlebt damit jeden Redeploy.
| Situation | Verhalten |
|---|---|
| Erster Start, Variablen gesetzt | Superuser wird angelegt |
| Neustart, Account existiert | „Superuser existiert bereits — Passwort bleibt unverändert" |
| Passwort im UI geändert, dann Neustart | UI-Passwort gilt weiter, Env wird ignoriert |
| Variablen nicht gesetzt | Übersprungen, PocketBase gibt Installer-Link im Log aus |
| Passwort zu kurz (< 8 Zeichen) | Container bricht mit Exitcode 1 ab |
Passwort später ändern: **im Admin-UI**, nicht über die Env eine Änderung der
Env-Variable hat keine Wirkung mehr, sobald der Account existiert.
## PocketBase aktualisieren
Die Version ist über das Build-Arg `PB_VERSION` gepinnt (Dockerfile und
`docker-compose.yaml`). Wert hochsetzen, committen, neu bauen.
Ein `latest` gibt es bewusst nicht: Der GitHub-Asset heißt
`pocketbase_<version>_linux_amd64.zip`, enthält die Version also im Dateinamen.
Der Pin ist auch gewollt sonst zieht jeder Rebuild unangekündigt eine neue
Version, inklusive möglicher DB-Schema-Migrationen.
Vor einem Update: Changelog prüfen
(https://github.com/pocketbase/pocketbase/releases) und `pb_data` sichern
PocketBase migriert die DB beim Start automatisch, ein Downgrade ist danach
nicht mehr ohne Weiteres möglich.