.png)
Self-Hosted Runners in 2026: ARC, Security, Cost
In December 2025, GitHub announced a $0.002 per-minute "cloud platform charge" for all Actions workflows, including jobs on self-hosted runners in private repositories. The backlash was immediate. Over 380 comments on r/programming. Hundreds more across r/devops and GitHub's own community discussions. GitHub postponed the change to "re-evaluate their approach," but made clear that the control plane has real costs they intend to recoup.
The postponement bought time. It didn't change the direction. Whether this fee lands in Q3 2026 or early 2027, the pricing signal is clear: running your own hardware no longer means free orchestration. Teams that moved to self-hosted runners specifically to avoid paying for GitHub-hosted minutes need to recalculate.
This article walks through what the fee actually costs at scale, which workloads still justify self-hosted runners, how to tune ARC for lower bills, and when it makes sense to look at alternatives entirely.
Two-tenths of a cent per minute sounds trivial. It isn't. The fee applies to every minute of every job across every workflow in every private repository. It compounds fast.
Here's what the platform charge looks like at three different scales:
Small team (5 developers, ~10,000 minutes/month): $20/month in platform fees. That's $240/year on top of whatever you're spending on EC2, GCP, or bare metal. Annoying but absorbable for most.
Mid-size team (20-30 developers, ~100,000 minutes/month): $200/month, or $2,400/year. This is where it starts to sting. Many teams at this scale moved to self-hosted runners precisely because GitHub-hosted minutes were costing them $1,500-3,000/month. The platform fee claws back a noticeable chunk of those savings.
Large team (100+ developers, ~500,000 minutes/month): $1,000/month, $12,000/year. At this volume, you're also probably running multiple ARC clusters, paying for node autoscaling, and employing at least one person part-time to manage the runner infrastructure. The platform fee is a line item, not a rounding error.
The real cost isn't just the fee itself. It's that the fee is proportional to wall-clock time, not compute consumed. A job that sits idle waiting for an approval gate or an external API still racks up charges. Every minute of a 45-minute build that could have been a 15-minute build with proper caching costs you three times as much in platform fees.
Before the pricing change, self-hosted runners were the default answer to "GitHub Actions is too expensive." Post-pricing-change, you need to be more selective. Not every job benefits from running on your own metal.
Start by exporting your Actions usage data. GitHub provides detailed usage reports that break down minutes by repository, workflow, and runner type. Pull the last 90 days and bucket your workflows into categories:
Jobs that need self-hosted runners. GPU builds, access to internal networks, compliance-mandated data residency, exotic hardware requirements (ARM, FPGA), or builds that depend on large local caches that would be too slow to restore over the network. These stay on self-hosted. There's no alternative.
Jobs that benefit from self-hosted but don't require it. Standard builds that run faster on your hardware because you've got more CPU or warm caches. These are candidates for migration back to GitHub-hosted runners, especially now that hosted runner prices dropped by up to 39% in January 2026. Do the math per-workflow: if a job takes 8 minutes on self-hosted and would take 12 minutes on a GitHub-hosted 4-core Linux runner at $0.006/minute, the hosted cost is $0.072 per run. The self-hosted cost is $0.016 in platform fees plus your infrastructure cost per minute. If your infrastructure cost is above $0.004/minute for that runner, you're not saving anything.
Jobs that are on self-hosted runners for no good reason. Linting, small unit test suites, artifact publishing, notifications. These often landed on self-hosted runners because someone set runs-on: self-hosted as a blanket default. Move them to GitHub-hosted runners immediately. The per-minute savings on these short, lightweight jobs don't justify the platform fee overhead.
If you're keeping self-hosted runners for workloads that genuinely need them, the next lever is reducing the minutes those jobs consume. ARC (Actions Runner Controller) is where most Kubernetes-based teams manage their runners, and its default configuration is not optimized for cost.
ARC's scale set controller scales runner pods based on pending job demand. The key settings are minRunners and maxRunners. A lot of teams set minRunners: 2 or higher "just in case." Pre-pricing-change, idle runners only cost you compute. Post-pricing-change, idle runners cost you compute plus platform fees for any job that happens to land on them and run longer than needed because the runner was cold or oversized.
Set minRunners: 0 unless you have a hard latency requirement for job pickup. The cold-start penalty for spinning up a new runner pod is typically 30-90 seconds. For most CI workloads, that's acceptable.
The platform fee is fixed at $0.002/minute regardless of your infrastructure cost. That makes reducing the infrastructure cost side even more important. Spot instances on AWS typically save 60-90% over on-demand pricing. For CI jobs that are idempotent (and most are, since they run from a clean checkout), spot interruption is a non-issue. The job just restarts.
Configure your ARC node pool to use spot instances with multiple instance type fallbacks. Here's a minimal Karpenter provisioner example:
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: ci-runners
spec:
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot"]
- key: node.kubernetes.io/instance-type
operator: In
values:
- m6i.xlarge
- m6a.xlarge
- m5.xlarge
- c6i.xlarge
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: ci-runners
disruption:
consolidationPolicy: WhenEmpty
consolidateAfter: 60sThe consolidateAfter: 60s setting is critical. It tells Karpenter to reclaim empty nodes after just 60 seconds, which keeps your infrastructure costs tight when there's no pending work.
With per-minute billing, every minute of wall-clock time costs money. The obvious targets:
The strongest post-pricing-change strategy for most teams is a hybrid approach. Keep self-hosted runners for the workloads that require them, and route everything else to GitHub-hosted runners or third-party alternatives.
GitHub's January 2026 price reduction makes this more viable than it was a year ago. Standard Linux 2-core runners dropped from $0.008/minute to $0.006/minute (25% reduction). Larger runners saw even bigger drops, up to 39%. For standard builds that don't need special hardware, the gap between self-hosted and GitHub-hosted is narrower than ever.
A practical split looks like this:
Implementing this doesn't require rewriting your workflows. Use runner labels and runs-on selectors to route jobs to the right runner type:
jobs:
lint:
runs-on: ubuntu-latest # GitHub-hosted, cheap
unit-tests:
runs-on: ubuntu-latest # GitHub-hosted
build-docker:
runs-on: [self-hosted, gpu] # Self-hosted, needs GPU
integration-tests:
runs-on: [self-hosted, internal-net] # Self-hosted, needs VPNAlongside the pricing announcement, GitHub previewed the GitHub Scale Set Client, a lightweight Go SDK for building custom autoscaling solutions without Kubernetes. This matters for cost optimization because ARC's Kubernetes dependency is itself a cost center. You need a cluster, cert-manager, Helm, and someone who understands Kubernetes operators.
The Scale Set Client lets you run autoscaling logic on VMs, containers, or bare metal directly. For teams that don't already have a Kubernetes cluster for other workloads, this could eliminate the overhead of running one just for CI runners. It handles job queuing, secure configuration, and scaling decisions, but you bring your own compute.
It's still on GitHub's roadmap, not generally available yet. But if you're planning infrastructure changes for the back half of 2026, it's worth tracking.
For some teams, the pricing change is the nudge that makes a full platform switch worth evaluating. Not because $0.002/minute is catastrophic on its own, but because it signals a direction: GitHub is going to charge for the control plane, and the rate will only go up.
Here's when a full evaluation makes sense:
The alternatives each have different strengths:
Buildkite uses a hybrid model where pipelines are managed in the cloud but jobs run on your own agents. You keep infrastructure control without paying a per-minute platform fee to Buildkite for self-hosted compute. Pricing is per-user, not per-minute, which makes costs more predictable at high volume.
CircleCI offers both cloud and self-hosted runners with credit-based pricing. Their self-hosted runner tier doesn't carry a per-minute platform charge from CircleCI itself, though you do need a paid plan to access it.
Dagger takes a different approach entirely. It wraps your CI logic in portable containers that can run on any CI platform, including GitHub Actions. If you define your pipeline in Dagger, you can switch the underlying CI orchestrator without rewriting your build logic. It doesn't replace GitHub Actions so much as it reduces your dependency on it.
Rather than prescribing one approach, here's a framework for deciding what fits your team:
For most teams in the 50,000-200,000 minutes/month range, the hybrid approach wins. You cut your self-hosted minutes (and therefore platform fees) by 40-60% while keeping the workloads that genuinely need custom infrastructure.
GitHub's pricing change isn't a crisis. But it is the end of the "self-hosted runners are free" era. The teams that come out ahead are the ones that treat their CI infrastructure with the same cost discipline they apply to production workloads: profile, measure, optimize, and don't pay for things you don't need.
Tags
Recommended for you
What's next in your stack.