self-host →

Custom runners & images

Define custom runner shapes and custom AMIs for RunsOn — per job in Flex (.github/runs-on.yml) or in the Terraform catalog for Fleet.

The built-in runners and images cover most workloads. When you need a specific instance shape, or your own AMI with internal SDKs, slow-changing toolchains, or security agents baked in, define a custom runner and/or a custom image.

The model is the same across products — only where you declare it and who selects it differ: in Flex workflow authors define and pick per job in .github/runs-on.yml; in Fleet the platform team curates a Terraform-owned catalog.

Custom runners

A custom runner is a named runner shape (CPU/RAM/family/image/extras/…) you reference instead of spelling everything out in the runs-on label.

Flex

Define runners in .github/runs-on.yml, then reference them with runner=<name>:

.github/runs-on.yml
runners:
gpu-type1:
family: ["g4dn", "g5"]
image: ubuntu24-gpu-x64
cpu: [4, 16]
spot: price-capacity-optimized
extras: ["s3-cache"]
preinstall: |
apt-get update && apt-get install -y my-tool
jobs:
build:
runs-on: runs-on=${{ github.run_id }}/runner=gpu-type1

Common fields: cpu, ram, volume, family, spot, image, ssh, extras, private, nested-virt, retry, preinstall, prerun, tags. See Repo-level config for the full reference.

Fleet

Fleet defines runner shapes in the Terraform runners map, exposes them through fleets, and workflows target the fleet label:

runners = {
gpu = {
family = ["g5.xlarge"]
image = "ubuntu24-gpu-x64"
extras = ["s3-cache"]
}
}
fleets = {
gpu = {
runner = "gpu"
runner_group = "platform"
}
}

See Runner fleets for the full catalog reference.

Custom images

Every runner launches from an EC2 AMI. Default runners use a public AMI managed by RunsOn; a custom image points RunsOn at your own AMI instead.

Flex

Define the image once in .github/runs-on.yml, then select it from workflow YAML (or from a custom runner’s image):

.github/runs-on.yml
images:
ci-ubuntu24:
platform: linux
arch: x64
owner: "123456789012"
name: "ci-ubuntu24-*"
jobs:
test:
runs-on: runs-on=${{ github.run_id }}/runner=2cpu-linux-x64/image=ci-ubuntu24

Fleet

The same decision belongs in the Terraform-owned catalog:

images = {
ci-ubuntu24 = {
owner = "123456789012"
name = "ci-ubuntu24-*"
platform = "linux"
arch = "x64"
}
}
runners = {
linux-ci = {
family = ["c8i.large"]
image = "ci-ubuntu24"
}
}
fleets = {
linux-ci = { runner = "linux-ci", runner_group = "platform" }
}

Image model

A runner image is an EC2 AMI plus metadata: platform, arch, owner, and either an exact ami ID or a name lookup pattern. The image must match the runner architecture and region — x64 runners need x64 AMIs, arm64 runners need arm64 AMIs, Windows runners need Windows AMIs.

The name pattern can include a wildcard (e.g. ci-ubuntu24-*); RunsOn then queries EC2 at launch time for the most recent matching AMI — the usual way to pick up nightly rebuilds without touching the workflow. Both Flex and Fleet images also support preinstall (runs as root before the runner agent) and prerun (just-in-time setup, useful with warm pools).

Use custom images for dependencies that change slowly: SDKs, compilers, language runtimes, large base Docker layers, internal tooling, or security agents. Keep fast-changing build outputs in caches instead.

Flex vs Fleet

Same runner and image model, same metadata fields; only the place where they’re declared and selected changes.

AspectFlexFleet
Where defined.github/runs-on.ymlTerraform module (runners/images)
Who selectsWorkflow author, per jobPlatform team, in the catalog
Per-job ami= override
Wildcard name lookup
Recommended forWorkflow-specific or experimental shapesShared standardized shapes for many repos