Hosted runners bill by the minute, and that meter never stops. Here's the break-even math on running your own, plus the tradeoffs nobody warns you about.
The invoice that started this for us was $4,200 for one month of GitHub Actions minutes. Not a Fortune 500 monorepo. A team of nine, one product, a test suite that liked to fan out across a matrix. Somebody in finance drew a red circle around the number and asked the obvious question: why are we renting compute by the minute when we run a Kubernetes cluster that sits half-idle at night?
Fair question. So we ran the numbers properly, and this is what came out.
Hosted runners are pure consumption. GitHub, GitLab, CircleCI, all of them bill per-minute, and a chunk of your minutes are pure overhead: spin-up, checkout, dependency install, cache warm. On a standard 2-vCPU hosted runner you pay for every second of that, and the price per minute climbs fast when you jump to 8 or 16 vCPU tiers. There is no idle cost, which is the whole appeal. You pay for exactly what you burn.
Self-hosted flips it. You pay for the box whether a job is running or not, plus the part everyone underestimates: ops time. A runner fleet is infrastructure. It needs patching, monitoring, an on-call story when the autoscaler wedges at 2am. The compute might be cheap. The human who keeps it alive is not.
So the honest comparison is not "per-minute price vs EC2 price." It is hosted minutes vs (infra + storage + egress + a real slice of an engineer's month).
Here is the shape of it. Say your hosted bill is per-minute rate r, and you burn M minutes a month. Hosted cost is simply r × M.
Self-hosted cost is infra + ops. A single always-on c6i.2xlarge-class VM runs roughly $250/month on demand, less on savings plans, a lot less on spot. Add maybe $150/month of amortized ops if you are honest about it. Call it $400 all-in for one beefy runner that can chew through jobs continuously.
At a typical hosted rate for an 8-vCPU tier, $400 buys you somewhere in the low thousands of minutes. If your monthly minutes sit above that line and keep climbing, self-hosting wins. Below it, you are paying a fleet's fixed cost to save pocket change, and the ops burden makes it a net loss.
The number that actually matters is not total minutes. It is concurrent demand. Ten thousand minutes spread evenly is one busy runner. Ten thousand minutes that all land in a 90-minute window before every deploy is a scaling problem, and that is where autoscaling earns its keep.
Static VM runners. One or two always-on boxes registered to your org. Dead simple, and for a team with steady, predictable load it is often the right answer. The downside is you size for peak and eat idle the rest of the day.
Autoscaling on Kubernetes with Actions Runner Controller. ARC runs runners as pods and scales replicas against pending jobs. If you already operate a cluster, the marginal cost of a runner pool is close to zero, and you get bin-packing for free. This is where we landed.
Ephemeral runners. Each job gets a fresh runner that is destroyed after one run. No state bleeds between jobs, which matters more for security than for cost, but it also kills the "poisoned cache" class of flakiness.
Spot instances for the pool. Runners are the perfect spot workload: interruptible, stateless, retryable. A spot autoscaling pool can cut the compute line by 60-70%. A killed runner just means a re-queued job, not a lost transaction.
Security is the big one. A self-hosted runner is a persistent machine with access to your network and, often, your secrets. Do not, under any circumstance, run untrusted pull-request workflows on a privileged self-hosted runner. A fork PR can run arbitrary code, and now that code is inside your VPC. Ephemeral runners help because they die after each job. Isolation helps: separate node pools, minimal IAM, no long-lived cloud credentials on the box. Keep public-repo PR builds on hosted runners and self-host only the trusted internal work.
Maintenance is real and recurring. Runner versions drift, the controller needs upgrades, disk fills with Docker layers, and someone has to care. Budget for it or it will find you.
Caching is the quiet win. This one surprised us. On hosted runners, every job pulls dependencies and Docker layers cold across the network. On a warm self-hosted node with a local layer cache and a persistent module cache, our average build dropped from 11 minutes to under 4. Faster feedback is worth more than the compute savings, and it does not show up anywhere on the invoice.
Faster hardware, on your terms. Want NVMe scratch disks, more RAM, or ARM runners for cheaper compute? You buy them once instead of paying a premium tier every minute.
Low volume, small team, spiky-but-light load: stay hosted. If your monthly bill is a couple hundred dollars, the ops time to run your own fleet costs more than you would ever save, and you are trading a solved problem for a pager. Self-hosting is a scale play. Below the break-even line it is a hobby.
If you are still choosing tools, this is a decision to make alongside your CI/CD platform, not after it, because portability of your workflows shapes how painful the runner migration will be.
What we actually run: a dedicated spot node pool tainted so only runners schedule there, ARC installed via its Helm chart, and a runner scale set pointed at the org. The scale set scales from zero, so nights and weekends cost nothing but the idle node pool floor. Jobs land, ARC provisions pods, the cluster autoscaler adds spot nodes when the pool is full. Ephemeral mode is on, so every runner is single-use. Secrets come from a short-lived OIDC exchange, never a static token on the node. Public-repo PRs stay on GitHub-hosted runners.
Draw your break-even line first. If concurrent demand keeps pushing you into expensive hosted tiers, self-host the trusted internal work on an autoscaling spot pool, run it ephemeral, and keep untrusted PRs off your own iron. If you are under the line, do not build a runner fleet to save $80 a month. The compute was never the expensive part. The engineer babysitting it is.
Get the latest tutorials, guides, and insights on AI, DevOps, Cloud, and Infrastructure delivered directly to your inbox.
A team was burning 40,000 CI minutes a month and could not say why. Here is how GitHub Actions billing actually works and where the money leaks.
Static keys leak. The question isn't if but how fast you notice and how clean your response runbook is when the pager goes off.
Explore more articles in this category
Every CI/CD platform claims to be fast and easy. The real differences are in pricing, self-hosting, and where each one falls apart at scale. This is the map.
Woodpecker forked Drone when the license changed. Here's how the two compare for small teams and homelabs that just want simple container-native CI.
Buildkite runs the control plane and lets you own the compute; Actions keeps everything close to your repo. Here's how they actually differ once you scale.
Evergreen posts worth revisiting.