Skip to content

Moving Node.js CI to Ubuntu 26.04 on GitHub Actions

GitHub's ubuntu-26.04 runner ships Node 24.21.0 by default where ubuntu-24.04 ships 22.23.3. Here is what else differs and how to test the switch.

· · 6 min read
A conveyor belt running through a large warehouse

Quick Take

The ubuntu-26.04 GitHub Actions runner puts Node.js 24.21.0 on PATH by default, while ubuntu-latest (still Ubuntu 24.04) puts 22.23.3 there. Python jumps from 3.12 to 3.14, OpenSSL from 3.0 to 3.5, the kernel from 6.17 to 7.0. A workflow that never calls setup-node changes Node major version the day you change the label. Pin Node explicitly, add 26.04 as an extra matrix leg first, and print the toolchain versions so a red build tells you why.

Changing one word in a workflow file feels harmless. runs-on: ubuntu-24.04 becomes runs-on: ubuntu-26.04, and you've swapped the operating system under every build step you own.

I haven't run a production pipeline on the new image yet, so this isn't a benchmark. What I did: read the contents lists GitHub publishes for both images (image versions 20260927.149.1 for 26.04 and 20261004.327.1 for 24.04), diffed the parts that matter to a Node.js project, and wrote down the order I'd test them in. The versions below are quoted from those lists, and image contents change weekly, so recheck before you rely on a number.

Is ubuntu-26.04 Ready to Use for CI?

It exists and you can target it. The labels are ubuntu-26.04 for x64 and ubuntu-26.04-arm for Arm64. GitHub announced the image as a public preview on June 11, 2026, with the warning that preview images may have longer queue times at peak hours and different tools than earlier images.

Is it still in preview? The runner-images README table now lists 26.04 as a plain row, without the preview badge that its Xcode 27 row carries. I didn't find a general-availability announcement in what I read, though, so I'd check the repository before pointing a required status check at it. And ubuntu-latest? Still Ubuntu 24.04 in that README. Per its own rules, GitHub says it will announce a move of the -latest label with lead time.

Ubuntu 26.04 LTS itself came out on April 23, 2026, with standard support to April 2031. Plenty of runway. The question is only when you switch, not whether.

What Differs Between the Two Images?

Here's the table I'd tape to the monitor. All values come from the two image readmes named above.

Itemubuntu-24.04ubuntu-26.04
Default node on PATH22.23.324.21.0
Cached Node versions22.23.3, 24.21.022.23.3, 24.21.0
Python3.12.33.14.4
OpenSSL (system)3.0.133.5.5
Kernel6.17.0 (azure)7.0.0 (azure)
Bash5.2.215.3.9
Docker client28.0.429.4.2
Docker Compose2.38.25.1.3
Podman4.9.35.7.0
g++13.2.015.2.0
Yarn (classic)1.22.221.22.22

Two rows deserve a second look. The cached Node versions are identical, so anything that calls actions/setup-node with an explicit version behaves the same on both. The default node on PATH is not identical, and that's the trap.

Why Does the Default Node Version Matter So Much?

Because plenty of workflows never install Node at all. They call npm ci and rely on whatever the image ships. On 24.04 that's Node 22. On 26.04 it's Node 24. You haven't touched your package.json, yet your CI now runs a different major version, with a different V8, different require(esm) behavior and different deprecations. If a build goes red after the label change, this is the first suspect.

Never let a CI image decide your Node version for you: the line that says which runtime your tests ran on belongs in your repository, not in someone else's runner changelog.

Share this Post on X Bluesky

The fix is one step, and it costs about a second when the version is cached:

- uses: actions/checkout@v4
- uses: actions/setup-node@v4
  with:
    node-version-file: .nvmrc
    cache: npm
- run: node --version && npm --version && python3 --version && openssl version
- run: npm ci
- run: npm test

The node --version line looks pointless. It isn't. When the same job is green on one image and red on another, that single log line is usually the whole diagnosis, and it saves you from scrolling through installation noise. I would add it to every workflow.

