diff --git a/Dockerfile b/Dockerfile index 1ef0ca1..cae26c8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,7 +32,15 @@ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static RUN mkdir -p /app/data/prototypes \ && chown -R nextjs:nodejs /app/data -USER nextjs +# The entrypoint runs as root at startup so it can fix ownership of +# /app/data even when it is a volume created by an earlier image, +# then drops to the unprivileged `nextjs` user via su-exec. +RUN apk add --no-cache su-exec +COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh +RUN sed -i 's/\r$//' /usr/local/bin/docker-entrypoint.sh \ + && chmod +x /usr/local/bin/docker-entrypoint.sh + EXPOSE 3000 +ENTRYPOINT ["docker-entrypoint.sh"] CMD ["node", "server.js"] diff --git a/README.md b/README.md index 647b365..45f947e 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,10 @@ Notes: - Image uses Next.js `output: "standalone"` and `pnpm` in multi-stage build. - Runtime data is written to `/app/data/prototypes`; mount a volume to persist uploads. +- A `docker-entrypoint.sh` runs as root at container start, ensures `/app/data/prototypes` + exists and is owned by the app user, then drops to the unprivileged `nextjs` user. This + avoids `EACCES` errors even when the volume was created by an earlier image as root — + no need to delete the existing volume when upgrading. ## Upload Requirements diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100644 index 0000000..b335e9b --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,16 @@ +#!/bin/sh +set -eu + +# Runs as root at container start. Ensures the runtime data directory +# exists and is owned by the app user, then drops privileges and runs +# the app. This fixes the case where /app/data is a named/bind volume +# created by an earlier image and therefore owned by root (EACCES on +# mkdir /app/data/prototypes). + +if [ "$(id -u)" -eq 0 ]; then + mkdir -p /app/data/prototypes + chown -R nextjs:nodejs /app/data + exec su-exec nextjs:nodejs "$@" +fi + +exec "$@"