From 310b6aece69a3b48974d1d42a8c7ff95fd3e6b60 Mon Sep 17 00:00:00 2001 From: Tom Brandenburg Date: Sun, 6 Sep 2026 14:02:45 +0200 Subject: [PATCH] fix(docker): add USER root before global npm install in derived images Fixes EACCES when building --docker derived images against base images with a non-root default USER (e.g. node:*-slim images that set USER node). Fixes #15 --- src/docker-image.js | 7 +++++++ test/unit/docker-image.unit.test.js | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/src/docker-image.js b/src/docker-image.js index 98d920b..bad7307 100644 --- a/src/docker-image.js +++ b/src/docker-image.js @@ -81,8 +81,15 @@ function derivedTag(image, version) { } function derivedDockerfile(image, version) { + // Switch to root before the global npm install: many community/base + // images set a non-root default USER (e.g. `node`), which lacks write + // access to the global npm prefix and would otherwise fail with EACCES. + // The derived image is left running as root - restoring the original + // image's default user would require inspecting the base image at build + // time, which is out of scope here. return [ `FROM ${image}`, + "USER root", `RUN npm install -g @tbrandenburg/node-red-cli@${version}`, `ENTRYPOINT ["node", "${SANDBOX_ENTRY_PATH}"]`, "" diff --git a/test/unit/docker-image.unit.test.js b/test/unit/docker-image.unit.test.js index 881c4e3..0c2263d 100644 --- a/test/unit/docker-image.unit.test.js +++ b/test/unit/docker-image.unit.test.js @@ -105,6 +105,11 @@ test("unit: resolveImage installs node-red-cli into a derived image when an expl assert.match(dockerfileContent, /FROM my-registry\/my-image:latest/); assert.match(dockerfileContent, /npm install -g @tbrandenburg\/node-red-cli@1\.2\.3/); assert.match(dockerfileContent, /ENTRYPOINT/); + assert.match( + dockerfileContent, + /USER root\nRUN npm install -g @tbrandenburg\/node-red-cli@1\.2\.3/, + "USER root must appear immediately before the global npm install so non-root base images don't hit EACCES" + ); }); test("unit: resolveImage skips the derived build when it's already cached for that image+version", async () => {