fix: build Docker image from the workspace root - #118
Conversation
The documented Docker build was broken. `cd opsimate-docs && docker build .` fails at `npm ci`: npm error `npm ci` can only install packages when your package.json and package-lock.json are in sync. Invalid: lock file's @docusaurus/core@3.8.1 does not satisfy 3.10.2 Cause: two lockfiles. opsimate-docs/package-lock.json was last touched in Sept 2025 and pinned Docusaurus 3.8.1 / React 19.1.0 / TypeScript 5.6.3, while the root lockfile -- the one CI installs and Dependabot maintains -- had moved on. Building from the subdirectory picked up the stale one. This predates the recent dependency work: verified by reproducing `npm ci` against opsimate-docs/package.json + its lockfile at 09b35b3, which fails the same way. The bumps widened the gap, they didn't create it. Nothing caught it because no CI builds the image, and inside the workspace npm walks up and uses the root lockfile, so local runs were unaffected. Delete the nested lockfile and build from the repo root, so the image installs exactly what CI tests. Also: - Add .dockerignore. There wasn't one, so `COPY . .` copied the host's node_modules over the container's, host-platform binaries included. - Add --host 0.0.0.0 to the CMD. Docusaurus binds to localhost, which `docker run -p` cannot reach. - Point Dependabot's docker ecosystem at / and drop link-check's reference to the deleted lockfile. Verified end to end: image builds, ships Docusaurus 3.10.2 / React 19.2.8 / TypeScript 6.0.3 (matching CI), and serves 200 on / and /docs/intro. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThe Docker build moved to the repository root. It installs the root npm workspace and starts Docusaurus on port 3000. CI, Dependabot, documentation instructions, and Docker build exclusions now match the root-level setup. ChangesRepository-root container workflow
Possibly related PRs
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Dockerfile`:
- Line 1: Update the Dockerfile to assign /app ownership to the unprivileged
node user, then add USER node before CMD so the documentation server runs
without root privileges.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: ea40e813-94de-40ed-b393-088bdf5a1e37
⛔ Files ignored due to path filters (1)
opsimate-docs/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (7)
.dockerignore.github/dependabot.yml.github/workflows/ci.yml.github/workflows/link-check.ymlDockerfileREADME.mdopsimate-docs/dockerfile
💤 Files with no reviewable changes (2)
- opsimate-docs/dockerfile
- .github/workflows/link-check.yml
| @@ -0,0 +1,21 @@ | |||
| FROM node:24-alpine | |||
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/sh
set -eu
user="$(docker image inspect opsimate-docs --format '{{.Config.User}}')"
test -n "$user"
test "$user" != "0"
test "$user" != "root"Repository: OpsiMate/documentation
Length of output: 204
🏁 Script executed:
#!/bin/sh
set -eu
echo "== repository files named Dockerfile =="
fd -a 'Dockerfile$' . || true
echo "== Dockerfile contents =="
if [ -f Dockerfile ]; then
cat -n Dockerfile
fi
echo "== package metadata for workspace/start scripts =="
if [ -f package.json ]; then
jq '.workspaces, .scripts' package.json
fi
if [ -d opsimate-docs ]; then
jq '.workspaces, .scripts' opsimate-docs/package.json
fi
echo "== search for USER/opsimate-docs/.docusaurus references =="
rg -n --glob '!node_modules' --glob '!dist' --glob '!build' 'USER\s|\.docusaurus|opsimate-docs' . || trueRepository: OpsiMate/documentation
Length of output: 2891
🌐 Web query:
Docker official Node Dockerfile node:alpine base image USER default CMD node USER node
💡 Result:
In the official Node.js Docker images (including node:alpine), the default user at runtime is root [1][2][3]. Although these images include a pre-created, unprivileged user named node (typically with UID 1000) [4][5][3], the images are designed to start as root by default to provide maximum flexibility during the build and setup process [1]. If the user were set to node by default, any subsequent instructions in a Dockerfile that require root privileges (such as installing OS-level dependencies via apk or apt) would necessitate switching back to root [1]. To follow security best practices and adhere to the principle of least privilege, you should drop privileges after your setup is complete by adding USER node to your Dockerfile, typically just before your CMD instruction [6][7][8][3]. Example workflow in a Dockerfile: FROM node:alpine # Perform root-level operations (e.g., installing dependencies) RUN apk add --no-cache some-package WORKDIR /home/node/app # Switch to the unprivileged node user USER node # Run the application CMD ["node", "index.js"] Alternatively, you can run an existing image as the non-root user without modifying the Dockerfile by using the --user or -u flag in your docker run command: docker run -u "node" node:alpine node index.js [7][3]
Citations:
- 1: why use root as the default user? nodejs/docker-node#1169
- 2: https://stackoverflow.com/questions/54989827/what-is-the-default-user-for-linux-alpine
- 3: https://github.com/nodejs/docker-node/blob/dbab8eb1810567084f4600f711b9f87fd11b3216/docs/BestPractices.md
- 4: https://github.com/nodejs/docker-node/blob/main/Dockerfile-alpine.template
- 5: https://github.com/nodejs/docker-node/blob/c517c39b81a3dd02485433f1fd7818b8220e1ada/22/alpine3.23/Dockerfile
- 6: https://github.com/nodejs/docker-node/blob/main/docs/BestPractices.md
- 7: https://www.docker.com/blog/how-to-use-the-node-docker-official-image/
- 8: https://safeguard.sh/resources/blog/dockerfile-nodejs
Run the documentation server as the unprivileged node user.
Dockerfile does not set USER, so CMD runs under the base image user. Add USER node before CMD after assigning /app ownership to that user.
Proposed fix
WORKDIR /app
...
COPY . .
+RUN chown -R node:node /app
+USER node
EXPOSE 3000🧰 Tools
🪛 Trivy (0.72.0)
[error] 1-1: Image user should not be 'root'
Specify at least 1 USER command in Dockerfile with non-root user as argument
Rule: DS-0002
(IaC/Dockerfile)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Dockerfile` at line 1, Update the Dockerfile to assign /app ownership to the
unprivileged node user, then add USER node before CMD so the documentation
server runs without root privileges.
Source: Linters/SAST tools
The Docker build documented in the README doesn't work
fails at
npm ci:Cause: two lockfiles
There were two —
package-lock.jsonat the root andopsimate-docs/package-lock.json. The nested one was last touched in September 2025 and pinned Docusaurus 3.8.1 / React 19.1.0 / TypeScript 5.6.3, while the root one — the lockfile CI installs and Dependabot maintains — moved on without it.Building from
opsimate-docs/gave Docker a context containing only the stale lockfile, so that's whatnpm ciused.This predates the recent dependency work. I verified rather than assumed: reproducing
npm ciagainstopsimate-docs/package.json+ its lockfile as they stood at 09b35b3 (before any of it) fails identically. The bumps widened the gap; they didn't create it.Two things hid it:
npm ciinsideopsimate-docs/locally works fine, because npm detects the workspace and walks up to the root lockfile. Only Docker, with its narrower build context, saw the stale file.Fix
Delete the nested lockfile and build from the repo root, so the image installs exactly what CI tests. While in here:
.dockerignore. There wasn't one, soCOPY . .copied the host'snode_modulesover the onesnpm cihad just installed — host-platform binaries into a Linux image.--host 0.0.0.0to the CMD. Docusaurus binds to localhost by default, whichdocker run -p 3000:3000cannot reach.dockerfile→Dockerfileat the root, updated the README build instructions, pointed Dependabot's docker ecosystem at/, and droppedlink-check.yml's reference to the deleted lockfile.Verification
Built and ran the image locally:
docker buildGET /GET /docs/introThe image now matches what CI and Vercel test. Also confirmed
npm ci,npm run build, andtsc --noEmitstill pass at the root after removing the nested lockfile.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Improvements