
Node 20 Is EOL: Find Every Runtime Pin Before a Failed Deploy
Node 20 is EOL. Audit package metadata, CI, Docker, and host settings, choose a supported target, then verify every runtime pin before a deploy fails.
Node 20 is already past its upstream support window
Node.js lists version 20 as end-of-life on April 30, 2026. The official release schedule is the reason to plan the upgrade, but an ordinary production application has a second problem: the actual runtime is usually declared in more than one place. A package.json engine, a Docker base image, a CI setup action, a deployment setting, and a version-manager file can disagree.
Platform deadlines turn that mismatch into a release risk. Vercel says it will disable Node 20 for Builds and Functions on October 1, 2026; existing deployments keep serving, but a new deployment using Node 20 will fail. Its current changelog also gives a command for locating affected Vercel projects.
The safe move is an audit first, then one deliberate target version across local development, CI, containers, and the host. Do not change a single engines field and call the job done.
Choose a target before touching the pins
The release schedule shows Node 22 in Maintenance LTS and Node 24 in Active LTS. Pick the version your framework, deployment platform, native dependencies, and operating systems support. Node's v22 to v24 migration guide is useful if you are moving from an already-supported Node 22 line to Node 24. If Node 20 is your starting point, treat the test suite and dependency install as the evidence for your own compatibility.
Do not use a Docker image as a way to avoid the upgrade. Vercel notes that its Project Settings version does not apply to container deployments, so a container can still run Node 20 after the platform deadline. That only changes who owns the patching responsibility.
Find every runtime declaration
Start with a repository search. The command below is illustrative. Run it from the repository root and adapt the file list to your tooling.
rg -n --hidden --glob '!node_modules' --glob '!.git' \
'node:20|NODE_VERSION|node-version|volta|engines|setup-node|FROM node:' \
package.json package-lock.json pnpm-lock.yaml yarn.lock \
.nvmrc .node-version .tool-versions .github Dockerfile docker-compose.yml \
.gitlab-ci.yml .circleci vercel.json netlify.toml 2>/dev/nullThen inspect every result in context. An engines field may guide a host but not a local shell. A setup-node action may control CI while a Docker build still gets its runtime from FROM node:20. A lockfile does not normally pin Node, but a package manager can refuse an unsupported runtime or install different native binaries under a new ABI.
The GitHub Actions runner image history makes this worth checking explicitly. A 2026 runner-images issue reported that ubuntu-latest still defaulted to Node 20 while macOS and Windows latest images used Node 22. Do not assume the runner's default version is your policy. Pin the runtime in the workflow.
# .github/workflows/ci.yml
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm
- run: node --version
- run: corepack pnpm install --frozen-lockfile
- run: corepack pnpm testThis is an illustrative GitHub Actions pattern, not a claim about your package manager or test script. Keep the version in one documented place where possible, and make CI print node --version before installation so a failed build shows the runtime that actually ran.
Update the container as a separate change
Search every Dockerfile and every stage. A multi-stage build can compile with one Node release and run with another. That can be intentional, but it should be reviewed deliberately.
# Before
FROM node:20-alpine AS build
# After, if this image and its native dependencies support Node 24
FROM node:24-alpine AS buildDo not replace an image tag without rebuilding from a clean dependency install. Native modules, optional platform packages, and postinstall scripts are the usual places where an apparently small runtime upgrade becomes a real build change. Keep the lockfile reviewable and test the same operating system and architecture that deployment uses.
Check the host's separate policy
Vercel provides `vercel project ls --update-required` to list projects that still use a deprecated Node release. Use that platform-specific check when Vercel is in scope. Heroku now displays a Node 20 EOL warning during builds, while existing apps can still build and run. Netlify has ended Node 20 support for Build Plugins, with plugin execution changing to its default Node 22 for older build runtimes. These are different behaviors, so do not copy one host's deadline into another host's runbook.
Make the upgrade prove itself
Use a short branch or a controlled deployment. Change the intended runtime pins, install from a clean state, run the normal lint, typecheck, test, and build commands, then exercise a real request path. For a service, include a startup check that records process.version without exposing secrets. For a job, run the task that loads any native dependency. For a container, build and run the image rather than only compiling the application on the host.
Keep a rollback that is honest about security. Reverting to Node 20 may restore service while returning to an unsupported runtime. A safer rollback is the last known-good build on Node 22 or 24, which is why a staged upgrade and a preserved lockfile matter.
A practical Node 20 exit checklist
1. Inventory runtime pins in package metadata, local version files, CI, Dockerfiles, and host settings.
2. Choose Node 22 or Node 24 from the actual compatibility requirements, not from the first file you find.
3. Update all build and runtime stages together, while keeping intentional differences documented.
4. Install cleanly and test the production operating system and architecture if native packages are involved.
5. Run the platform's own affected-project check and deploy a staging build before the next routine release.
6. Record the selected runtime and the command that verifies it, so the next dependency or host upgrade does not silently restore Node 20.
Node 20 EOL is not one edit. It is an inventory problem with a deadline. Make the effective runtime visible in every environment, test the new one on the path users actually take, and remove the old pins before a platform turns an ordinary release into an outage.