Skip to content
Latchkey

GitHub Actions runs-on Labels: Every Option

Multiple labels are ANDed, not ORed. runs-on: [self-hosted, linux, gpu] needs one runner carrying all three labels, which is why jobs queue indefinitely instead of failing.

The runs-on key selects the machine, and two things about it surprise people: the specs differ between public and private repositories, and multiple labels require a single runner that has every one of them.

A job requesting a label combination no runner satisfies does not fail. It queues, forever, with no error.

GitHub-hosted standard runners

LabelPrivate repoPublic repoRate (private)
ubuntu-latest / ubuntu-24.042 vCPU, 8 GB4 vCPU, 16 GB$0.006/min
ubuntu-24.04-arm2 vCPU, 8 GB4 vCPU, 16 GB$0.005/min
windows-latest2 vCPU, 8 GB4 vCPU, 16 GB$0.010/min
macos-latest3 vCPU, 7 GB (arm64)Same$0.062/min

Larger runners

Linux x64 sizeRatearm64 rate
4-core$0.012/min$0.008/min
8-core$0.022/min$0.014/min
16-core$0.042/min$0.026/min
32-core$0.082/min$0.050/min
64-core$0.162/min$0.098/min

Labels are ANDed

.github/workflows/ci.yml
# one runner must carry ALL of these labels
runs-on: [self-hosted, linux, x64, gpu]

# a single label
runs-on: ubuntu-latest

# a group plus a label (organisation runners)
runs-on:
  group: my-group
  labels: [self-hosted, linux]

# choose by matrix
strategy:
  matrix:
    os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}

Why a job queues forever

  • No runner carries the full label set. Labels are ANDed, and this is by far the most common cause.
  • The matching runner is offline or already busy at its concurrency limit.
  • A typo in a label. Labels are matched literally, and there is no warning for one nobody has.
  • A larger-runner label that is not configured for the repository or organisation.
  • Concurrency limits for your plan are already saturated by other runs.

Frequently asked questions

How many vCPUs does ubuntu-latest have?
2 vCPU and 8 GB RAM on a private repository, 4 vCPU and 16 GB on a public one. The asymmetry is why the same workflow can feel slower after a repository is made private.
Why is my job stuck in queued?
Almost always because no online runner carries every label the job requested. Multiple labels are ANDed, so [self-hosted, linux, gpu] needs one runner with all three, not three runners with one each.
Are larger runners billed on public repositories?
Yes. Standard runners are free and unlimited on public repositories, but larger runners are always billed, including on public repos and while included minutes remain.
How do I run a job on ARM?
Use an ARM label such as ubuntu-24.04-arm. ARM is cheaper on every provider that publishes both, typically 17% to 40% below the x64 rate for the same size.

Related guides

References

Run this faster and cheaper on Latchkey managed runners - self-healing included. Start free → 30-day trial · No credit card