Why a Backstage Node.js upgrade needs its own plan
Backstage supports exactly two adjacent even-numbered Node.js releases, and as of Backstage 1.46.0 these are Node.js 22 and 24, according to the project's versioning policy. A platform team running its app on an older Node.js release should treat the day that release leaves the supported pair as a dated migration, not a line item in the next routine bump. The runtime move touches your container base image, CI pipeline, and any native modules, none of which yarn backstage-cli versions:bump updates for you.
The routine upgrade and the runtime upgrade fail in different ways. A versions bump moves the @backstage packages together and surfaces template changes, so most breakage shows up as TypeScript or test failures in code you already maintain. A Node.js change can instead break at install time, at build time in a native dependency, or only in the production image, which may never run the same version as your laptop. Schedule the Node.js move as its own change, with its own test run and rollback path, so a runtime problem is not mixed up with a framework problem when something breaks.
Backstage ships a main release line monthly and a next release line weekly, so the framework moves faster than most platform teams plan for. Per the same policy, high and critical severity vulnerabilities are backported to releases from the last 6 months where feasible, and other security fixes may be backported. Staying on a Node.js release outside the supported pair means you are no longer in the path where the project's fixes are checked against your runtime. Confirm with the project's release page which Backstage line you run, and check the policy for the Node.js versions that line supports before you set a deadline.
What changes when the Node.js baseline moves
This table compares each layer of a Backstage app that depends on Node.js and says what changes, what breaks if it lags, and what to do. Read it top to bottom before you change the runtime, because the Backstage release line you run decides which Node.js releases are valid.
| Layer | What changes | Action needed |
|---|---|---|
| Supported Node.js pair | Backstage supports exactly two adjacent even-numbered Node.js releases. As of Backstage 1.46.0 these are Node.js 22 and 24. | Confirm the current pair in the versioning policy before choosing a target runtime. |
| Older release dropping out | When a newer even-numbered release enters the pair, the oldest one leaves support. Apps still on it receive no compatibility assurance. | Plan the move while the old release is still in the pair, not after it has dropped out. |
| Backstage release line | The Backstage version you run determines which Node.js releases it supports, so the runtime cannot be chosen independently of the framework. | Check the Node.js requirement for your target Backstage version before you bump packages. |
| Docker base images | Your container image pins a Node.js major version. It must match the supported runtime, or the image ships an unsupported one. | Change the base image tag in the same change set as the runtime, then rebuild and scan the image. |
| CI runner versions | Build and test jobs run on the Node.js version set in CI. A mismatch can pass locally and fail in the pipeline, or the reverse. | Pin the same major version in CI, local tooling and the Dockerfile, and run the full test suite on it. |
| Native module rebuilds | Packages with compiled native code must be rebuilt for the new Node.js major version. Prebuilt binaries may not exist for it. | Reinstall dependencies from a clean lockfile on the new runtime and confirm each native package installs and loads. |
| Internal plugins and scripts | Custom plugins, scripts and build tooling may use Node.js features or packages tied to the old runtime. | Run your plugin tests and build scripts on the target Node.js version before deployment. |
How to pick the target Node.js release
The target Node.js release is set by the Backstage version you are moving to, not by the Node.js version you would like to run. The Backstage versioning policy supports exactly two adjacent even-numbered Node.js releases at any given time. As of Backstage 1.46.0, those were Node.js 22 and 24. Start by opening the Backstage release and versioning policy and confirm which pair applies to your target version, because the pair moves over time. Then check the Backstage releases page for the exact version you plan to adopt. The policy describes the pattern, but the release notes for that version are what tell you whether the framework code you run has been tested on the Node.js release you are choosing. If the notes are silent on Node.js, treat that as a question for the project or your own test run, not as confirmation.
Decide whether to move Node.js first or together with the Backstage bump. If your app is on a current release line and the Node.js release you need is already in the supported pair, moving Node.js first on the current version is usually the lower-risk option, because it isolates runtime failures from framework changes. Test the app, backend plugins, Dockerfile base image and CI image on the new runtime before touching the @backstage package versions. If the app is several releases behind, or the target version also changes plugin APIs, moving Node.js with the larger upgrade may be more efficient, but failures then have more possible causes. In that case, record a known-good state first so you can roll back one variable at a time. Ask your team to name the single owner of the runtime change, since Node.js and framework changes often surface in the same test run.
What to prepare before changing the runtime
A Node.js change touches more than the application code. The database, plugin inventory, build baseline, environments, container images and rollback path all determine whether you can move forward and return to the current state if something fails. Prepare each of these before anyone edits a version number.
Restore a PostgreSQL backup
Take a fresh backup of every Backstage plugin database in PostgreSQL, then restore it into a scratch instance and start the backend against it. A backup that has never been restored tells you little. Confirm that the catalog, scaffolder and auth databases all come back, and time the restore so you know how long a rollback would take. Run this test on the current Node.js release so any failure is clearly attributable to the upgrade that follows.
Inventory custom plugins and native modules
List every internal plugin, forked package and dependency that compiles native code, such as addons built with node-gyp. Native modules are the most common reason a Node.js switch fails at install or at runtime, because prebuilt binaries may not exist yet for the new release. For each item, record the owner, the source location and whether the vendor or maintainer has confirmed support for the target Node.js version.
Pin the lockfile and record a build baseline
Commit the current yarn.lock and record the exact Node.js version, package manager version and build output from a clean install. Store the outputs of the type check, lint, test and build steps. When the runtime changes, any difference from this baseline is caused by the new runtime or by a dependency it pulls in, which narrows the search for regressions.
Build a production-like test environment
Stand up a non-production Backstage instance with the same configuration, authentication providers, integrations, database version and network path as production. Load a representative catalog and run the scaffolder against a test repository. Confirm which external endpoints it reaches, because a test environment without the same integrations will pass checks that production later fails.
Update base images and CI runners
Change the Node.js version in your Dockerfile base image and in the CI runner definitions in the same change set, so the image and the pipeline build against one runtime. Mismatched versions across local development, CI and the container are a frequent source of failures that only appear after deployment. Confirm the Node.js version your base image provides and pin it by tag or digest.
Keep a rollback image of the current release
Retain the container image and the Git commit that run the current Node.js release, tagged so it cannot be overwritten or garbage-collected. Confirm that the rollback path works with the database state after migrations have run, since some schema changes may not be reversible. Write down who can trigger the rollback and how long they expect it to take before the upgrade window opens.
The upgrade sequence with checks and rollback points
This sequence moves a Backstage app onto a newly supported Node.js release in a controlled order, so each change is verified before the next one builds on it. Every step ends with a check and a point you can return to if it fails.
Confirm the target Node.js release
Read the Backstage release and versioning policy and the release notes for the Backstage version you run or plan to move to. Confirm which two even-numbered Node.js releases that version supports, and record the current release in your app and the target release. Note the current lockfile, the current container image tag and the CI runner image so you have a documented starting point. The step is done when the team agrees on the target Node.js release in writing and the rollback references are recorded in the change ticket.
Set the target Node.js version in tooling
Change the Node.js version in the places developers and pipelines read it: the version pin used by local tooling, the engines field in your package files, and the CI job images. Keep the change in a single branch so it can be reverted as one commit. Ask each developer to confirm their local runtime matches before they pull the branch. The check is that the same Node.js version reports from a developer laptop and from the CI job that builds the app.
Reinstall dependencies and rebuild native modules
Remove existing installed modules, reinstall dependencies with the lockfile under the target runtime, and rebuild any package that compiles native code. Packages that bind to native libraries are the usual source of failures after a runtime change, so review the install log for compile errors and for packages that skip their build step. Commit the regenerated lockfile only after the install completes cleanly. The check is a clean install with no build errors and a lockfile diff that contains only the changes you expect.
Update the container base image
Change the base image in the Dockerfile for the backend and any frontend build stage to the image that carries the target Node.js release. Build the image under a new tag and keep the previous tag in your registry untouched. Confirm that the image contains the expected runtime and that system libraries required by native modules are present. The check is that the new image builds, starts and reports the target runtime, and the previous tag still exists so you can redeploy it without a rebuild.
Run build and test suites
Run the full build, type checks, linting and unit tests in CI using the new runner and image. Then run any end-to-end tests your team maintains against a local instance started from the new image. Compare the results with the last passing build on the previous Node.js version so new failures stand out from existing ones. The check is a green pipeline on the branch, with any skipped or newly failing tests explained in the change record before the branch moves forward.
Deploy to staging and verify core paths
Deploy the new image to a staging environment that uses the same database, cache and identity provider configuration as production, with only non-production credentials. Verify the catalog loads and entities refresh, sign-in works through the configured provider, and a software template runs end to end through the scaffolder. Check the backend logs for new warnings or errors after startup. The check is a written test record covering each path, with the tester, the image tag and the time of each run.
Promote to production with a rollback plan
Promote the image tag that passed staging during a window your change process approves, and keep the previous image tag ready to redeploy. Watch health checks, error rates and the catalog and scaffolder paths for a period your team defines in advance. If a check fails, redeploy the previous image tag and restore the previous lockfile and CI settings from the commit history, then record the failure for the next attempt. The step is done when the production checks pass and the previous tag and commit are retained for the agreed period.
Native modules and when the older Node.js release is dropped
Native addons and packages that ship compiled binaries are the most common cause of failures during a Node.js runtime move. Pure JavaScript packages usually run unchanged on a new runtime, but anything built against the V8 or Node.js C++ interfaces must be rebuilt for the new major version, and some older prebuilt binaries simply do not exist for it. In a Backstage app these are often pulled in indirectly, through database drivers, SQLite for local development, image or PDF processing, or authentication libraries, so they may not appear in your own package.json. Before changing the version in your Dockerfile, run a clean install with the target Node.js release in a scratch branch and read the install log for node-gyp builds, gyp ERR! messages and packages that fall back to compiling from source.
To find every compiled package, search the lockfile and the installed tree rather than relying on memory. Look for packages that declare an install or postinstall script calling node-gyp or prebuild-install, and for .node files under node_modules. Then check each one's release notes or issue tracker for the target major version. For each hit, decide whether to upgrade to a release that ships binaries for the new runtime, replace the package with a pure JavaScript alternative, or rebuild it in your own image with the matching build toolchain. Record the outcome per package, because the same list will matter again at the next runtime move and during your supply chain review.
Plan the runtime move before the older Node.js release stops being supported by Backstage, not after. Backstage supports two adjacent even-numbered Node.js releases at a time, and the project removes the older one on its own schedule. Confirm the current pair and the removal timing in the Backstage release and versioning policy and in the release notes for the Backstage version you run, since these can change between releases. Once you know the removal window, schedule the move ahead of it, and keep the native-module review and a test cycle inside that window so that a dropped runtime does not force an emergency upgrade alongside your normal monthly Backstage release.
What makes a Node.js upgrade take longer
Why do custom plugins make a Node.js upgrade take longer?
Custom plugins add the most unpredictable work, because each one is a separate codebase that must build and run on the new Node.js release. Plugins with native dependencies, such as packages that compile C or C++ code during installation, need prebuilt binaries or a rebuild for the new runtime, and some of those packages lag behind new Node.js releases. The effort grows with the number of plugins, the number of native modules inside them, and whether anyone on your team still owns each plugin. Before you start, inventory every plugin's dependency tree and confirm with each maintainer, or with your own team, which packages need recompilation.
How do Docker images and CI pipelines change the upgrade effort?
Every Docker image and CI pipeline that pins a Node.js version is a separate change to make and verify. The effort rises when the backend image, the frontend build, the database migration job and any scheduled jobs each use their own base image, and when some pipelines run on self-hosted runners with an older Node.js installed. Teams with one shared base image and a single CI template usually finish faster. Ask for a list of every place the Node.js version is set, including .nvmrc, engines fields, Dockerfiles and runner images, so no environment is left on the old release.
Does being many Backstage releases behind change the Node.js work?
Yes. An app that is close to the current Backstage release needs a small change set, while an app that is far behind also has to absorb framework changes, deprecated packages and dependency updates before the runtime change can be tested cleanly. Node.js compatibility is only one layer, so the two upgrades can overlap and make failures harder to attribute. If the gap is large, it is often better to bring the app current first and then move the runtime. Confirm the current Backstage version and the supported Node.js versions in the versioning policy before you plan the order of work.
Why do database and cache clients need matching versions?
Database and cache drivers, such as the PostgreSQL client used through Knex and any Redis client, contain native or version-sensitive code that can behave differently on a new Node.js release. A mismatch can surface as connection failures, slow queries or errors only under load, which are harder to find than build failures. The effort depends on how many backend plugins use a database, whether you run a managed database that has its own client requirements, and whether you use a shared cache. Ask your team to test migrations and cache reads and writes against a copy of production-like data, not only to confirm that the app starts.
How does the depth of staging tests affect the timeline?
Staging tests set how much evidence you collect before production, and that directly controls the duration. A smoke test that confirms the portal loads and the catalog ingests can be run in a day or two of engineering time, while a full regression run covering authentication, scaffolder actions, TechDocs builds, search and custom plugins takes longer to script, run and triage. Organizations with change management or evidence requirements also need test results recorded and approved. Ask for the exact test scope, pass criteria and rollback steps in the written scope before work begins.
Can our own team handle a Node.js upgrade without a partner?
Often yes, if the team has someone who owns the Backstage app, can read dependency and build errors, and has time for test cycles. Effort rises sharply when no one owns the custom plugins, when several environments need changes, or when the portal is business critical and changes need a formal approval path. A partner can take on a fixed-scope piece, such as the native module audit, the image and pipeline changes or the regression test plan, while your team keeps ownership of the app. Ask any provider to split the quote by plugin inventory, environment changes, testing and rollback so you can compare it with your own estimate.
What should we ask for in a quote for a Node.js upgrade?
Ask for a written scope that lists the Backstage version and the target Node.js release, every plugin with native dependencies, every image and pipeline to change, the database and cache clients in use, and the staging tests that must pass. Ask which work is one-time, such as the upgrade and test plan, and which recurs, such as the next release cycle or ongoing support under a retainer. Ask what is excluded, including fixes to custom plugins that break for reasons unrelated to Node.js. DeployOpen plans and delivers this work as fixed-scope engagements, with the scope and timeline agreed in writing before work starts.
How do we check that native modules still build after the upgrade?
Start by listing every dependency that compiles native code during install, then run a clean install on the target Node.js release in a build environment that matches your CI runners. Check that each module loads at runtime, not only that the install succeeds, because some modules fail only when the code path runs. Confirm that your container build uses the same operating system and build tools as your image, since a binary built on one base image can fail on another. Keep a short record of each module, its version and its test result so the next upgrade starts from evidence.
How DeployOpen scopes and delivers a Backstage runtime upgrade
DeployOpen approaches Node.js upgrades for Backstage as a scoped change to your app, container images and CI pipeline, not as a version number swap. We agree the scope in writing first, then build, test, document and hand over the change in your own infrastructure, with optional ongoing support available as a retainer.
How we scope
- We inventory your Backstage app, backend plugins, Dockerfiles and CI jobs that pin a Node.js version.
- We confirm which Node.js release your target Backstage version supports before agreeing dates.
- We list native modules and internal plugins that need rebuild or compatibility checks in the written scope.
- Engagements are fixed-scope, so the agreed deliverables and timeline are documented before work starts.
How we build
- We move the engines field, .nvmrc, base images and CI runner versions together in one tracked change.
- We rebuild native dependencies on the target Node.js release and record any package that needs replacing.
- We run the existing test suite, build and smoke checks on the new runtime before merging.
- We keep the change set separate from any Backstage version bump so failures can be traced to one cause.
How we hand over
- You receive the updated repository, container build definitions and CI configuration with a changelog of runtime changes.
- We document the Node.js version policy, the planned date to move to the next release, and the rollback steps.
- Your engineers walk through the verification results and the list of open items with us before sign-off.
- We confirm with your team which owner tracks Node.js end-of-life dates after the engagement closes.
How we support
- Ongoing support covering Node.js and Backstage upgrades is available as a retainer, scoped in writing.
- Retainer work can include tracking the Backstage release line and planning the next runtime change ahead of time.
- Continuous testing of your portal can run against new releases so regressions surface before production.
- Ask any provider, including DeployOpen, to confirm response expectations and deliverables in the written quote.

