fix: Docker container fails to start with misleading OOM error#22
Conversation
The distroless nonroot image (UID 65532) couldn't write to the bind-mounted data directory owned by the host user, causing SQLite to report SQLITE_CANTOPEN as "out of memory (14)". - Create /data with correct ownership in builder stage and COPY it to runtime stage with --chown=65532:65532 - Switch from bind mount to named volume so Docker handles permissions automatically on first use Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Thanks for tracking this down - the permission issue with distroless + bind mounts is a real pain point. One concern before merging: this switches existing compose users away from Suggested fix - migration-aware entrypoint: The idea is to detect when a user is upgrading from bind mount to named volume and copy their existing DB files once. 1. Add a Docker entrypoint in // +build docker
func dockerEntrypoint() {
legacyPath := "/legacy-data/onwatch.db"
newPath := "/data/onwatch.db"
// Only migrate if:
// - legacy DB exists (user upgrading from bind mount)
// - new DB doesn't exist (named volume is fresh)
if fileExists(legacyPath) && !fileExists(newPath) {
log.Println("Migrating DB from legacy bind mount to named volume...")
// Copy main DB and WAL/SHM files
copyFile(legacyPath, newPath)
copyFileIfExists("/legacy-data/onwatch.db-wal", "/data/onwatch.db-wal")
copyFileIfExists("/legacy-data/onwatch.db-shm", "/data/onwatch.db-shm")
log.Println("Migration complete")
}
}2. Update ENTRYPOINT ["/onwatch"]
CMD ["--docker-entrypoint"]Or use a shell wrapper if you prefer separation. 3. Update volumes:
- onwatch-data:/data # new named volume (read-write)
- ./onwatch-data:/legacy-data:ro # old bind mount (read-only, for migration)Why this works:
Validation approach:
If you'd like, I can implement this and push to your branch - just let me know. Either way, I'd hold off merging until we have an upgrade path for existing users. |
|
Thanks for the review! I don't think migration logic is needed here though - users are either using a bind mount or a named volume, never both at the same time. There's no scenario where data silently disappears:
Adding an entrypoint migration path with a legacy mount adds complexity for a case that doesn't organically occur. |
|
The scenario I'm concerned about:
The data isn't deleted (still in Just want to make sure we don't surprise existing users. How would you approach handling this? |
The Dockerfile fix (creating /data owned by UID 65532) resolves permission errors for both bind mounts and named volumes, so there's no need to change the default volume strategy in docker-compose.yml. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Good point - I've reverted the docker-compose.yml changes. The Dockerfile fix alone handles this: creating So now:
Simpler change, no breaking behavior. |
prakersh
left a comment
There was a problem hiding this comment.
Thanks for the quick iteration and for reverting the compose default change to avoid user migration surprises. Approving this as an incremental improvement; we will follow up with DB-path preflight diagnostics to replace the SQLite OOM-style startup error with actionable permission guidance.
Fail fast with actionable diagnostics when the SQLite DB path is not writable, including existing read-only database files. Preserve valid SQLite file: URIs and memory DSNs, and update Docker bind-mount guidance to use recursive chown. Follow-up to #22.
Summary
nonrootimage (UID 65532) couldn't write to the bind-mounted data directory owned by the host user, causing SQLite to reportSQLITE_CANTOPENas "out of memory (14)"/datawith correct ownership in the builder stage andCOPY --chown=65532:65532to the runtime stageTest plan
docker compose buildsucceedsdocker compose upstarts without errors (previously crash-looped with OOM)🤖 Generated with Claude Code