Skip to content

--docker: derived-image build breaks when base image overrides NPM_CONFIG_PREFIX (hardcoded /usr/local assumption) #20

Description

@tbrandenburg

Problem

derivedDockerfile() / SANDBOX_ENTRY_PATH (src/docker-image.js) hardcode the assumption that npm's global install prefix is /usr/local:

const SANDBOX_ENTRY_PATH =
  "/usr/local/lib/node_modules/@tbrandenburg/node-red-cli/bin/node-red-cli-sandbox-entry.js";

Any community/base image that overrides npm's global prefix via NPM_CONFIG_PREFIX (or an .npmrc prefix setting) — increasingly common for rootless dev-container images that want npm install -g to work without root — breaks this silently: npm install -g @tbrandenburg/node-red-cli@<version> succeeds (exit 0, no error in the build log) but installs to the actual configured prefix, not /usr/local. The subsequent ENTRYPOINT ["node", "<SANDBOX_ENTRY_PATH>"] then fails at container-run time with MODULE_NOT_FOUND for a path that was never populated.

This is unrelated to and independent of #15 (which was about the build failing outright with EACCES when the default prefix is non-writable for a non-root user). Here the build succeeds cleanly; only the runtime docker run fails, and only for images that redirect the prefix rather than just changing the default user.

Reproduction

$ docker run --rm --entrypoint sh <some-image-with-custom-npm-prefix> -c 'npm config get prefix'
/home/node/.npm-global   # (example — anything other than /usr/local)

Building a derived image from such a base and running it:

$ node-red-cli flows.json calculate --docker <that-image> --set x=4 --set y=5
node:internal/modules/cjs/loader:1568
  throw err;
Error: Cannot find module '/usr/local/lib/node_modules/@tbrandenburg/node-red-cli/bin/node-red-cli-sandbox-entry.js'
...
code: 'MODULE_NOT_FOUND'

Confirmed directly by inspecting the derived image's filesystem:

$ docker run --rm --entrypoint sh <derived-image-tag> -c \
    'test -f /usr/local/lib/node_modules/@tbrandenburg/node-red-cli/bin/node-red-cli-sandbox-entry.js && echo HAS || echo MISSING'
MISSING

while the base image itself reports:

$ docker run --rm --entrypoint sh <base-image> -c 'npm config get prefix; env | grep -i npm'
/home/node/.npm-global
NPM_CONFIG_PREFIX=/home/node/.npm-global

Root cause

buildRunArgs's counterpart on the build side, derivedDockerfile(), does:

FROM <image>
USER root
RUN npm install -g @tbrandenburg/node-red-cli@<version>
ENTRYPOINT ["node", "/usr/local/lib/node_modules/@tbrandenburg/node-red-cli/bin/node-red-cli-sandbox-entry.js"]

USER root (added for #15/#16) makes the install writable, but does not change where npm installs to — that's controlled entirely by NPM_CONFIG_PREFIX/.npmrc, which persists across USER switches within the same build (env vars and .npmrc files aren't user-scoped). If the base image sets a non-default prefix, the package lands there instead of /usr/local, and the hardcoded SANDBOX_ENTRY_PATH constant used for both the test -f existence check (hasSandboxEntry()) and the final ENTRYPOINT no longer matches reality.

Expected behavior

The derived-image build and entrypoint should work correctly regardless of the base image's configured npm global prefix — generic compatibility with any community/base image was the explicit design goal of the --docker <image> derived-build path (see #15/#16).

Proposed fix

In derivedDockerfile(), before RUN npm install -g ..., explicitly reset the install location to a known value so SANDBOX_ENTRY_PATH is guaranteed correct regardless of the base image's own npm configuration, e.g.:

FROM <image>
USER root
ENV NPM_CONFIG_PREFIX=/usr/local
RUN npm install -g @tbrandenburg/node-red-cli@<version>
ENTRYPOINT ["node", "/usr/local/lib/node_modules/@tbrandenburg/node-red-cli/bin/node-red-cli-sandbox-entry.js"]

(Exact mechanism open to the implementer — an explicit ENV NPM_CONFIG_PREFIX=/usr/local override is the most direct approach; alternatively, resolve the actual post-install path via npm root -g in a build step and pass it through to ENTRYPOINT, though that requires shell-form entrypoint or a wrapper script instead of the current exec-form array.)

Scope

Affects --docker <image[:tag]> (derived-image mode) for any base image that overrides npm's default global prefix — a real and growing pattern among rootless/dev-container-style community images, not specific to any one image.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions