Skip to content

fix(docker): run the api and web images as the unprivileged node user - #47

Open
Yi-111-a wants to merge 1 commit into
ErTasselli:mainfrom
Yi-111-a:fix/run-containers-as-non-root
Open

Yi-111-a wants to merge 1 commit into
ErTasselli:mainfrom
Yi-111-a:fix/run-containers-as-non-root

Conversation

@Yi-111-a

Copy link
Copy Markdown

Fixes #25

Summary

Both runtime stages of server/Dockerfile and web/Dockerfile inherit root from the node:22-slim base image — neither Dockerfile has a USER directive, so the API and the web container both run as uid 0. This adds the missing directive and fixes the ownership it implies.

Changes

server/Dockerfile (runtime stage)

RUN mkdir -p /app/data && chown -R node:node /app
USER node

/app/data is created in the image before the user is dropped, because docker-compose.yml mounts the terminal-data named volume there:

environment:
  - DATA_DIR=/app/data
volumes:
  - terminal-data:/app/data

Docker initialises an empty named volume from the image directory at that path, including its ownership. Creating and chowning /app/data in the image is therefore what keeps the volume writable for node; without it the volume would be created root-owned and the API would fail at startup, when server/src/db.ts runs mkdirSync(dataDir, { recursive: true }) and server/src/auth.ts writes the generated key with writeFileSync(keyFile, generated, { mode: 0o600 }). The web container mounts the same volume read-only (:ro), so no ownership change is needed for it.

web/Dockerfile (runtime stage)

RUN chown -R node:node /app
USER node

The standalone Next.js bundle only reads its own output at runtime, so chowning /app and switching users is sufficient.

The build stages are unchanged — they only need write access to /app while compiling.

Why this is safe

  • The node user already exists in the official image (uid/gid 1000); no useradd is needed.
  • Neither runtime stage needs any capability that root provided — both just node a JS entrypoint and bind a high port (>1024), so there is no reason to keep uid 0.
  • No CAP_ requirements are introduced or removed.
  • The image contents are unchanged apart from ownership bits, so layer caching still works: the chown runs after the final COPY, in the runtime stage only.

Verification

The change is Dockerfile-only; npm run test -w server, npm run build -w server and npm run build -w web (the jobs in .github/workflows/ci.yml) do not build or run the images, so they are unaffected by this diff. I could not run docker build on the machine this was prepared on, so the build itself is unverified here — CI does not exercise the Dockerfiles either. Worth a maintainer-side docker compose build && docker compose up to confirm the volume-ownership behaviour described above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Docker images run as root in both server and web Dockerfiles (no USER directive)

1 participant