Tenki’s startup program is live: up to $50K in credits and grants.Apply
Runners

Self-Hosted Runner Cost Optimization After GitHub's Pricing Shift

Eddie Wang
Eddie Wangengineering

Share Article:

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.

What $0.002/Minute Actually Costs

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.

Workload Profiling: Which Jobs Still Justify Self-Hosted Runners

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.

Tuning ARC for Cost Efficiency

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.

Right-size your autoscaling bounds

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.

Use spot instances aggressively

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: 60s

The 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.

Optimize job duration directly

With per-minute billing, every minute of wall-clock time costs money. The obvious targets:

  • Docker layer caching. If you're building Docker images in CI, use BuildKit cache mounts backed by a persistent volume or a registry-based cache. Rebuilding unchanged layers is the single biggest waste of CI time.
  • Dependency caching. GitHub's built-in cache action works on self-hosted runners. Use it. A cold npm install can take 2-3 minutes; a cache restore takes seconds.
  • Test parallelization. Splitting a 20-minute test suite across 4 parallel jobs means 4x the platform fee charges but about 5 minutes of wall-clock time each. That's 20 minutes of platform charges vs. 20 minutes in serial. No savings there. Parallelization only helps platform-fee costs if you can reduce total minutes through better test selection or skipping unchanged modules.
  • Path-based filtering. Don't run your full build when only docs changed. Use path filters in your workflow triggers or conditional steps to skip expensive builds that aren't needed.

The Hybrid Strategy: Self-Hosted Where It Matters, Hosted Everywhere Else

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:

  • Self-hosted: GPU workloads (ML training, CUDA builds), builds requiring VPN/internal network access, jobs with enormous cache dependencies (multi-GB build artifacts), compliance-gated workloads
  • GitHub-hosted: Linting, type checking, unit tests, integration tests against managed services, artifact publishing, deployment scripts
  • Third-party runners: Builds that need faster compute than GitHub-hosted but don't need internal network access. Services like Blacksmith, Depot, or Tenki Runners offer faster builds at lower cost than GitHub-hosted runners, which directly reduces your per-minute fee exposure by cutting wall-clock time.

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 VPN

The New Scale Set Client: A Lighter Alternative to ARC

Alongside 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.

When to Evaluate Alternative CI Platforms

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:

  • Your CI spend exceeds $3,000/month and you're already managing runner infrastructure. At this scale, the migration cost to another platform is dwarfed by potential annual savings.
  • You're spending engineering hours on runner maintenance. ARC upgrades, node pool management, debugging flaky runners, managing runner images. If a platform engineer spends 10+ hours a month on this, that's a hidden cost the per-minute fee just made harder to justify.
  • You don't have hard GitHub Actions lock-in. If your workflows are mostly shell scripts wrapped in YAML, porting them to Buildkite pipelines or Dagger functions isn't a rewrite. If you've invested heavily in custom GitHub Actions, reusable workflows, and Actions marketplace integrations, the switching cost is higher.

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.

A Decision Framework

Rather than prescribing one approach, here's a framework for deciding what fits your team:

  1. Export your usage data. Pull 90 days of Actions minutes from GitHub's billing reports. Break it down by workflow and runner type.
  2. Calculate your projected platform fee. Multiply self-hosted runner minutes by $0.002. This is your new unavoidable cost.
  3. Tag each workflow. Does it need self-hosted? Would it be cheaper on GitHub-hosted? Can it be optimized to run in fewer minutes?
  4. Estimate your total cost under each scenario. All self-hosted (status quo + fee), hybrid (self-hosted for must-haves, GitHub-hosted for everything else), and full migration to an alternative.
  5. Factor in engineering time. Runner maintenance, ARC management, debugging. This is real cost even if it doesn't show up in your cloud bill.

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

#github-actions-cost#cicd-cost-optimization#github-actions-runners#cicd-optimization

Recommended for you

What's next in your stack.