Merged
Size
M
Change Breakdown
Bug Fix80%
Testing10%
Config10%
#26898fix(core): Preserve NODE_PATH for globally installed npm packages in Docker

Global npm packages now work in Docker

A broken environment variable was preventing n8n's task runner from finding globally installed npm packages when running in Docker. The fix preserves the NODE_PATH that Docker sets for external modules.

When running n8n in Docker, globally installed npm packages—installed via npm install -g—could not be found by the task runner. The problem: n8n's internal module loading was overwriting the NODE_PATH environment variable that Docker sets to point to global package directories. Without this path, the task runner's require() calls failed for any externally installed package.

The fix appends the internal module paths to the existing NODE_PATH instead of replacing it entirely. This preserves Docker's configuration while ensuring n8n's own modules remain resolvable. Additionally, NODE_PATH is now explicitly allowed in the task runner launcher's environment, so the variable reaches the subprocess in both internal and external runner modes.

This change resolves issues with Code nodes that depend on globally installed packages, such as word-extractor, when n8n runs inside Docker containers.

View Original GitHub Description

Summary

  • load-nodes-and-credentials.ts was clobbering process.env.NODE_PATH with module.paths, destroying the Docker-configured value needed for globally installed packages (e.g. npm install -g word-extractor)
  • This caused the task runner's require() to fail with "Cannot find module" for any externally installed package in both internal mode (child process) and external mode (sidecar container)
  • Fixes by appending existing NODE_PATH instead of replacing it, and adds NODE_PATH to the runner launcher's allowed-env list

Root Cause

In load-nodes-and-credentials.ts:71, process.env.NODE_PATH was being overwritten with module.paths.join(delimiter). In Docker, the DHI base image sets NODE_PATH=/opt/nodejs/node-v.../lib/node_modules so that globally installed packages are resolvable. This clobbering replaced that path with only n8n internal paths, so when the task runner subprocess inherited NODE_PATH, it could no longer find global packages.

Three prior fix attempts (PRs #24517, #24765, DHI Dockerfile changes) were insufficient because they all assumed NODE_PATH would survive through to the task runner — but this clobbering happened after Docker ENV was set and before the task runner was spawned.

Changes

FileChange
packages/cli/src/load-nodes-and-credentials.tsAppend existing NODE_PATH instead of replacing
docker/images/runners/n8n-task-runners.jsonAdd NODE_PATH to allowed-env for external runner mode
packages/@n8n/task-runner/.../require-resolver-global-modules.test.tsNew: 5 regression tests for NODE_PATH + _initPaths behavior
packages/cli/src/__tests__/load-nodes-and-credentials.test.tsNew: 2 tests for NODE_PATH preservation logic

Test plan

  • New regression test suite (5 cases) verifies require() behavior with/without NODE_PATH
  • NODE_PATH preservation unit tests verify append-not-clobber logic
  • Existing load-nodes-and-credentials tests still pass (30/30)
  • Manual Docker verification: build custom image with npm install -g <package>, confirm Code node can require() it

Closes https://github.com/n8n-io/n8n/issues/24191

🤖 Generated with Claude Code

© 2026 · via Gitpulse