Runners & infrastructure

GitHub Actions lets custom runner images stack on other custom images

GitHub Actions lets custom runner images stack on other custom images

Custom images for GitHub-hosted runners can now be built on top of other custom images, per the June 18 changelog. For a platform team running a fleet of hosted runners, that single change converts image management from a flat collection of one-off recipes into a layered chain — a base image, a shared middle layer, a team-specific tip — that mirrors how container images have been organised for years.

The changelog itself is narrow. It says custom runner images are gaining new capabilities that give teams more flexibility over how they compose and manage image-generation pipelines, and the headline capability is stacking custom images on other custom images. Beyond that, the post does not enumerate flags, image tags or pipeline syntax, so the operational specifics still live in the GitHub Actions docs rather than the announcement.

The mechanic

A custom runner image used to be a leaf node. You built it once from whatever base GitHub published, shipped the resulting image, and that artefact was what a workflow booted. After this change, the leaf can itself become a base for another custom image. The chain the platform now allows is therefore something like vendor base → shared org image → team image, with each layer expressing only the deltas above the one below it.

Why this matters at fleet scale

For a single project, layering is a curiosity. For a platform team that owns the runner image catalogue, it is the difference between "every team duplicates the same hardening recipe" and "the hardening lives in one image that everyone inherits." The familiar argument for layered images applies one level up the stack: a shared layer is built once, audited once and revoked once. Drift between teams shrinks. When a CVE lands in something the base layer installs, you rebuild that layer and everything downstream inherits the patch on its next rebuild, rather than each team chasing the fix separately.

Using the layered chain

The changelog does not publish the exact build-time syntax for declaring a parent custom image, so the practical recipe belongs to the docs rather than this post. The shape, though, is familiar from container workflows — a build that references a parent image, a registry the runner image system pulls from, and a tag scheme that promotes layers up the chain. Two habits port over from container image management without much editing:

  • Pin parent images to immutable references, not floating tags. A team image that points at <org>/base-runner:latest will produce silently different fleets across rebuilds.
  • Treat each layer as a release artefact. Tag, sign and changelog it the way you would a base container image; the fact that it boots a CI runner rather than an application does not change the supply-chain footprint.

The catch on a 3am call

Layering simplifies governance and complicates the diff. When a workflow starts failing on a fresh runner, the question "what changed in the runner image" used to terminate at one Dockerfile. With three layers in the chain, the change might sit in any of them — and the team that owns the tip image may not own the middle layer that introduced the regression. The mitigation is unsurprising: lock the parent reference per release, keep build provenance attached to each layer, and rehearse the rollback on the tip image before the rollback is the only thing standing between you and a stalled merge queue.

There is a registry-availability tail to this, too. A flat custom image is one fetch on cold start; a three-deep chain is potentially three, with whatever the runtime's pull behaviour does on a partial failure. Worth pinning down against the docs once a fleet starts composing more than two layers.

Continuity caveat

Image stacking is a build-time convenience, not a guarantee about what reaches a runner at boot. The same content-trust questions a platform team would ask about a container base image — who signed it, how is it scanned, when is it rebuilt — now apply layer-for-layer to a runner image that has a parent. The changelog moves the mechanism. The policy that decides which parent images are allowed to sit underneath a production runner is still the operator's to write.

Source: GitHub Changelog (github.blog)

Related
Runners & infrastructure

GitHub Actions resumes self-hosted runner version enforcement

Self-hosted runners must register on 2.329.0 or later and install each new release within 30 days, with full enforcement landing September 25, 2026 on github.com. The change moves runner version management from a hygiene task into a fleet-inventory problem.

June 15, 2026
Runners & infrastructure

ARM-based CI runners go mainstream — what it means for build costs

Hosted ARM64 runners are now generally available across the major CI clouds. For many builds they cut minutes-billing meaningfully — but only if your toolchain is already multi-arch.

June 14, 2026
Security

actions/checkout v7 refuses fork PR code in pull_request_target

GitHub shipped actions/checkout v7, which fails by default when a workflow triggered by pull_request_target or workflow_run tries to fetch the head of a fork's pull request. Same-repo PRs and the standard pull_request event are unaffected; a deliberately conspicuous opt-out exists for teams who really mean it.

Invalid Date

Turn this into your pipeline. Build it on Buddy.

Start free