jobs
runs-on:
Selects the runner (or runner group) that will execute the job. Accepts a single label, an array to match multiple labels (self-hosted), or an object form for larger runners and runner groups.
runs-on: <label> | [label1, label2] | {group: <group>, labels: [...]} Common values / subkeys
| Value / Subkey | Purpose |
|---|---|
| ubuntu-latest | Rolling alias for the newest supported Ubuntu image |
| ubuntu-24.04 | ubuntu-22.04 | Pinned Ubuntu versions |
| windows-latest | windows-2022 | Windows Server images |
| macos-latest | macos-14 | macOS images (arm64 on 14+) |
| self-hosted | Route to a self-hosted runner (combine with more labels) |
| group / labels | Object form for larger/self-hosted runner groups |
Examples
runs-on: ubuntu-latest Standard GitHub-hosted Linux runner
runs-on: [self-hosted, linux, x64, gpu] All labels must match on the runner
runs-on:
group: production
labels: [ubuntu-latest, large] Larger-runner group selection
runs-on: ${{ matrix.os }} Runner chosen per matrix leg
Gotcha
`ubuntu-latest` migrates on GitHub's schedule and can change tool versions overnight — pin (e.g. `ubuntu-24.04`) for reproducibility; array form is AND (all labels must match), not OR.
Related keywords
jobs:
Top-level map of jobs that run in parallel by default. Each job has an ID (used by `needs:` and outputs) and either a `steps:` list or a `uses:` reference to a reusable workflow.
strategy:
Fans a job out across a matrix of variable combinations. Each combination becomes an independent job whose values are exposed via the `matrix` context.
run:
Executes a shell command on the runner. Multi-line scripts run in a single shell invocation; the shell is chosen by `shell:` (or workflow `defaults`) and defaults to bash on Linux/macOS and pwsh on Windows.
needs:
Declares job dependencies so the current job waits for the listed jobs to complete successfully. Also exposes their outputs via `needs.<job_id>.outputs.<name>` and their result via `needs.<job_id>.result`.
if:
Conditional expression that controls whether a job or step runs. On a job it gates the entire job; on a step it gates only that step, and both automatically wrap the value in an implicit `${{ }}` if omitted.