Every CI conversation eventually arrives at the same fork: keep paying per-minute for hosted runners, or stand up your own compute and eat the maintenance cost instead. The pitch decks make it sound like a pure math problem — hosted minutes cost $X, a c5.4xlarge costs $Y, do the division. It isn't. The three big platforms — GitHub Actions, GitLab CI, and CircleCI — implement "self-hosted" so differently that the right answer depends less on your budget and more on what your infrastructure already looks like.
I've run all three in production-ish setups over the last year: a GitHub Actions fleet behind actions-runner-controller on EKS, a GitLab Runner pool using the Kubernetes executor, and a CircleCI machine-runner deployment for an air-gapped client. Here's where each one actually wins, and where the "just self-host it" advice quietly sets you up for an on-call rotation nobody budgeted for.
The three implementations aren't the same shape
GitHub Actions self-hosted runners are the simplest to get running and the least opinionated about scale. You register a runner with a token, it long-polls GitHub for jobs, and that's it — no built-in autoscaling. For real elasticity you need actions-runner-controller (ARC), a separate open-source Kubernetes operator GitHub doesn't fully own. ARC works, but you're now debugging two systems when a job hangs: is it GitHub's queue, or is it ARC's runner-pod lifecycle?
# ARC RunnerDeployment — minimal example
apiVersion: actions.summerwind.dev/v1alpha1
kind: RunnerDeployment
metadata:
name: prod-runners
spec:
replicas: 3
template:
spec:
repository: myorg/myrepo
resources:
limits: { cpu: "4", memory: "8Gi" }
GitLab Runner has the most mature autoscaling story because GitLab owns the whole stack. The Kubernetes executor spins up a fresh pod per job natively, no third-party controller required, and the Docker Machine executor (older but still common) autoscales VMs on AWS/GCP directly. Registration is a single gitlab-runner register call, and concurrency, idle timeouts, and machine limits live in one config.toml. It's the runner that feels like it was designed by the same team that built the CI product, because it was.
CircleCI's self-hosted runners split into two flavors — machine runners (install an agent on a persistent VM) and, more recently, container runners (a Helm chart on Kubernetes). The machine runner is deliberately low-abstraction: no autoscaling, no ephemeral pods by default, just a long-lived process pulling jobs. That sounds primitive next to GitLab, but it's the reason CircleCI's self-hosted option is the one I'd trust in a regulated or air-gapped environment — there's no orchestration layer phoning home, and resource classes map cleanly to whatever hardware you hand it.
Where each one actually wins
GitHub Actions + ARC wins when your workloads are already GitHub-native and you have someone comfortable operating Kubernetes operators. It's the right call for teams running dozens of repos with bursty, spiky job volume — ARC's pod-per-job model means idle cost approaches zero between bursts. It's the wrong call if you don't already run Kubernetes; standing up a cluster solely to host CI runners is a lot of surface area for what should be a build server.
GitLab Runner wins on operational simplicity at scale. If you're already on GitLab (or migrating to it), the Kubernetes executor gives you autoscaling without an extra controller to patch, and the Docker Machine executor is still the most battle-tested VM-autoscaling CI runner in the industry — it's been doing this since before Kubernetes was the default answer to everything. The honest tradeoff: GitLab's own hosted runners are priced aggressively enough that self-hosting only pays off once you're consistently burning through the included minutes, which for most teams under ~50 engineers doesn't happen.
CircleCI self-hosted runners win in exactly one scenario often underweighted in "which CI tool" posts: environments where the constraint isn't cost, it's control. No outbound internet from build agents, hardware you can point at a specific compliance boundary, resource classes that map 1:1 to physical machines you already own. If you're evaluating CircleCI's self-hosted option purely to save money versus their cloud tier, run the math first — the machine runner doesn't autoscale, which means you're paying for idle capacity 24/7 unless you build your own scaling wrapper around it. That's a real engineering cost, not a config toggle.
The breakeven nobody puts on the pricing page
Rough numbers, using on-demand cloud pricing and each platform's public rates as of mid-2026: a single c5.2xlarge-equivalent runner costs roughly $0.34/hour on-demand, or about $245/month running 24/7. Hosted CI minutes on all three platforms land somewhere between $0.006 and $0.016 per minute for Linux compute-class jobs, depending on tier. Do the division and self-hosting "wins" once you're consistently running upward of 15,000–25,000 build minutes a month per runner-equivalent — call it a team of 15-20 engineers with a normal PR cadence and a reasonably fast test suite.
But that number ignores three costs the pricing comparison never counts:
- Patching and CVEs. A hosted runner image is someone else's problem. A self-hosted one is now on your team's patch cadence, and CI runners are an underrated attack surface — they have credentials to your source and often your deploy targets.
- Runner drift. Hosted images are rebuilt and versioned by the platform. Self-hosted images drift unless someone owns rebuilding them, and "works on my runner" becomes a real failure mode.
- The 2am page. Autoscaling systems (ARC, GitLab's Docker Machine, or a homegrown CircleCI wrapper) fail in ways that hosted minutes never do — a stuck scale-down leaves you paying for idle VMs, a stuck scale-up leaves your whole team blocked on PRs.
The actual decision framework
Skip the generic "self-host to save money" advice and ask three questions instead: Do you already run Kubernetes for something else? Do you have a compliance or network-isolation requirement that hosted runners can't satisfy regardless of cost? And is your monthly CI spend high enough that the breakeven math above actually clears, after you price in a fractional headcount for runner maintenance?
If the answer to the first two is no and the third is "not really," stay on hosted minutes — GitHub Actions, GitLab, or CircleCI's cloud tier, whichever matches where your code already lives. Self-hosted CI runners are a real capability, not a default. The teams that get burned aren't the ones who self-host — they're the ones who self-host because a pricing calculator told them to, without budgeting for the operator they just adopted.
Top comments (0)