GitHub rents you a Mac by the minute. I already had a Mac mini on my desk, a Framework desktop and a Linux box. So I built ushr to run CI on them, and today it is open to everyone.
ushr runs GitHub Actions jobs on your own hardware. Every runner is single-use. One fleet serves several GitHub orgs by priority. The hosted control plane schedules the work but never holds a GitHub credential.
Why Now
Mac CI is the expensive part of CI. GitHub’s list price for a standard macOS runner is $0.062 a minute, against $0.006 for 2-core Linux. That is ten times the rate, on hardware that most Apple teams already have on a desk.
Three things moved this year:
- GitHub tried to charge for self-hosted minutes. On 16 December 2025 it announced a $0.002 per minute platform fee for self-hosted runners from 1 March 2026. A day later it postponed the fee “to take time to re-evaluate our approach”. Postponed, not cancelled. Public repos stay free.
- The main flat-price Mac vendor stopped selling. Cirrus Labs announced on 7 April 2026 that it is joining OpenAI. Cirrus CI shut down on 1 June. Cirrus Runners, at $150 per runner per month, takes no new customers.
- Tart changed hands. The macOS VM tool behind most Apple-silicon CI is now openai/tart, under FSL-1.1-ALv2. ushr uses it by default and ships a Lume driver as a fallback.
The options left are rented Macs, or a runner service you install yourself on each machine with no view across them. I wanted my own hardware with a managed fleet view on top.
— Cirrus Labs, April 2026We are no longer accepting new customers for Cirrus Runners but will continue supporting the service for existing customers through their existing contract periods.
If the runner economics can change under you in a day, own the runner.
What ushr Does
- Single-use runners. On a Mac, each job gets a fresh Tart or Lume VM. On Linux, each job gets a fresh Docker or Podman container. The runner registers just in time, runs one job and is gone. No state leaks between jobs.
- Cross-org priority. One fleet serves several GitHub orgs and repos. Higher priority runs first, and a waiting job gains priority every minute, so low-priority work does not starve.
- Keyless control plane. Each org’s GitHub App private key stays on the host. The agent mints the runner config locally. Dispatch is two-phase: the control plane offers a job, and one host claims it. The hosted plane sees jobs and hosts, never a GitHub credential.
- Open core. The agent, CLI and self-hostable controller are Apache-2.0. The hosted plane adds a dashboard for fleet, jobs, analytics and cost avoided, plus multi-host scheduling, alerts by email, Slack or webhook, an audit log, and managed agent updates that roll back on failure.
A CI service that holds your GitHub App key can register runners in your org. That makes it a target. ushr’s hosted plane cannot do that, because the key never leaves your machine. Releases are signed with keyless cosign and carry an SBOM per archive from v0.2.11.
30 Days on My Own Fleet
ushr builds ushr. Both of its repos and this blog run their CI on ushr runners. Three hosts serve four GitHub orgs and my personal repos: a Mac mini (M4 Pro), a Framework desktop and a Linux box. These numbers come from the production database for the 30 days to 6 October:
- 5,464 jobs ran. A job counts when its runner started or finished. 5,441 ran on Linux, 23 on the Mac.
- 0.24 seconds from offer to claim, median. That is the control plane’s own overhead.
- 30 seconds from GitHub queue to offer, median. The 95th percentile is 138 seconds. This wait includes time for a free slot, so it grows when the fleet is busy.
- 8 seconds from claim to runner start on Linux, 23 seconds on the Mac, median. That is the cost of a fresh container or VM per job.
- 11,660 runner minutes, rounded up per job as GitHub bills: 11,452 on Linux and 208 on the Mac.
- 214 more dispatches never started a runner. The offer expired, or a host lost the claim.
At GitHub’s list prices, those minutes come to about US$82. That assumes every repo is private, and some of mine are public, where GitHub minutes are free. The hosted plane costs me about US$30 a month to run, about $20 for the Neon database and $10 for Fly.
So my own bill was never the point. The Framework desktop serves a client org, ushr’s repos and this blog from one priority queue. That is the feature I could not buy.
The Mac story is also honest: 23 jobs this month. The Mac is where the per-minute rate hurts, and US$49 buys only 790 macOS minutes at list price. A team with real iOS volume passes that in a day or two.
What ushr Doesn’t Do Yet
- GitHub.com only on the hosted plane. The self-hosted controller has GitHub Enterprise Server settings, but nobody has tested them on a real GHES instance.
- No Windows runners.
- No Kubernetes driver. If you run Kubernetes, GitHub’s actions-runner-controller already exists. A ushr driver is on the roadmap.
- Two macOS VMs per Mac. Apple’s licence caps it there, so a Mac mini runs at most two Mac jobs at once.
- The control plane is a dependency. If it goes down, running jobs finish, but no new jobs dispatch until it comes back. Self-host the controller if that is not acceptable.
Pricing
Free covers one host and one GitHub scope. Pro is US$49 a month for up to three hosts and unlimited orgs, plus US$19 for each extra host. Prices exclude tax. Self-hosting the controller is free under Apache-2.0.
Try It
curl -fsSL https://get.ushr.io | sh
ushr login
ushr login opens a browser for approval, creates the GitHub App from a manifest in two clicks, and installs the service. Then ushr init rewrites your workflows’ runs-on labels. To self-host instead, run ushr setup --org NAME.
Start at ushr.io, read the docs, check pricing, or read the source on GitHub. If your Macs sleep at night, give them a job.



