Skip to content

Blog

Is ubuntu-latest moving to Ubuntu 26.04, and what will break in my workflows?

·

Yes. GitHub is moving the ubuntu-latest label from Ubuntu 24.04 to 26.04 between 19 October and 19 November 2026, so a workflow can land on either image until then. What breaks is newer defaults, a few tools 26.04 no longer carries and, on 26.04 images built before a fix GitHub merged on 20 October, a size-capped /tmp. Pin ubuntu-24.04 to stay put.

This post answers the five questions people ask when the move reaches them: when it happens and how to see which image a job got, what breaks, why large builds failed with “disk quota exceeded” and whether that is fixed, how to pin and test, and how to stop the next move being a surprise. The GitHub facts were read from GitHub’s issues, release notes and image readmes, and from Ubuntu’s release notes, on 10 November 2026. Where something is ours, it says so.

When does ubuntu-latest switch to Ubuntu 26.04, and how do I tell which image a job got?

Between 19 October and 19 November 2026. GitHub’s announcement, issue 14748 in the runner-images repository, says the change is “rolled out over a period of several weeks beginning October 19, 2026” and that GitHub plans to complete it by 19 November. It names the x64 label and does not say the Arm64 one moves. It gives no schedule within those weeks, so, reading it plainly, two runs of one workflow on the same day can get different images until the move ends.

The ubuntu-26.04 label itself is not new: GitHub closed the request to add it on 11 June 2026, so you can already run on it by name.

To see which image a job got, open the run, then the Set up job step. Its Runner Image group lists the image (ubuntu-24.04 or ubuntu-26.04), its version, and links to the list of installed software and the release. GitHub’s image build also sets ImageOS and ImageVersion in the environment, so a step can record it too, along with the one setting that matters most below.

- name: Which image is this? run: | echo "ImageOS=$ImageOS ImageVersion=$ImageVersion" . /etc/os-release && echo "$PRETTY_NAME" uname -r findmnt -T /tmp -o TARGET,FSTYPE,SIZE

There is a precedent. The last move, to 24.04, was announced on 17 September 2024 for a rollout ending 17 January 2025, and its thread filled with reports of missing tools and a pip “externally managed” error. The same kind of list follows.

What changes in Ubuntu 26.04 that breaks builds?

Mostly newer defaults, plus a short list of removed tools, and not the .NET SDKs: GitHub’s 26.04 image still carries .NET 8, 9 and 10. Here is what differs between the two images, from GitHub’s own readmes for the 1 November builds.

Itemubuntu-24.04ubuntu-26.04
systemd255259
Kernel6.17 at the announcement, 7.0 by the 1 November build7.0
GCC (the apt gcc package)13.2.015.2.0
Python (default)3.12.33.14.4
Node.js (default)22.23.424.22.2
Java (default)17.0.2125.0.5
PostgreSQL16.1518.6
.NET SDKs8, 9 and 108, 9 and 10

Two cautions on that table. The kernel is no longer a difference: by the 1 November build the 24.04 image was on 7.0 as well, so a build that breaks on 26.04 is rarely the kernel’s doing. And the announcement’s own table says the default Java “remains Java 17” while the 26.04 readme lists 25.0.5 as the default, so read JAVA_HOME in a job instead of assuming either.

The readmes also differ in what they list. Julia, Miniconda, Mercurial, Fastlane, Lerna, Newman, Parcel, Pulumi, MediaInfo, p7zip, the Sphinx search server and haveged are in the 24.04 readme and not in the 26.04 one. That is our comparison, not a GitHub notice, so check the one your job uses and install it in the job if it is gone.

Underneath are changes Ubuntu made, which apply on any 26.04 machine. Ubuntu’s release notes list sudo-rs as the default sudo (the old one is sudo.ws), Rust coreutils with the classic GNU tools one gnu prefix away, cgroup version 1 removed, glibc 2.43, and apt-key gone. A script that parses sudo output, reads cgroup v1 paths or calls apt-key is the kind that fails.

Two reports in the announcement’s thread show the shape of the rest: a GCC cross-compile for s390 that fails on a -Werror=maybe-uninitialized in system headers, and a WireGuard setup action that failed with “fopen: Permission denied” until the job moved back to 24.04. Both come from users, not GitHub, and the second was put down to AppArmor.

The .NET case is worth a sentence because it is the one people expect. Ubuntu’s own 26.04 archive has no .NET 8 or 9 SDK package, so apt-get install dotnet-sdk-8.0 finds nothing in Ubuntu’s own archive on a bare 26.04 or in a container built FROM ubuntu:26.04. GitHub’s 26.04 image and ours carry the 8, 9 and 10 SDKs regardless, so a plain dotnet build keeps working. If you install by package, use the setup action instead:

- uses: actions/setup-dotnet@v6 with: dotnet-version: | 8.0.x 9.0.x

Why do large builds fail with “disk quota exceeded” on /tmp, and is it fixed?

On a 26.04 image built before GitHub’s fix, /tmp was a RAM-backed tmpfs capped at half of memory, with a per-user quota, so a large build ran out of space long before the disk was full. The reporter’s Go build died with write $WORK/b4822/_pkg_.a: disk quota exceeded. GitHub’s maintainer reproduced it on both ubuntu-26.04 and ubuntu-26.04-arm: the tmpfs was about 7.8 GB on a standard runner, a regular user hit the quota near 6.3 GB, and the same write passed on 24.04.

