Skip to content

fix: build Docker image from the workspace root - #118

Merged
idanlodzki merged 1 commit into
mainfrom
fix/docker-workspace-build
Aug 2, 2026
Merged

fix: build Docker image from the workspace root#118
idanlodzki merged 1 commit into
mainfrom
fix/docker-workspace-build

Conversation

@idanlodzki

@idanlodzki idanlodzki commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

The Docker build documented in the README doesn't work

cd opsimate-docs && docker build -t opsimate-docs .

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 @docusaurus/core@3.10.2
Invalid: lock file's react@19.1.0 does not satisfy react@19.2.8
Invalid: lock file's @vercel/analytics@1.5.0 does not satisfy @vercel/analytics@2.0.1

Cause: two lockfiles

There were two — package-lock.json at the root and opsimate-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 what npm ci used.

This predates the recent dependency work. I verified rather than assumed: reproducing npm ci against opsimate-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:

  • No CI builds the image, so nothing exercised this path.
  • Running npm ci inside opsimate-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:

  • Added .dockerignore. There wasn't one, so COPY . . copied the host's node_modules over the ones npm ci had just installed — host-platform binaries into a Linux image.
  • Added --host 0.0.0.0 to the CMD. Docusaurus binds to localhost by default, which docker run -p 3000:3000 cannot reach.
  • Moved dockerfileDockerfile at the root, updated the README build instructions, pointed Dependabot's docker ecosystem at /, and dropped link-check.yml's reference to the deleted lockfile.

Verification

Built and ran the image locally:

docker build succeeds
Docusaurus in image 3.10.2 (was 3.8.1)
React in image 19.2.8 (was 19.1.0)
TypeScript in image 6.0.3 (was 5.6.3)
GET / 200
GET /docs/intro 200

The image now matches what CI and Vercel test. Also confirmed npm ci, npm run build, and tsc --noEmit still pass at the root after removing the nested lockfile.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a root-level Docker setup for building and serving documentation on port 3000.
    • Excluded unnecessary files from Docker build contexts for more efficient builds.
  • Improvements

    • Updated CI, link checking, dependency monitoring, and Docker instructions to work from the repository root.
    • Simplified documentation build instructions using the shared workspace lockfile.

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>
@vercel

vercel Bot commented Aug 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
documentation-opsimate-docs2 Ready Ready Preview Aug 2, 2026 6:04pm

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The 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.

Changes

Repository-root container workflow

Layer / File(s) Summary
Root Docker build
Dockerfile, .dockerignore, README.md
The repository adds a root Dockerfile, excludes dependency and generated files from the build context, and updates Docker instructions to use the root workspace.
Automation root alignment
.github/workflows/link-check.yml, .github/dependabot.yml, .github/workflows/ci.yml
Documentation checks and Docker dependency updates now use the repository root. The CI comment references the root Dockerfile.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@idanlodzki
idanlodzki merged commit 2297e32 into main Aug 2, 2026
5 of 6 checks passed
@idanlodzki
idanlodzki deleted the fix/docker-workspace-build branch August 2, 2026 18:05

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 79f70ac and 437d112.

⛔ Files ignored due to path filters (1)
  • opsimate-docs/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (7)
  • .dockerignore
  • .github/dependabot.yml
  • .github/workflows/ci.yml
  • .github/workflows/link-check.yml
  • Dockerfile
  • README.md
  • opsimate-docs/dockerfile
💤 Files with no reviewable changes (2)
  • opsimate-docs/dockerfile
  • .github/workflows/link-check.yml

Comment thread Dockerfile
@@ -0,0 +1,21 @@
FROM node:24-alpine

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 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' . || true

Repository: 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:


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

Learn more

(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

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.

1 participant