fix: auto-fix upload-volume ownership in Docker, fail loudly on worker misuse - #22
Merged
Merged
Conversation
…misuse Docker Compose creates a missing bind-mount source directory (e.g. ./data/uploads) owned by root on the host. The image runs as a non-root "app" user, so every write there failed with EACCES — surfacing as "Upload failed" / the new storage-writable health check going red, with no indication of the actual cause. - docker-entrypoint.sh now starts as root, chowns UPLOAD_DIR/TUS_DIR to "app" if they aren't already (skipped on later starts once ownership is correct, so no recursive walk on every restart), then drops to "app" via su-exec before running migrations and starting the app — the same pattern official images like postgres use. Dockerfile no longer sets a permanent USER, since the entrypoint now handles the privilege drop. - the entrypoint also now forwards a custom command (e.g. compose's `command: ["npm", "run", "worker"]`) instead of silently ignoring it and always starting the web server. The prebuilt standalone image can't actually run the dedicated worker (needs tsx + raw TS sources it doesn't ship), so this turns a silent, hard-to-notice misconfiguration — a "worker" container that was actually just running an unreachable second copy of the web server — into an immediate, clear startup failure. - docs/self-hosting.md: new Troubleshooting section covering both.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A self-hosted deployment on the standard
docker-compose.ymlpattern hit "Der Datei-Speicher ist nicht beschreibbar" in the admin panel (the storage-writable health check added in v1.1). Root cause: Docker Compose creates a bind-mount source directory (./data/uploads) owned by root on the host the first time it doesn't already exist, but the image runs the app as a non-rootappuser — so every write fails withEACCES. This affects essentially every fresh deployment following the documented Quick Start, not just this one report.Fix
docker-entrypoint.shnow starts as root, corrects ownership ofUPLOAD_DIR/TUS_DIRif needed (skipped once already correct, so no recursivechownwalk on every restart of a long-lived deployment), then drops to theappuser viasu-execbefore running migrations and starting the app — the same privilege-drop pattern official images likepostgres/mysqluse. TheDockerfileno longer sets a permanentUSER app, since the entrypoint now owns that.Second finding (same file, same investigation)
While tracing this, I found the entrypoint unconditionally ran
node server.jsregardless of anycommand:override — so aworker:service configured per the docs (command: ["npm", "run", "worker"], pointed at the prebuilt image) doesn't actually run the worker. It silently starts a second, unreachable copy of the web server instead, since the prebuilt standalone image doesn't includetsxor the raw TypeScript sources the worker entrypoint needs. No crash, no error — just quietly wrong, which is worse than a visible failure.The entrypoint now forwards a given command and lets it fail loudly (missing script / missing
tsx) instead of silently substituting the web server. Documented in the new Troubleshooting section: don't pointworker:at the prebuilt image — the web tier already runs jobs in-process by default, which is sufficient for most deployments; a real dedicated worker needs a custom image built from a full source checkout (out of scope here — happy to build that separately if there's demand).Changes
docker-entrypoint.sh: root-then-drop-privileges + command forwarding (see above)Dockerfile: addssu-exec, removes the now-redundantUSER appdocs/self-hosting.md: new Troubleshooting section for both issues, including the manual one-off fix for anyone already on an older imageVerification
No Docker daemon available in this sandbox to run a real
docker build, so I could not execute this end-to-end. I did:su-execis a standard Alpine package used by numerous official images for exactly this pattern.sh -n docker-entrypoint.sh— syntax check passes; script is plain POSIXsh(no bashisms), consistent with Alpine'sash.study-app, no args) path and theworker(npm run workerargs) path, including what happens ifUPLOAD_DIR/TUS_DIRare already correctly owned (fast top-level-only check, no full walk).npm run typecheck && npm run lint && npm test— unaffected by this change, still green (126 passed).Please have CI's Docker build validation (
release-candidatelabel) confirm the image actually builds and starts before merging — that's the one thing I couldn't verify locally.🤖 Generated with Claude Code
https://claude.ai/code/session_01UrdkggYqhNCCZVHyJrdwBR
Generated by Claude Code