Test a runtime upgrade by adding the new version to a CI matrix next to the current one, running the full suite on both, fixing what fails on the new one while the old keeps passing, then switching the production image and rolling out in stages. Both versions stay green until the day you drop the old one.
Why runtime upgrades are different
A runtime upgrade is a change under every dependency at once. A package that worked on the old runtime may use a removed API, a changed default, or a native extension that needs a rebuild. Runtimes also have support windows, so staying put has its own security cost once security fixes stop. For a worked example of tracking those windows, see how long a Node.js LTS release is supported.
Step 1: Add the new version to the matrix
Most CI systems let one job run across a list of runtime versions. Add the target version beside the current one. Mark the new version as allowed to fail at first, so it reports without blocking merges, then make it required once it is green.
| Matrix cell | Purpose | Blocking |
|---|---|---|
| Current runtime | Protects production today | Yes |
| Target runtime | Shows what breaks | No, until green |
| Next runtime (pre-release) | Early warning, optional | No |
Also include each operating system and architecture you ship on. A runtime upgrade often breaks one platform first.
Step 2: Fix in small pull requests
Work through failures grouped by cause: removed APIs, dependency incompatibilities, changed defaults, build tooling. Land each fix so it works on both runtimes. This keeps the main branch releasable and keeps the final switch small. For dependencies that block the move, see whether a newer release supports the target runtime before reaching for overrides.
Step 3: Check things the unit tests miss
- Native extensions and binary wheels. Confirm they install for the new runtime on your platforms, from your registry, without compiling surprises.
- Container image. Build on the target runtime base image and run the smoke test. See testing a base image upgrade.
- Memory and startup. Runtimes change garbage collection and startup behavior. Compare memory and time to ready.
- TLS and crypto defaults. Newer runtimes sometimes tighten defaults, which can break connections to old services.
- Locale, timezone and encoding behavior in formatting and parsing.
- Deprecated flags in launch scripts and configuration.
Step 4: Switch and roll out
Change the runtime in the production build, deploy to a small slice, and compare per-version error rate, latency, memory and restarts against the control. Kubernetes Deployments roll out new pods gradually when the pod template changes, such as a new image, and keep revision history for rollback. Keep the old image for a defined period.
Step 5: Remove the old cell
After a full traffic cycle on the new runtime, drop the old matrix cell, update documentation, and set the next review date. Write the support end date of the new runtime in your calendar now, so the next upgrade is planned and not forced.
Keeping it from becoming a project
Small, regular runtime upgrades are cheaper than occasional large ones. Many teams upgrade one runtime version at a time soon after release, not several versions at once after support ends.
Safeguard's dependency scanning and SBOM give you the component inventory to review against the new runtime. Free scan: gold.safeguard.sh/scan.
Quick check
Should the new runtime cell block merges immediately?
No. Start it as informational, fix failures, and make it required once it passes consistently.
Can I upgrade the runtime and dependencies together?
It is better to separate them. Change one thing at a time so a failure has a clear cause.
Sources
Last reviewed: 19 October 2026.