A one-shot backup service runs before the app starts (depends_on
service_completed_successfully; compose re-runs exited one-shots on
every up), so the plain docker compose pull + up -d update flow always
snapshots the database first. Uses SQLite's online-backup API via
better-sqlite3 from the app image itself - safe against the live WAL
database, no downtime, no extra tooling. Backups land in ./backups
(newest 14 kept); a failed backup blocks the app from starting rather
than updating without a safety net.
Docker creates ./data root-owned on the host, but the app runs as the unprivileged node user, causing SQLITE_CANTOPEN on first deploy. New entrypoint starts as root, chowns DATA_DIR, then drops privileges with setpriv. Compose keeps the SQLite database in ./data next to the compose file. Also trims scripts/ and playwright artifacts from the image.