Runner Image

A runner image is the prepared disk image a CI runner boots from, carrying the operating system and the preinstalled toolchains a job expects to find.

A runner image is the prepared disk image a machine boots from before it accepts a GitHub Actions job, carrying the operating system together with the compilers, language runtimes, and command line tools that jobs expect to find. The image decides what exists on the machine at second zero, so any tool the image lacks has to be installed by a step inside the job.

The image is built ahead of time and published as a versioned artifact. Two runs of the same workflow can land on two different builds of the same image, which is the usual explanation for a tool version that moved without a commit touching it.

Definition

A runner image is a snapshot of a complete filesystem, produced by a build pipeline and published in a bootable form: an AMI on AWS, a managed image on Azure, a machine image on GCP, or a container image where jobs run inside containers. When a runner starts, the machine boots that image, the runner agent registers with GitHub, and the first step of the job executes against whatever the image already contains.

Four layers travel inside a runner image, and a job depends on all four.

LayerContentsWhy a job depends on it
Operating systemKernel, init system, base distribution packagesFixes the syscall surface, the C library version, and the package manager available to steps
System packagesgit, curl, unzip, a compiler toolchain, a container runtimeSteps call these directly and rarely install them
Language runtimes and SDKsNode, Python, Go, Java, .NET, Ruby, held in a tool cache that can carry several versions of eachA setup action resolves a requested version from the cache instead of downloading it
Machine configurationDefault user, PATH, environment variables, service units, warmed cachesDecides file permissions and what has already run before the first step

Where the image definition lives

GitHub builds the images behind its hosted runners in the open. The actions/runner-images repository holds the build definitions plus one README per image listing the packages and versions in the current build, such as the Ubuntu 24.04 image README (checked on 2026-08-13). Self-hosted fleets follow the same pattern with their own pipeline: a tool such as Packer provisions a machine, installs the toolchain, and captures the result as a versioned image.

Image versions move

A published image is immutable, and the pipeline that produces it ships a new version on a cadence. Jobs pick up the newest version as it rolls out, so a workflow that pins nothing inherits the image's choice of patch versions. Hosted images record the build a job ran on in the ImageOS and ImageVersion environment variables, which is why cache keys in published workflows often include them (GitHub-hosted runners reference, checked on 2026-08-13).

Terms that sit next to it

  • Container image. The filesystem named in a container: key or a Dockerfile FROM line, which a job's processes run inside. The runner image still sits underneath and supplies the kernel and the container runtime.
  • Runner agent. The software that registers the machine and polls GitHub for queued jobs. It is baked into the image or fetched at start, and updating it changes no part of the toolchain.
  • Tool cache. A directory inside the image holding prebuilt runtimes, exposed through the RUNNER_TOOL_CACHE environment variable and located at /opt/hostedtoolcache on hosted Linux images. A version present there is a directory copy; a version absent from it is a download on every run.

Example

This workflow leans on the runtimes the image already carries. Nothing here installs a language.

name: test
on:
  push:
    branches: [main]

jobs:
  unit-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Show what the image carries
        run: |
          node --version
          python3 --version
          docker --version
          ls "$RUNNER_TOOL_CACHE/node"
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - run: npm ci
      - run: npm test

The setup-node step is a lookup rather than an install while the image tool cache holds a matching build. The step log says the version was found in the cache and prints the path under /opt/hostedtoolcache/node, and the only work done is adding that directory to PATH. Request a version the image build predates and the same step falls back to a download.

Requested versionHeld in the image tool cacheWhat the step doesFailure modes it adds
node-version: 22YesAdds the cached directory to PATHNone beyond the image itself
node-version: 25NoFetches the release tarball and extracts itNetwork reachability, checksum, and rate limits on every run

The same split explains why one workflow reaches its test command with no downloads at all while a sibling workflow spends its opening steps on apt-get install and toolchain fetches.

Recording which image build ran

When a job fails and the commit looks innocent, the image build is the first thing to check.

      - name: Record the image build
        run: echo "image ${{ env.ImageOS }} ${{ env.ImageVersion }}"

Printing those two values on every run turns an image rollout into a visible event in the logs. A green run on ImageVersion 20260701.1 and a red run on ImageVersion 20260715.2 at the same commit points at the image rather than at the change.

When a required tool is missing from the image, there are two routes. Install it in the job, which pays the install cost on every run, or bake it into an image of your own and route jobs to that image, which moves the cost to build time and adds an image pipeline to maintain.

FAQ

What is a runner image in GitHub Actions?

It is the prepared disk image a runner machine boots from before it accepts a job. The image carries the operating system, the system packages, and the language runtimes and SDKs that steps use, so anything the image lacks has to be installed by a step inside the job.

How is a runner image different from a Docker image?

A runner image boots a whole machine and supplies the kernel, the init system, and the container runtime. A Docker image supplies a filesystem for a process that runs on top of that machine, so a job using a container still depends on the runner image underneath it.

Why did a tool version change without any workflow edit?

Runner images are versioned and republished on a cadence, and a job runs on whichever build is current when it starts. A workflow that requests a runtime by major version inherits the patch version the image happens to carry, so an image rollout can move it between two runs of the same commit.

Start with $10 in free credits

Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.