From 993adca80b3fb2798117f326f81e3e498a3c5bff Mon Sep 17 00:00:00 2001 From: Nick Aldewereld Date: Wed, 8 Jul 2026 10:30:03 +0200 Subject: [PATCH] fix(docker): run the default image as a non-root user Run as the built-in unprivileged `node` user, add a HEALTHCHECK, exclude .git/.github from the build context, and add no-new-privileges / cap_drop / read_only to the reference compose file. --- .dockerignore | 2 ++ Dockerfile | 12 +++++++++--- docker-compose.yml | 13 +++++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/.dockerignore b/.dockerignore index 68e6410a..2411e5d5 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,5 @@ +.git +.github .output .data .nuxt diff --git a/Dockerfile b/Dockerfile index 6f681840..ed7f44e4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,7 +26,7 @@ RUN pnpm --filter @enclosed/crypto run build && \ pnpm --filter @enclosed/app-client run build && \ pnpm --filter @enclosed/app-server run build:node -# Production image +# Production image FROM node:22-alpine WORKDIR /app @@ -35,9 +35,15 @@ WORKDIR /app COPY --from=builder /app/packages/app-client/dist ./public COPY --from=builder /app/packages/app-server/dist-node/index.cjs ./index.cjs -# Create the .data directory -RUN mkdir -p /app/.data +# Create the .data directory and hand ownership to the built-in unprivileged +# `node` user so the container does not run as root. +RUN mkdir -p /app/.data && chown -R node:node /app + +USER node EXPOSE 8787 +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD wget -qO- http://127.0.0.1:8787/api/ping || exit 1 + CMD ["node", "index.cjs"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 3132a7e8..2282111b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,6 +6,19 @@ services: volumes: - enclosed-data:/app/.data restart: unless-stopped + # Defense-in-depth hardening for the reference deployment. + security_opt: + - no-new-privileges:true + cap_drop: + - ALL + read_only: true + tmpfs: + - /tmp + # Set a strong secret and enable auth if this instance is not meant to be public: + # environment: + # AUTHENTICATION_JWT_SECRET: "" + # PUBLIC_IS_AUTHENTICATION_REQUIRED: "true" + # AUTHENTICATION_USERS: "you@example.com:" volumes: enclosed-data: