Skip to content

fix(docker): prevent SQLITE_CANTOPEN crash on first startup (closes #38)#38

Open
ldoublewood wants to merge 2 commits into
AeternaLabsHQ:mainfrom
ldoublewood:fix/data-dir-permission
Open

fix(docker): prevent SQLITE_CANTOPEN crash on first startup (closes #38)#38
ldoublewood wants to merge 2 commits into
AeternaLabsHQ:mainfrom
ldoublewood:fix/data-dir-permission

Conversation

@ldoublewood

Copy link
Copy Markdown

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.

…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 syswave-dev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread Dockerfile Outdated
Co-authored-by: Andreas <263179084+syswave-dev@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants