Self-Hosted CI Runners vs Paying for Cloud Minutes
Self-hosted CI runners vs cloud minutes: when owning your build machines beats paying per-minute, and the real cost math across a portfolio of repos.
If your CI bill scales with how much you ship, you have the incentive backwards. Self-hosted CI runners flip that. You pay for a machine, not for minutes, and the machine does not care whether you run one build a day or four hundred. For a portfolio that ships constantly, owning the runners is cheaper and faster within the first month. For a repo that builds twice a week, keep the hosted minutes. The dividing line is volume and build weight, and most teams cross it earlier than they think.
When do self-hosted CI runners beat cloud minutes?
Cloud minutes make sense when your usage is spiky and low. Free tiers cover a solo project. The moment you have real traffic, the meter turns into a tax on velocity.
I run CI across roughly twenty repos on my own boxes. The math is simple. A hosted provider charges per build minute, and heavy jobs (Docker builds, integration tests, video renders) burn minutes fast. A dedicated runner on a VPS costs a flat monthly rate and runs those same jobs on hardware I already pay for. Across the portfolio, the crossover happened at a laughably low volume. One repo with a slow test suite covered the box.
Cross to self-hosted when any of these are true:
- You run more than a few thousand build minutes a month.
- Your builds are CPU or memory heavy and hosted runners feel slow.
- You need a big cache that hosted runners throw away between jobs.
- You want builds inside your own network, next to your own database.
Stay on cloud minutes when builds are rare, when you have no one to babysit a runner, or when a compliance rule forces isolated ephemeral machines.
What owning your runners actually gets you
Speed is the underrated part. A self-hosted runner keeps its Docker layer cache, its dependency cache, and its warm toolchain between builds. Hosted runners start cold every time. My builds that took eight minutes on shared runners drop to two on a warm box because nothing gets re-downloaded.
You also get to size the machine to the job. Need 16 gigabytes of RAM for a memory-hungry test? Buy it once. On hosted minutes you rent that class of machine at a premium every single run.
And you get placement. My runners live on the same private network as my Postgres box, so integration tests hit a real database over a local link instead of spinning up a throwaway container each time. That is the same reason I keep one Postgres box for every app: proximity and control compound.
The hidden costs people forget
Owning runners is not free. Be honest about the parts that do not show up on the invoice.
You maintain the machine. OS updates, disk cleanup, runner agent upgrades. Docker build caches will eat a disk alive if you never prune them, and a full disk fails builds in confusing ways. Budget an hour a month.
You handle security. A CI runner executes arbitrary code from your repos, so it is a juicy target. Keep runners off your production network where you can, rotate the registration tokens, and never let untrusted pull requests run on a persistent self-hosted runner without isolation. This is the same discipline behind managing secrets without a cloud vendor: ownership means you own the attack surface too.
You handle capacity. One runner is a single point of failure for your pipeline. Run two, or accept that a dead box blocks deploys until you rebuild it.
None of this is hard. It is just work that the hosted provider was doing for you, priced into the minutes.
How to make the switch without downtime
Do not rip out hosted CI on day one. Add a self-hosted runner alongside it and route one repo's jobs to the new runner. Watch it for a week. Compare build times and failure rates against the hosted baseline.
Once one repo is stable, move the heavy repos next, since those save the most money and time. Leave light, rarely-built repos on hosted minutes if you like; there is no prize for purity. The goal is owning the expensive part of the workload, not owning everything.
Set up the runner with a clean provisioning script so you can rebuild it from scratch in minutes. That script is your insurance. If the box dies, you spin up a fresh VPS, run the script, register the runner, and you are back. That is the same pattern that makes git-push deploy on your own box reliable: everything is reproducible.
The broader point is the one behind the whole own your stack idea. A bill that grows every time you ship is a bill that punishes the exact behavior you want. Owning the machine breaks that link. You can get the runners, the VPS, and the private network to hang it all on from HostSSH, and the flat monthly number will make the per-minute meter look absurd in hindsight.