Beim Cold-Start eines Containers mit persistentem Volume (DB ist nicht
leer) liefen die gunicorn-Worker parallel in init_db() und beide
versuchten CREATE TABLE. Einer crashed mit 'table genres already exists',
der andere startete.
Symmetrisch zum seed-Lock: init_db() jetzt auch in FileLock gewrapped.
Zusaetzlich *.init.lock in .gitignore.
Verifiziert mit 2 Workern + bestehender DB: beide Container starten
sauber, kein Crash, 8 items erhalten.
Default: Image wird aus git.pkop.de/vibecode/watchlist:<version>
gepullt. Version via WATCHSTACK_VERSION ueberschreibbar, liest sonst
aus app/__init__.py.
Opt-in Profile "local" baut das Image lokal aus dem Dockerfile
(Dev-Workflow, Port 8001 damit Registry-Variante parallel laeuft).
docker compose up -d # Registry-Image
docker compose --profile local up -d --build # lokal
WATCHSTACK_VERSION=0.6.0 docker compose pull && up -d
Verifiziert: pull rc=0, up rc=0, health -> version=0.5.1-beta.
- bin/build-and-push.sh: baut + pusht Image in die Gitea-Registry
unter git.pkop.de/vibecode/watchlist:<version>
- bin/get-version.py: liest __version__ aus app/__init__.py
(single source of truth, kein Quoting-Wahnsinn in Bash)
- bin/build-image.sh + write-manifest.sh nutzen den neuen Helper
- dist/README.md: erklärt die Registry-Policy (kein Tar im Repo)
- load-image.sh entfernt — Tars gibt's nicht mehr
- .gitignore: dist/*.tar + manifest.json ausschließen,
dist/README.md bleibt dokumentiert
- docker-compose.yml: Hinweis auf Registry-Image
- CI-Workflow: vereinfacht, Push läuft lokal (idempotent)
- Version auf 0.5.1-beta gebumpt
Verifiziert end-to-end:
bin/build-and-push.sh -> Image in Gitea-Registry
docker rmi ... -> lokal weg
docker pull ... -> aus Registry gezogen
docker run ... -> startet, version 0.5.1-beta, 8 unique Items
When the beta image runs with --workers > 1 (default: 2), every worker
calls lifespan() in parallel. Each worker sees an empty DB, then
seed_if_empty() runs concurrently, producing 2x (or Nx) demo entries.
Fix: wrap the seed operation in a process-wide FileLock. Even if a
worker loses the race it re-checks the table count inside the critical
section and returns without re-seeding.
Verified by cold-starting a 4-worker container: 8 items (was: 32).
- FastAPI + SQLite backend with REST API
- Polymorphic Media model (book + series)
- Cover lookup via Open Library
- Vanilla HTML/CSS/JS frontend (MAL-inspired)
- Dark theme, status tabs, drawer detail, stats modal
- 8 seed demo entries