fix(docker): prevent SQLITE_CANTOPEN crash on first startup (closes #38)#38
fix(docker): prevent SQLITE_CANTOPEN crash on first startup (closes #38)#38ldoublewood wants to merge 2 commits into
Conversation
…eternaLabsHQ#38) When docker compose up -d is run without a pre-existing ./data directory, the Docker daemon creates it as root. The old Dockerfile used USER app, so the container process (UID=100) could not write to the root-owned bind mount — causing better-sqlite3 to fail with SQLITE_CANTOPEN and the container to restart endlessly. Replace the static USER app directive with a docker-entrypoint.sh that runs as root, chowns /data to app:app, then drops privileges via su-exec before starting node. This separates privilege-requiring setup from application execution — the same pattern used by official Postgres and Redis images. Co-Authored-By: Claude <noreply@anthropic.com>
syswave-dev
left a comment
There was a problem hiding this comment.
Thanks for this — the diagnosis is exactly right, and the entrypoint + privilege-drop pattern is the correct fix for the root-owned bind mount. I built the branch and ran it against a fresh root-owned /data mount to confirm the behaviour.
One sneaky thing to fix before merge: node:22-alpine already ships its own /usr/local/bin/docker-entrypoint.sh. Because the exec-form ENTRYPOINT ["docker-entrypoint.sh"] is resolved via $PATH — and /app isn't on $PATH — it picks up the base image's script, not the one added here (command -v docker-entrypoint.sh inside the image resolves to /usr/local/bin/docker-entrypoint.sh). So the new docker-entrypoint.sh never actually runs: the chown + su-exec drop is skipped, and since USER app was removed, the container ends up running as root. That's why it boots (root can write to the root-owned mount) — but it's a silent regression from the previous non-root app user, and the chown/su-exec logic in this PR is effectively dead code.
Referencing the script by absolute path fixes it (suggestion inline on the ENTRYPOINT line) — then your script runs, the chown applies, and it correctly drops back to app. I verified with that change: the container comes up, node server.js runs as PID 1 under app (not root), and cache.db lands in the root-created mount owned by app:app.
Non-blocking nit: chown -R app:app /data runs on every start, which adds latency once /data grows. Guarding it (only chown when the owner differs) would be cleaner, but I'm happy to merge without it.
Co-authored-by: Andreas <263179084+syswave-dev@users.noreply.github.com>
When docker compose up -d is run without a pre-existing ./data directory, the Docker daemon creates it as root. The old Dockerfile used USER app, so the container process (UID=100) could not write to the root-owned bind mount — causing better-sqlite3 to fail with SQLITE_CANTOPEN and the container to restart endlessly.
Replace the static USER app directive with a docker-entrypoint.sh that runs as root, chowns /data to app:app, then drops privileges via su-exec before starting node. This separates privilege-requiring setup from application execution — the same pattern used by official Postgres and Redis images.