From 1abc08b60fa659de4da223d05c2161801ab54fad Mon Sep 17 00:00:00 2001 From: Tronax Date: Sun, 16 Aug 2026 16:35:08 +0200 Subject: [PATCH] fix(docker): make /app/data writable by node user on bind-mounted volumes The container ran as the unprivileged "node" user, but bind-mounted host directories (e.g. ./data:/app/data in docker-compose or an Unraid appdata folder) are mounted with root ownership. The node user therefore could not create trxtd.db in /app/data, failing at startup with: Error: unable to open database file at file:///app/server/db.mjs:12 Fix: - Add docker-entrypoint.sh that mkdir/chown /app/data to node:node on container start, then drops privileges back to node via su-exec - Install su-exec in the runtime stage (apk add --no-cache su-exec) - Remove USER node so the entrypoint runs as root and can fix ownership, with privilege dropping handled inside the entrypoint instead - Wire the script up as ENTRYPOINT, keeping CMD as the app itself The mount path itself is unchanged (/app/data, host ./data). Verified with a real bind mount: /health responds ok and trxtd.db/-shm/-wal are created in the mounted directory. --- Dockerfile | 19 +++++++++++-------- docker-entrypoint.sh | 11 +++++++++++ 2 files changed, 22 insertions(+), 8 deletions(-) create mode 100644 docker-entrypoint.sh diff --git a/Dockerfile b/Dockerfile index 94778e4..69d4d64 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,27 +13,30 @@ FROM node:22-alpine WORKDIR /app ENV NODE_ENV=production -# production dependencies only (ws) +# production dependencies only (ws) + su-exec for privilege dropping COPY package.json package-lock.json ./ -RUN npm ci --omit=dev && npm cache clean --force +RUN npm ci --omit=dev && npm cache clean --force && \ + apk add --no-cache su-exec COPY server/server.mjs server/server.mjs COPY server/db.mjs server/db.mjs COPY shared/ shared/ COPY --from=build /app/dist dist/ +COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh +RUN chmod +x /usr/local/bin/docker-entrypoint.sh # persistent account/progress database (mount a volume here on the host) -RUN mkdir -p /app/data && chown -R node:node /app/data +RUN mkdir -p /app/data VOLUME ["/app/data"] -# run as unprivileged user -USER node - EXPOSE 3001 ENV PORT=3001 ENV DATA_DIR=/app/data +# The entrypoint fixes permissions on bind-mounted /app/data and drops +# privileges to the unprivileged "node" user before running the app. +ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] +CMD ["node", "server/server.mjs"] + HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD node -e "fetch('http://localhost:3001/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))" - -CMD ["node", "server/server.mjs"] diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100644 index 0000000..dc93ab7 --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,11 @@ +#!/bin/sh +set -e + +# Ensure the data directory exists and is owned by the node user. +# This fixes permission issues with bind-mounted volumes (e.g. ./data:/app/data) +# where the host directory is owned by root but the container runs as node. +mkdir -p /app/data +chown -R node:node /app/data 2>/dev/null || true + +# Drop privileges and run the actual application as node +exec su-exec node "$@" \ No newline at end of file