runner supply for GitHub Actions

CI on your
own iron.

ushr schedules GitHub Actions jobs onto hardware you already own: ephemeral VMs, just-in-time runner registration, one priority queue across every org. Your workflows don't change. Your bill does.

Join the waitlisthosted control plane · early access
  • runner lifetime: exactly one job, then the VM is destroyed
  • your keys: never leave your machines
  • pipeline migration: none; it's still GitHub Actions
dispatch ledger · live2 orgs · 3 machines · 6 slots
14:02:07build-ios #8841dispatchedushr-m2-01
14:02:09unit-tests #8842dispatchedushr-m2-02
14:02:31build-ios #8841runningushr-m2-01
14:03:04lint #8843queuedqueue: labs
14:03:45unit-tests #8842done4 min · $0.25
14:03:47lint #8843dispatchedushr-m2-02
14:04:26e2e #8844dispatchedushr-lx-01
saved this hour: $0.93 · on GitHub-hosted this would still be queued

One fleet. Every org.

GitHub scopes self-hosted runners to a single org. Share hardware across orgs the traditional way and you're installing a separate runner service per org on every machine, partitioning the fleet by hand, and watching one org's Macs sit idle while another org queues.

ushr unpins the hardware. One agent serves all your orgs; every job gets a fresh runner minted just-in-time into whichever org is next by priority and age. The busiest queue gets the iron.

And the iron is virtualised: each Mac hosts two isolated macOS VMs (Apple's licensing ceiling), a Linux box holds as many runners as its cores and memory allow, and the agent runs anywhere you can boot it, including inside a VM.

runners pinned per org · the old way
acme-engmac-01 mac-02idle all week
acme-labsmac-037 jobs queued
↓ same hardware, ushr scheduling
one queue · 3 machines · 6 VM slots
acme-labse2e #311dispatched → mac-01·vm1
acme-labse2e #312dispatched → mac-01·vm2
acme-engnightly #88done → mac-02·vm1

Twice the silicon, none of the wait

An M4 Pro against GitHub's 3-vCPU macOS slice isn't a fair fight: real builds finish in roughly half the wall-clock. Your jobs get whole performance cores and local NVMe, not a rented sliver of someone else's box.

Caches never cross the network either. Hosted runners start empty every run: download the tarball, unpack, re-upload. ushr keeps per-repo build caches on the machine, so warm layers load at disk speed and Docker builds skip straight to what changed.

And there's no capacity queue in front of your fleet: dispatch is measured in milliseconds, not "waiting for a runner".

same job, both laneslower is better
clean macOS build
github-hosted14m 20s
ushr · m4 pro6m 50s
warm Docker build
github-hosted4m 10s
ushr · m4 pro22s
queue → running
github-hosted~1m
ushr · m4 pro0.3s
representative builds · mac mini (m4 pro) vs github's 3-vCPU macOS runner · pickup measured on a live fleet

Keyless by architecture

The control plane is a scheduling brain, nothing more. Your agent dials out; queue metadata goes up, dispatch decisions come down. The GitHub App key stays on your machine, mints just-in-time runner tokens locally, and your runners talk to GitHub directly.

There is no credential to breach on our side, because we never hold one. That's not a policy, it's the wire protocol.

ushr.iocontrol plane

scheduler · dispatch ledger · analytics

metadata ↑
dispatch ↓
your ironopen-source agent

polls GitHub · mints JIT tokens · provisions VMs

[key] never leaves this box

runners register just-in-time
github.comunchanged

How it works

  1. 1

    curl get.ushr.io | sh

    One line installs the CLI and agent on any Apple Silicon Mac or Linux box. No toolchain, checksummed binaries.

  2. 2

    ushr login

    One guided command per machine: missing prereqs offered as installs, browser approval, your GitHub App created with the key written to your machine (not ours), agent service started. Tart, Lume or Docker isolation; each runner serves one job, then it's gone.

  3. 3

    Label your jobs

    runs-on: [self-hosted, macos] and you're routed. ushr dispatches by label, priority and age across every org you run, from one queue.

$ curl -fsSL https://get.ushr.io | sh
==> Downloading ushr (darwin/arm64)
==> Installed ushr 0.2.3 to ~/.local/bin

$ ushr login
==> Wrote default agent config: ~/.config/ushr/agent.yaml
==> tart image "runner-mac" present ✓
==> Opening https://ushr.io/cli/auth… 
==> Confirmation code: KTX-42P
==> Waiting for approval........ 
 Enrolled agent "mini-01" for org(s) acme-eng
 ushr-agent service running
  Target jobs with:  runs-on: [self-hosted, macOS, ARM64]

real output, not a mockup: a fresh machine to a live runner in two commands

driverstartlumedockerk8s: plannedlabelsmacoslinuxarm64x64

The meter runs backwards

GitHub-hosted macOS runners bill at $0.062 a minute. A Mac mini on your shelf bills at zero. Drag the slider to your team's monthly runner-minutes and watch the invoice unhappen.

runner-minutes / month12,000
$744
saved / month
$8,928
/ year
vs GitHub-hosted macOS
at $0.062/min

the receipts

$ ushr cost
ushr cost: last 30 days (rate $0.062/runner-min)

        ORG   JOBS   RUNNER-MIN      SAVED
   acme-eng    412         9840    $610.08
  acme-labs     96         2310    $143.22
      TOTAL    508        12150    $753.30

real subcommand, ships in the CLI

The control plane, hosted

ushr.io schedules across your fleet while your hardware does the work. Keys, code and secrets never cross the wire. Early access is rolling out now.

© 2026 ushrusher, without the e