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/storage/extras/…) you reference instead of spelling everything out in the runs-on label. A sticky disk is separate from the runner root volume and gives cache directories persistent EBS storage.
Flex#
Define runners in .github/runs-on.yml, then reference them with runner=<name>:
runners: gpu-type1: family: ["g4dn", "g5"] image: ubuntu24-gpu-x64 cpu: [4, 16] spot: price-capacity-optimized sticky: build-cache:30gb extras: ["s3-cache"] preinstall: | apt-get update && apt-get install -y my-tooljobs: build: runs-on: runs-on=${{ github.run_id }}/runner=gpu-type1Common fields: cpu, ram, volume, sticky, 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"] sticky = "build-cache:30gb" }}
fleets = { gpu = { runner = "gpu" runner_group = "platform" }}See Runner fleets for the full catalog reference.
Use runs-on/action@v2 to place cache paths on a configured sticky disk. See
Sticky disks for the label syntax,
cache modes, and lifecycle.
Instance type priority#
List exact instance types in family from most to least preferred. For example, ["c8a.large", "c8i.large"] puts AMD ahead of Intel. How AWS uses that order depends on the purchasing option:
| Configuration | How AWS selects an instance |
|---|---|
On-demand (spot: false) | RunsOn automatically enables prioritized allocation. AWS tries the first type before using the next to fulfill capacity. |
Spot (spot: capacity-optimized-prioritized or spot: cop) | AWS honors the order on a best-effort basis, but optimizes for capacity first. |
Other Spot settings, including the default spot: true | List order has no effect. AWS uses the selected price/capacity strategy. |
Spot prioritization requires v3.3.0 or later.
For Flex, define reusable runners in .github/runs-on.yml:
runners: amd-on-demand: family: [c8a.large, c8i.large] image: ubuntu24-full-x64 spot: false amd-spot: family: [c8a.large, c8i.large] image: ubuntu24-full-x64 spot: capacity-optimized-prioritizedSelect either runner with runner=amd-on-demand or runner=amd-spot. The equivalent per-job labels are:
jobs: on-demand: runs-on: runs-on=${{ github.run_id }}/family=c8a.large+c8i.large/image=ubuntu24-full-x64/spot=false spot: runs-on: runs-on=${{ github.run_id }}/family=c8a.large+c8i.large/image=ubuntu24-full-x64/spot=copFor Fleet, set the same ordered family list and spot value in the Terraform runners map. Workflow labels still select a fleet; they do not override its instance preferences.
Use exact types throughout the list for predictable ordering. On-demand chooses its allocation strategy after compatibility filtering: if every remaining entry is exact, it uses prioritized allocation; otherwise, it uses lowest-price allocation.
With spot=cop, every entry must be an exact type before filtering. Family names such as c8a, wildcards such as c8a.*, missing selectors, and unrecognized types cause a launch validation error, even when mixed with valid exact types.
Exact types ignore cpu and ram ranges, including values inherited from a runner preset. Choose sizes explicitly and use types compatible with the selected image. An x64 image cannot provide an ARM fallback.
With spot=cop, c8i.large may still launch while c8a.large has capacity: this is a preference, not a strict fallback chain or a fixed distribution. If Spot cannot fulfill the request, RunsOn falls back to on-demand and uses the exact-type order. That fallback may cost more; priority does not mean lowest price. See the AWS allocation strategy documentation ↗.
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):
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-ubuntu24Fleet#
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.
| Aspect | Flex | Fleet |
|---|---|---|
| Where defined | .github/runs-on.yml | Terraform module (runners/images) |
| Who selects | Workflow author, per job | Platform team, in the catalog |
Per-job ami= override | ✓ | — |
| Wildcard name lookup | ✓ | ✓ |
| Recommended for | Workflow-specific or experimental shapes | Shared standardized shapes for many repos |