TypeScript

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.

Mahdi Ben RhoumaMahdi Ben Rhouma
7 min read

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:

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

dockerfile
RUN node --version && npm --version

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

dockerfile
FROM node:24-bookworm
RUN node --version && npm --version

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

dockerfile
FROM node:24-bookworm AS dependencies
WORKDIR /app
COPY package.json package-lock.json ./
RUN node --version && npm --version && npm ci

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

bash
npm view npm@10.0.0 engines --json

Compare 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 caseResult
None: Node 16.20.2 requests npm 10.0.0, engine-strict falseInstallation exits with EBADENGINE
Only the requested target becomes npm 9.9.4Installation succeeds; the installed CLI prints 9.9.4
Only the runtime becomes Node 26.7.0, still requesting npm 10.0.0Installation 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

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote

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.