Fix Docker npm EBADENGINE: Unsupported engine
Read the Required and Actual lines, distinguish npm self-upgrades from application installs, and stop a floating npm release breaking an unchanged Dockerfile.
TL;DR#
When a Docker build fails at npm i npm@latest -g with EBADENGINE, read the package named in the error. If it is npm itself, the requested npm release needs a Node version outside the image's installed range. Change the Node runtime or select an npm release that supports it. For a maintained build, use a supported official Node image and retain its bundled npm unless the application requires a deliberate override.
The reported Docker failure installed Node 16.20.2, then attempted to replace npm 8.19.4 with npm 10.0.0. The failing command ran before the application's dependency installation:
npm ERR! code EBADENGINE
npm ERR! engine Unsupported engine
npm ERR! engine Not compatible with your version of node/npm: npm@10.0.0
npm ERR! notsup Required: {"node":"^18.17.0 || >=20.5.0"}
npm ERR! notsup Actual: {"npm":"8.19.4","node":"v16.20.2"}This npm compatibility check was reproduced on native Windows with the same Node and npm versions on 28 October 2026. The Linux package installation and complete Docker build were not reproduced; the test isolates the npm operation responsible for the error.
Why the npm upgrade rejects the image#
The npm 10.0.0 release notes explicitly removed support for Node 14 and 16. Its declared Node range is ^18.17.0 || >=20.5.0. The error's Required line is the target npm package's contract; Actual describes the runtime and CLI performing the installation. It is not asking you to change an application's dependency version with the same number.
The source Dockerfile combined a fixed Node installation line with a moving npm target. A new npm release could therefore change the result without a Dockerfile edit. The application lock file cannot pin that separate global npm installation: npm's lock-file documentation describes the dependency tree generated for the project.
There is also a distinction between an ordinary dependency's engine warning and updating npm itself. The npm 8.19.4 installer source performs an explicit engine check for global npm installation. In the native reproduction, setting engine-strict to false still produced the failure above. Advice to disable that setting does not repair this case.
Fix the runtime and npm pair#
1. Print versions inside the failing build stage#
Add the following diagnostic instruction immediately before the npm upgrade. These values must come from the stage that fails, not from your laptop's terminal:
RUN node --version && npm --versionFor a multi-stage Dockerfile, inspect each relevant base. Docker's FROM reference explains that another FROM starts a new build stage. Updating the final runtime image does not update an earlier dependency-building stage derived from a different base.
Read the first failed command as well as the last error line. In the source incident, the npm self-upgrade followed a successful Node installation. Changing application imports, removing application packages or rebuilding a framework's cache cannot alter the runtime executing that earlier command.
2. Prefer an official supported Node image#
If the image is principally for a Node application, start with an official Node image instead of adding Node and then replacing npm unconditionally. For example, this minimal Dockerfile checks the supplied pair:
FROM node:24-bookworm
RUN node --version && npm --versionThe official image's Dockerfile installs Node and smoke-tests both commands. The Node release table identifies supported LTS branches; check it when choosing your application's runtime. Compatibility with npm alone is not a reason to keep an end-of-life Node branch.
For an existing npm project with a committed lock file, a dependency-installation stage can then use the bundled CLI directly:
FROM node:24-bookworm AS dependencies
WORKDIR /app
COPY package.json package-lock.json ./
RUN node --version && npm --version && npm ciThis is a dependency stage, not a complete deployment recipe: retain the application's appropriate build, source-copy and runtime steps. If the original Ubuntu image also supplied browsers or other operating-system packages, account for those requirements explicitly when changing the base. The source example installed browsers; replacing its base does not automatically recreate those packages.
3. Override npm only when you have a specific requirement#
Inspect the requested release's metadata before installing it. For the historical failing release, the command is:
npm view npm@10.0.0 engines --jsonCompare that Node range with the version printed in the failing stage. If an upgrade is necessary, choose an exact npm version supported by the selected Node runtime and test the application with the resulting pair. Installing the latest npm on every build adds another moving input even when the application dependency tree is locked.
The source answer's npm downgrade is historically valid: npm 9.9.4 metadata includes Node 16.20.2 in its supported range. It can explain or unblock a controlled legacy reproduction, but it does not restore Node's maintenance status. Do not start a new production image with that old pair just because the package manager installation succeeds.
Verify the change before blaming application dependencies#
Keep the failing log, rebuild after the runtime or npm-target change, and compare the same stage's version output. Success means the npm installation completes and the CLI executes under that runtime. Then run the application's normal installation, tests and build; a compatible package manager is only the prerequisite for those checks.
For the native reproduction, installation used an isolated prefix inside the test directory rather than replacing the workstation's npm. The installer remained npm 8.19.4 throughout these checks:
| Change from the failing case | Result |
|---|---|
| None: Node 16.20.2 requests npm 10.0.0, engine-strict false | Installation exits with EBADENGINE |
| Only the requested target becomes npm 9.9.4 | Installation succeeds; the installed CLI prints 9.9.4 |
| Only the runtime becomes Node 26.7.0, still requesting npm 10.0.0 | Installation succeeds; the installed CLI prints 10.0.0 |
The same old runtime also rejected the current latest npm during the test. That is why an old answer's exact compatibility numbers should not be applied to whatever the latest tag resolves to today: inspect the metadata for the release actually being selected. These native checks establish the npm mechanism, not the behaviour of the untested Linux browser packages in the original image.
If installation proceeds but the framework then reports its own minimum Node version, follow that separate requirement. The Next.js minimum-Node diagnosis shows how to identify the runtime the framework actually starts with. npm accepting a runtime does not imply every application dependency accepts it.
Avoid the three misleading fixes in this log#
The NodeSource deprecation warning in the original question is a separate installation-method warning. It does not change the explicit npm engine range. Maintaining an Ubuntu-based image may require updating the package repository instructions too, but diagnose each message against the command that emitted it.
Do not add force to install an incompatible npm. npm's configuration reference documents that force can override engine protections. Overriding the refusal does not add the runtime features required by the package you install. An installation that exits successfully can still leave a CLI that cannot run correctly.
Likewise, legacy-peer-deps addresses a different contract. The npm configuration documentation describes ignoring peer dependencies when constructing a package tree. It does not upgrade Node. If the next failure is a React peer conflict, use the conflicting peer dependency diagnosis and inspect the dependency requiring the incompatible React range.
Finally, do not assume every old-runtime problem prints this error. An ordinary package installation may only warn, leaving the runtime to reject newer JavaScript syntax when the tool starts. The TypeScript unexpected-token diagnosis covers that later failure. Here, npm stops during its own global upgrade: repair the pair at that point, preserve the application lock file, and verify each later build step on its own evidence.
Further reading: Fix ERR spawn EINVAL from npx install-peerdeps on Windows. Also see Fix tar EPERM: operation not permitted, futime.
Frequently Asked Questions
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
One email a month — the latest explained errors
What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.
Continue Reading
Fix esbuild spawnSync ETXTBSY in Docker: Node 20.3 io_uring
Why npm install of esbuild started failing with ETXTBSY on node:20.3 images in June 2023, what changed in libuv and Docker since, and the build fix that holds today.
Fix npm EACCES: permission denied on package-lock.json
Separate a local lockfile permission failure from cache and global-install errors, then fix the owner without changing the dependency tree.
Fix EACCES: permission denied, mkdir node_modules/.cache
Reproduced in Docker: why the cache directory cannot be created, the one-line Dockerfile move that fixes it, and why an old volume keeps the error alive after a rebuild.


