← bright.log

Cloud Macs are slow and expensive

Like a lot of other mostly-backend developers, I started vibe coding mobile apps last year, thrilled to be able to get working prototypes done in a day. But I was much less excited about cloud macOS options for CI.

GitHub charges $0.062 a minute for their cheapest macOS runner, ten times the Linux rate. A 20-minute build is $1.24. Push twice a day and macOS CI alone is over $50 a month. That was surprising enough that I went and measured what the money buys.

Here’s XcodeBenchmark on the runners you can rent, and two Macs I own:

RunnerRelativeTimePer minute
GitHub macos-latest, M1 ×3838s$0.062
GitHub macos-15-large, Intel ×12955s$0.08
GitHub macos-15-xlarge, M2 Pro ×5339s$0.10
MacBook Air M2 ×8, 2022202s
MacBook Pro M4 Max ×16, 202477s
XcodeBenchmark, one clean build. Shorter is faster; the two in accent are machines I already own.

A four-year-old Air beats the most expensive macOS runner GitHub offers. The M4 Max is four times faster than it. For macOS the usual cloud tradeoff is inverted: you pay a premium for slower hardware while a faster machine sits idle on your desk. Put both axes on one chart and there is nothing in the bottom-left corner but hardware I already own:

Cost per build against build time. The three rented runners cost between $0.57 and $1.27 a build and take 339 to 955 seconds. The two Macs I own cost nothing to run and finish in 77 and 202 seconds.18 min0$0$1.50macos-latestmacos-15-largemacos-15-xlargeMacBook Air M2MacBook Pro M4 MaxCost per build →Build time →
Cost per build is the measured time at each runner’s posted rate. Down and left is better.

So I wrote localmost. It manages GitHub’s official actions-runner on your Mac (registration, process lifecycle, surviving a closed lid) and falls back to hosted runners when your machine isn’t there.

Wrapping the runner was straightforward. Harder was deciding what a job off the internet should be allowed to do. macOS has several sandboxing mechanisms, none of which quite compose, and none of which are the VM-level isolation a CI runner really wants. localmost proxies runner traffic through an allowlist and confines writes to the job’s working directory. Much worse guarantees than an ephemeral container or VM.

So if your repo takes pull requests from people you don’t know, don’t run them on your laptop. localmost is for your own projects, or a small high-trust team. While you can configure who can trigger jobs (default is just you), I would not recommend the liability for larger public projects.

Although localmost was originally meant mostly just for running xcodebuild jobs, I’ve also found it useful for projects where I want to benchmark performance on Apple Silicon directly.

If you’re also a newly prolific mobile app dev, or otherwise run a lot of Mac builds, localmost might speed up your workflow and save you money. Try it out and let me know what you think.