If your project is still on Node 20, the Node.js 20 to 24 migration guide covers what breaks at the language level, and our Node.js 24.19.0 notes cover one of the releases in the 24.x line these images carry.

What Breaks When Python and the Compiler Move?

Pure JavaScript projects mostly won't notice. Projects with native addons might.

Node's own binaries bundle their own OpenSSL, so the system OpenSSL jump from 3.0 to 3.5 matters mainly to native modules linked against the system library and to anything shelling out to openssl or curl. The part more likely to bite is node-gyp. It uses Python and the system C++ compiler to build addons that don't ship prebuilt binaries, and the 26.04 image moves Python from 3.12.3 to 3.14.4 and ships g++ 15.2. An older addon that compiled quietly on a 2024-era toolchain can fail on a 2026 one. I haven't hit that on this image, so I can't name a package. I just know which step to look at: npm ci or npm rebuild, in the logs, before any test runs.

Docker is the other place to look. Compose went from 2.38.2 to 5.1.3 across the two images, a jump in major version number, so a workflow that parses docker compose output or relies on an older flag deserves a dry run.

How Do You Test the Switch Without Risking the Main Branch?

Add the new OS as an extra matrix leg that is allowed to fail, then promote it once it's green for a week.

jobs:
  test:
    runs-on: ${{ matrix.os }}
    continue-on-error: ${{ matrix.experimental }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-24.04]
        node: [22, 24]
        experimental: [false]
        include:
          - os: ubuntu-26.04
            node: 24
            experimental: true
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
          cache: npm
      - run: node --version && python3 --version
      - run: npm ci
      - run: npm test

fail-fast: false keeps one red leg from cancelling the others, and continue-on-error stops the experimental leg from blocking merges. Two weeks of green on the extra leg is a decent bar. Then flip experimental to false and drop the old OS when you're ready.

Watch out for one thing: a required status check is matched by job name. Renaming or reshaping the matrix changes the names, and a required check that never reports leaves a pull request stuck. Change the branch protection rule in the same sitting as the workflow.

The same approach works for the type-check job in our TypeScript 7 CI guide. It's a short job, so the experimental leg costs almost nothing.

Should You Move Now?

My view: add the matrix leg this week, switch the primary label later. There's no deadline pushing you. Ubuntu 24.04 stays supported for years, and ubuntu-latest hasn't moved. But the default-Node change is silent, and the cheapest day to discover it is a day when nothing else is broken.

Ready to try it? Pin Node, print the versions, add one experimental leg. Then read the logs of that first run before anything else. If it comes back green on the first try, you've lost ten minutes. If it doesn't, you've found out on your terms.

Frequently Asked Questions

Does ubuntu-latest point to Ubuntu 26.04 yet?
Not in the runner-images README I read on October 14, 2026: ubuntu-latest and ubuntu-24.04 are the same row, and 26.04 is listed separately as ubuntu-26.04 (x64) and ubuntu-26.04-arm. The README says GitHub will announce a change to a -latest label and give lead time first, so check it before assuming either way.
Which Node.js version does the ubuntu-26.04 runner use by default?
Node.js 24.21.0, according to the Ubuntu 26.04 image readme (image version 20260927.149.1). The ubuntu-24.04 image (20261004.327.1) lists Node.js 22.23.3 as its default. Both images cache Node 22.23.3 and 24.21.0 for setup-node, so an explicit version works the same on either.
Will actions/setup-node still work on ubuntu-26.04?
It should, since the image carries cached Node 22 and 24 builds and setup-node downloads other versions on demand. I haven't run a workflow on the new image, so treat that as the expected behavior and verify with a matrix leg before you switch a required check.
Do I need to change anything for native npm modules?
Possibly. The 26.04 image has GCC 15.2 and Python 3.14.4 for node-gyp, against Python 3.12.3 on 24.04. Packages that compile on install can break on a newer compiler or Python even when the JavaScript is untouched. A test install on a 26.04 matrix leg shows it before it matters.