diff --git a/Dockerfile b/Dockerfile index 76a8e5fc..b2ed1768 100644 --- a/Dockerfile +++ b/Dockerfile @@ -40,7 +40,7 @@ RUN corepack enable && corepack prepare pnpm@10.34.3 --activate # Install build dependencies for better-sqlite3 native module RUN apt-get update && \ - apt-get install -y --no-install-recommends python3 make g++ ffmpeg && \ + apt-get install -y --no-install-recommends ffmpeg gosu && \ rm -rf /var/lib/apt/lists/* WORKDIR /app @@ -72,6 +72,11 @@ COPY --from=builder /app/packages/web/dist packages/web/dist # Create data directories RUN mkdir -p /app/data/uploads +# Non-root hardening: copy the privilege-dropping entrypoint. It chowns the +# data volume as root, then execs the CMD as the unprivileged `node` user. +COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh +RUN chmod +x /usr/local/bin/docker-entrypoint.sh + # Set environment defaults ENV NODE_ENV=production ENV PORT=3000 @@ -94,4 +99,5 @@ HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=5 \ # Run the server using tsx from the server package directory WORKDIR /app/packages/server +ENTRYPOINT ["docker-entrypoint.sh"] CMD ["node", "--import", "tsx/esm", "src/index.ts"] diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100755 index 00000000..85c79b73 --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,18 @@ +#!/bin/sh +# Runs as root: make the (bind-mounted, host-owned) data dir writable by the +# non-root `node` user, then drop privileges via gosu and exec the CMD. This +# lets the container run as uid 1000 while still owning ./data on hosts where +# the bind mount was created by a different uid. +# +# - Idempotent AND cheap: only chown entries not already node-owned, so after +# the first boot this is near-instant. A plain `chown -R` over a large +# uploads/ tree on slow Pi/SD storage would delay startup on EVERY restart. +# - Non-fatal: on a bind mount that rejects chown (some CIFS/NFS backings), +# warn and continue rather than crash-looping under `restart: unless-stopped` +# (the old root container booted fine on such mounts). +set -e +mkdir -p /app/data/uploads +chown node:node /app/data /app/data/uploads 2>/dev/null || true +find /app/data ! -user node -exec chown node:node {} + 2>/dev/null || \ + echo "docker-entrypoint: warning: could not chown /app/data; continuing (ensure it is writable by uid 1000)" +exec gosu node "$@"