GitHub runners
GitHub runners give each GitHub Actions job a fresh microVM, on your own servers or on the Frazer fleet, next to Frazer Cache. A pool splits its capacity into lanes, so pull requests opened by agents get their own floor, cap and daily budget and never starve the jobs people are waiting for. To move a workflow, you change one line: runs-on.
Product overview: GitHub runners.
How it works
Section titled “How it works”You install the Frazer GitHub Runners app on your GitHub organization and create a pool. When a job asks for a Frazer label, Frazer starts a microVM, registers a just-in-time runner for that one job, attaches the repository’s warm disk and points the job’s tools at the cache. When the job ends, the VM is destroyed. A runner never takes a second job, so nothing survives from one job to the next: no files, no Docker state, no credentials.
Scaling follows the queue. For each lane, Frazer adds runners for the jobs GitHub has assigned, keeps a warm pool learned from the last 14 days of arrivals, and stays inside the lane’s floor, cap and budget, the pool’s minimum and maximum, and the free capacity of its servers. On your own servers, CPU is overcommitted, because CI jobs rarely use every core they ask for. An optional overflow pool on the Frazer fleet takes jobs when your servers are full.
A pool belongs to a host project, usually the project that owns your servers (for example acme-infra), and is shared with the projects whose repositories run on it. Each repository belongs to one project per pool. That project pays for the repository’s jobs and sees them under Engineering › Runners.
- Pool: runner capacity for one GitHub connection: placement, sizes, lanes, isolation, image, cache policy, egress policy and scaling. A pool is mirrored to a GitHub runner group with selected repositories.
- Size: a
runs-onlabel with its vCPU, memory and disk, such asfrazer-8c. - Lane: a priority class inside a pool, chosen by a suffix on the label (
frazer-8c-agent). - Runner: one job’s VM. Read-only: you see its state, server, job and lane.
- Runner image: what a job’s VM boots, versioned and rolled out in rings.
- Job: the record of one job: phases, tests, cache restores, result and runner-seconds.
| Label | vCPU | Memory | Disk |
|---|---|---|---|
frazer-4c |
4 | 16 GiB | 75 GiB |
frazer-8c |
8 | 32 GiB | 100 GiB |
frazer-16c |
16 | 64 GiB | 150 GiB |
Isolation
Section titled “Isolation”| Level | What runs the job | Use it for | Where |
|---|---|---|---|
microvm (default) |
A microVM with its own kernel and its own Docker daemon | Every job, including pull requests and agents; services:, testcontainers, docker build |
Your servers and the fleet |
vm |
A full VM per job from a warm pool | Whole GPUs, nested virtualization (Android emulators, VM tests), very large disks | Your servers and the fleet; GPU runners run on your GPU servers |
container |
A plain container, with a rootless builder for image builds | Trusted repositories that need the fastest start | Your servers only, in pools without forks or agent lanes |
Create a pool
Section titled “Create a pool”You need the right to install a GitHub App on your GitHub organization, and a server pool in the host project (or a Frazer fleet location) for the runners to run on.
-
Install the Frazer GitHub Runners app. In the console, open your organization’s Settings › Integrations, choose GitHub and install Frazer GitHub Runners on your GitHub organization. The installation becomes a connection, such as
github-acme.The App asks for organization Self-hosted runners: write, repository Actions: write and Metadata: read, and the
workflow_jobandworkflow_runevents. It has no access to your code and no administration permission. -
Create the pool in the host project.
Terminal window frazer githubrunners pools create acme-ci --provider github --connection github-acme \--placement pool:vint-hill-default --sizes frazer-4c,frazer-8c,frazer-16c \--isolation microvm --cache-policy acme-default --dry-runRead the plan, then run the same command without
--dry-runto apply it. If a repository’s workflow usespull_request_target, the plan names the workflow file: trusted pools refuse it.Open Infrastructure › Build › Runners in the host project and choose New runner pool. Pick the connection, the placement, the sizes and the cache policy, review the plan and apply it.
-
Share the pool with the projects that run on it, and name each project’s repositories.
Terminal window frazer githubrunners pools share acme-ci --project acme-infra \--consumer acme-staging=repositories:notes,platform-envs --dry-run -
Change
runs-onin a workflow to a Frazer size..github/workflows/ci.yml jobs:test:runs-on: ubuntu-latestruns-on: frazer-8csteps:- uses: actions/checkout@v5- run: go test ./...actions/cachekeeps working with no edits: the job’s cache calls go to Frazer Cache. -
Push a commit and open the job in the consumer project under Engineering › Runners › Jobs, or list jobs from the CLI.
Terminal window frazer githubrunners jobs list --project acme-staging --since 24hEach job shows five phases: Wait, Start, Cache, Steps and Upload.
Route jobs to lanes
Section titled “Route jobs to lanes”GitHub hands a queued job to any idle runner whose labels match, so priority between kinds of jobs needs separate labels. Each lane is its own set of runners with its own label suffix, and Frazer divides the pool’s capacity between lanes in this order:
| Lane | Label suffix | Runs | Default |
|---|---|---|---|
| Release | -release |
Release workflows, on frazer-16c-release |
Floor of 8 runners |
| Main | -main |
Pushes and the merge queue (merge_group) |
Floor of 6 runners |
| Human PR | none (frazer-8c) |
Pull requests opened by people | — |
| Agent PR | -agent |
Pull requests opened by agents | Floor of 4, cap of 40 % of the pool’s maximum, daily budget |
| Batch | -batch |
Scheduled workflows | — |
Print the line for a pool, and paste it into your workflows:
frazer githubrunners pools snippet acme-ciruns-on: frazer-8c${{ github.event_name == 'pull_request' && (github.event.pull_request.user.type == 'Bot' && '-agent' || '') || '-main' }}Pull requests opened by bot accounts go to the agent lane, other pull requests to the human lane, and everything else to the main lane. A workflow that keeps a plain frazer-8c runs in the human lane; release workflows use frazer-16c-release and scheduled workflows the -batch suffix. A pool can also treat agent branches, or a list of agent accounts in your organization, as agents; the snippet then includes them.
Control agent pull requests
Section titled “Control agent pull requests”The agent lane has four controls, set per pool:
- Cap: at most 40 % of the pool’s maximum runners by default.
- Daily budget: 1,900 runner-minutes a day per pool by default. When the budget is used up, the lane is held: its jobs stay queued in GitHub, marked Held: agent budget, and are never canceled, while people’s lanes keep running.
- Cancel superseded runs: when an agent pushes a new commit to a pull request, Frazer cancels the run for the older commit. People’s runs are canceled only by their workflow’s own
concurrencysetting. - Skip identical trees: when the same tree passed on this pool in the last 24 hours, Frazer posts a check run that links to that result instead of running it again.
Frazer Agents read the lane’s remaining budget and slow their pushes before it runs out. To raise the budget or the cap, apply a plan from the Runners Overview, or update the pool:
frazer githubrunners pools update acme-ci --lane agent.cap=40% --lane agent.budget=1900m --dry-runBefore a scaling change applies, you can replay it against a stored trace of the pool’s queue: the plan on the pool’s Scaling tab shows the wait at the 90th percentile for people and for agents. An excerpt of frazer githubrunners pools get acme-ci -o yaml, with the agent lane and the pool’s scaling:
lanes: agent: suffix: -agent floor: 4 cap: 40% budget: { runnerMinutesPerDay: 1900, onExhausted: hold } cancelSuperseded: true skipIdenticalTree: 24h backpressure: truescaling: { min: 2, max: 60, overcommit: 3.0, warm: { mode: learned, windowDays: 14 }, scaleDownIdle: 2m }Find out why a job was slow
Section titled “Find out why a job was slow”A job page builds its phases from three sources: GitHub’s timestamps, the VM’s own agent (claim, boot, warm-disk attach, checkout) and the cache agent (each restore with its tier, bytes and seconds). Tests come from JUnit XML or go test -json. Cost is in runner-seconds, and in dollars on the fleet.
When a job is slower than usual, the page compares its phases and slowest tests with the job’s median over the last 7 days and names causes from records: an eviction, a full Git fetch, a cache refusal, a new test, a cold GPU VM. Nothing is guessed. Explain hands the record to the Assistant.
frazer githubrunners jobs list --project acme-production --slow --since 24hfrazer githubrunners jobs get 8812345 --whyThe Insights tab shows jobs by author type (human or agent), queue wait per lane, runner-minutes per merged pull request, the slowest steps with their trend, flaky tests (failed, then passed on the same commit), cache hit rate per repository and fleet spend. A flaky test gets a quarantine pull request that adds it to .frazer/quarantine.yaml.
frazer githubrunners insights --project acme-production --flakyTo reproduce a failure, open a devbox from the failed job’s image, warm disk and environment:
frazer githubrunners jobs debug 8812345Use your own runner image
Section titled “Use your own runner image”The default image, frazer-ubuntu-24.04, carries the same tool set as GitHub’s hosted runner images (languages, tools, Docker and buildx). It is rebuilt every week and signed.
To build your own, create a runner image from a Dockerfile, a devcontainer.json or a workspace template. A workspace template builds the runner image, the devbox image and the agent sandbox image from the same bytes, so CI, your engineers’ boxes and your agents run the same tools.
Image versions are numbered and roll out in rings: ring 1 is a canary that takes 5 % of jobs, ring 2 is every job once ring 1 has run without new failures. Rollback is one click. The runner binary’s version is part of the image, so runner upgrades roll out the same way.
frazer githubrunners images versions list acme-ci-ubuntu-24.04Run pull requests from forks
Section titled “Run pull requests from forks”Code from a fork runs only on a pool marked untrusted: microVMs on the fleet or on isolated servers, with no secrets, an egress allow-list, read-only caches and no warm-disk commit. GitHub’s approval for outside contributors stays on. Trusted pools refuse workflows that use pull_request_target, because it runs fork code with the base repository’s secrets.
Access and audit
Section titled “Access and audit”- Who manages pools: members of the host project with the right roles create and change pools, always through a plan. Sharing a pool with another project needs the
runners.runnerPools.sharepermission and is recorded in both projects. - Who sees jobs: members of the project a repository is shared to see its jobs, tests and costs.
- No long-lived credentials: each VM gets a just-in-time runner registration. Jobs reach Frazer Registry, Frazer Secrets and the cache with the job’s own OIDC token, and secret values are scoped to the job. The App’s private key is a key that can sign but never be exported.
- Egress you can read: each pool has an egress allow-list (GitHub, Frazer endpoints and the site’s package proxies). A new policy runs in audit mode for 7 days, then enforces. Every destination is logged per job, and internal address ranges and node metadata are always denied.
- Watchdog: a runner is wedged when a step shows no progress and no output for 5 minutes, its connection to GitHub’s broker is dead, or a step runs past 1.5 times its timeout. Frazer destroys the VM, re-runs the job once and records it; a second wedge on the same job opens an incident.
- Alerts: every pool ships with Prometheus rules
FrazerRunnerWedged,FrazerRunnerQueueP90High,FrazerRunnerBootSlowandFrazerCacheHitDrop. - Budget caps: when a project’s budget cap is reached, runner jobs on the Frazer fleet stay queued; nothing is deleted.
Limits and quotas
Section titled “Limits and quotas”| Setting | Default |
|---|---|
| Jobs per runner | 1; the VM is destroyed after the job |
| Lane floors | Release 8, Main 6, Agent 4 runners |
| Agent lane cap | 40 % of the pool’s maximum |
| Agent lane budget | 1,900 runner-minutes a day per pool; then held |
| Skip identical trees | Same tree passed on the pool within 24 hours |
| CPU overcommit | 3.0 on your own servers; none on the Frazer fleet |
| Warm pool | Learned per hour and weekday; never more than 20 % of the pool’s maximum |
| Idle warm VMs | Destroyed after 2 minutes above the warm target |
| Overflow to the fleet | When a lane other than Batch waits more than 60 s at the 90th percentile, or the pool has no free capacity for 60 s |
| Wedged runner | No progress for 5 minutes, a dead broker connection, or 1.5 × a step’s timeout |
| Start | Queued → in progress p50 ≤ 3 s, p95 ≤ 10 s at every size in a burst of 15 (target) |
| Organization scope | Pools are organization-level runner groups; enterprise-level runners are not supported |
Pricing
Section titled “Pricing”On your own servers, GitHub runners and Frazer Cache are part of the per-core server fee, with no per-minute charge. On the Frazer fleet you pay per runner-second by size, with no minimum.
| GitHub runners | On your servers | On the Frazer fleet |
|---|---|---|
| Price | Included · no per-minute fee | $0.045 / vCPU-hour with 4 GiB per vCPU, per second, no minimum, no free minutes · every size below |
Every line, with the estimator: pricing.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Cause | Fix |
|---|---|---|
| Agent jobs stay queued, marked Held: agent budget | The agent lane used its daily budget. | Wait for the next day’s budget, or raise it from the Runners Overview or with frazer githubrunners pools update acme-ci --lane agent.budget=… --dry-run. |
| A job stays queued and no runner picks it up | Its runs-on label is not a size and lane of the pool, or the repository is not shared to a project on the pool. |
Compare the label with frazer githubrunners pools snippet, and add the repository to a project’s share entry. |
| The create or update plan names a workflow file | The workflow uses pull_request_target, which trusted pools refuse. |
Remove pull_request_target from that workflow, or run it on an untrusted pool. |
| A fork’s pull request never runs | Fork code runs only on untrusted pools. |
Create an untrusted pool for public repositories. |
| A job was re-run on its own | The watchdog found the runner wedged, destroyed it and re-ran the job. | Read the job’s Activity tab. A second wedge on the same job opens an incident. |
| A job is slower than usual | A phase took longer than its 7-day median. | Run frazer githubrunners jobs get <job> --why, or open the job page; the causes come from records. |
| Restores are slow or miss | The warm disk was evicted, the toolchain changed, or the cache is degraded. | See Cache troubleshooting. A cache outage only makes jobs slower; it never fails them. |
| A run cannot be canceled through GitHub’s API | A zombie run on GitHub’s side. | The console lists it with GitHub Support steps and leaves it out of the queue math. |
Reference
Section titled “Reference”CLI
| Command | What it does |
|---|---|
frazer githubrunners pools create | Create a runner pool from a plan, with its provider, connection, placement, sizes, isolation and cache policy. |
frazer githubrunners pools get | Show a pool; with -o yaml, its full spec, lanes and scaling included. |
frazer githubrunners pools update | Change a pool's lanes, caps, budgets or scaling, as a plan. |
frazer githubrunners pools snippet | Print the lane-aware runs-on line for a pool. |
frazer githubrunners pools share | Share a pool with other projects and name the repositories each one runs, as a plan. |
frazer githubrunners jobs list | List a project's jobs, for example only slow ones in the last 24 hours. |
frazer githubrunners jobs get | Show a job's phases; with --why, compare them with the job's usual time and name the causes. |
frazer githubrunners jobs debug | Open a devbox from a failed job's image, warm disk and environment. |
frazer githubrunners images versions list | List the versions of a runner image and the ring each one is in. |
frazer githubrunners insights | Show a project's CI insights, such as flaky tests. |
All of them: CLI reference.
MCP tools
| Tool | What it does |
|---|---|
frazer.runners.v1 | Read pools, jobs, phases, tests, insights and a lane's remaining budget; plan pool changes. Writes become a plan that waits for approval. |
All of them: MCP tools reference.
Related
Section titled “Related”- Cache: the tiers, write rules and quotas behind every job.
- Devboxes: the same images and caches for your engineers.
- Agents: agents that open pull requests and read their lane’s budget.
- Reviewer: reads job results as records.
- Registry: push images from CI without a stored password.
- GitHub runners product page