The cause is Ubuntu’s, not GitHub’s: the 26.04 release notes say /tmp “is now a tmpfs file system by default”. Because it is memory, a big file in it also competes with your build for RAM.

GitHub has a fix. A change titled “Keep /tmp on the root disk” masks tmp.mount in the 26.04 image build and was merged on 20 October 2026. On 29 October, in issue 15177, a member of GitHub’s runner-images team wrote of the 26 October builds that the image keeps /tmp on disk. Issue 14777 itself was still open on 10 November with the label awaiting-deployment, so run the findmnt line in the step above to confirm on the image you got: tmpfs means an image built before the fix, and you are exposed. Ours said tmpfs when we ran that step on runnersio-2vcpu-ubuntu-2604 on 10 November: /tmp was 3.9G, half of the 7.8 GiB of memory the job reported, with the root disk at 79 GB. GitHub’s change is not in our 26.04 image, so on it the workarounds below apply.

If it says tmpfs, there are two workarounds, either of which is safe on 24.04 too. The first points temporary files at the runner’s temp directory, which is the alternative GitHub’s maintainer named. The second puts /tmp back on the root disk, and has to be the first step, because anything already in /tmp disappears when it is unmounted.

steps: # Option 1: most tools read TMPDIR - name: Temp files on the runner's disk run: echo "TMPDIR=${{ runner.temp }}" >> "$GITHUB_ENV" # Option 2, as the FIRST step: for tools that hard-code /tmp - name: Put /tmp back on the root disk run: | if mountpoint -q /tmp; then sudo systemctl mask tmp.mount sudo umount -l /tmp fi

How do I pin a job to ubuntu-24.04 while I test 26.04 on a branch?

Write runs-on: ubuntu-24.04. GitHub gives exactly this as the way back, and says to use ubuntu-26.04 to test the new image explicitly. Put both in a matrix on a branch, let the 26.04 leg fail without failing the run, and read what it reports before the label changes under you.

jobs: test: name: test (${{ matrix.os }}) runs-on: ${{ matrix.os }} continue-on-error: ${{ matrix.experimental }} strategy: fail-fast: false matrix: include: - os: ubuntu-24.04 experimental: false - os: ubuntu-26.04 experimental: true steps: - uses: actions/checkout@v7 - run: make test

When the 26.04 leg is green, set its experimental to false, and then change the pinned leg when you choose to. The ARM labels are named the same way (ubuntu-24.04-arm and ubuntu-26.04-arm), so the same pin applies there.

How do I keep the OS version explicit from now on, so the next -latest move is a choice?

Name the version in every runs-on, and fail the build when someone writes -latest. GitHub describes a -latest label as “the latest stable images that GitHub provides”: it moves when GitHub decides, with weeks of notice in an announcement issue, which is how 24.04 and now 26.04 arrived.

name: no-latest-labels on: pull_request jobs: check: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v7 - name: Refuse -latest labels run: | if grep -rnE 'runs-on:.*-lates[t]' .github/workflows; then echo '::error::Name the OS version, not -latest' exit 1 fi

The bracket in lates[t] stops the check matching its own file. Then keep one scheduled job on the newest label, so the next image shows its problems in a workflow you chose to run rather than in your release. GitHub posts each move as an announcement issue in the runner-images repository (14748 is pinned there), so that is the place to watch.

Where runners.io fits

Every label names its operating system version: it contains -ubuntu-2204, -ubuntu-2404 or -ubuntu-2604 (with -arm after it on ARM64), or -windows-2025 or -windows-2022 on Windows. There is no -latest, so a version never changes under a job. As of 10 November 2026, when the migration reads ubuntu-latest it writes the 24.04 label, the version it ran until this rollout, and ubuntu-26.04 becomes the 26.04 one.

jobs: test: runs-on: runnersio-2vcpu-ubuntu-2404 # stays on 24.04 until you edit it test-next: runs-on: runnersio-2vcpu-ubuntu-2604 # the same size, on 26.04

A size costs the same on each Ubuntu version, so testing 26.04 is a change to the end of the label; the labels page has every one and the runners page the sizes. The image page says our 26.04 image carries the same set as the other Ubuntu images, including the .NET 8, 9 and 10 SDKs. Be clear about the limit: pinning avoids the 26.04 changes above, it does not remove them. Most are Ubuntu’s, so they reach our 26.04 image as they reach any other. Ubuntu’s tmpfs /tmp does apply on our 26.04 image: on runnersio-2vcpu-ubuntu-2604 it was 3.9G on 10 November, half of that label’s memory, so its size follows the label’s memory and not GitHub’s 7.8 GB. We measured that one label; the findmnt step above tells you for yours. If you mistype the end of one of our labels while editing forty files, the job is refused and its page names the label to change; if you mistype the runnersio- prefix, the job waits for a runner that will never come, which this guide explains.

Run your CI and your infrastructure on one bill

Connect GitHub

Card required. You pay only for the minutes your jobs run.