GitHub Actions Self-Hosted Runners in 2026: Cost, Security, ARC

Nov 10, 2026 · 13 min · Ajay Kumar

Every few months someone finds the GitHub Actions line on the bill, does quick arithmetic, and announces that we should run our own runners. Sometimes they are right. Often the arithmetic leaves out the engineer who will babysit the fleet, the security model that changes once runners are not thrown away after each job, and the fact that GitHub's prices moved twice in a year. This post is the version of that conversation I would have at a whiteboard: what GitHub Actions self-hosted runners cost in 2026, what happened to the proposed self-hosted platform fee, when self-hosting is a security problem rather than a saving, and how Actions Runner Controller compares with Terraform-on-EC2 and the third-party runner services.

What changed in GitHub Actions pricing for 2026?

Two things were announced together in a GitHub changelog post on 16 December 2025. First, GitHub-hosted runner prices would drop by up to 39 percent on 1 January 2026. Second, from 1 March 2026 a new $0.002 per minute "cloud platform charge" would apply to self-hosted runner usage, counting toward plan minutes.

The first part happened. The second did not: the same changelog now says GitHub is postponing the self-hosted billing change to re-evaluate its approach, with no new date. The Register covered the walk-back the day after the announcement. As of today, GitHub's runner pricing reference lists hosted per-minute rates and no self-hosted charge at all.

So self-hosted minutes are free, hosted minutes got cheaper, and a $0.002 fee was floated once and could be floated again. If your plan only works because self-hosted is free, rerun it with $0.002 added.

Current hosted rates, all from GitHub's pricing reference (the self-hosted row is from the changelog above):

Runner x64 per minute arm64 per minute
Linux 2-core $0.006 $0.005
Linux 4-core $0.012 $0.008
Linux 8-core $0.022 $0.014
Linux 16-core $0.042 $0.026
Linux 64-core $0.162 $0.098
Windows 2-core $0.010 $0.010
macOS 3 or 4-core $0.062 n/a
Self-hosted (any size) $0 today; $0.002 postponed $0 today

Linux arm64 is cheaper at every size, so try ubuntu-24.04-arm before you self-host anything. And price per core is roughly flat, so a bigger runner that finishes proportionally faster costs about the same. Bigger runners are often the cheapest fix for slow CI; the rest of that toolbox is in how to speed up GitHub Actions CI.

When do self-hosted runners actually make sense?

Cost is the reason people give, and rarely the best one. Self-hosting is clearly right for:

  • Private network access. Tests need a database, artifact store or cluster API that is not on the internet.
  • Hardware GitHub does not sell you. Specific GPUs, huge memory, a fixed CPU generation for benchmarks, local NVMe.
  • Warm caches. Docker layer and Bazel caches next to the runner (carefully, see the security section).
  • Compliance. Builds must run in a specific region, account or tenancy.
  • Volume. The hosted bill is a real line item and a platform team already runs compute.

It is clearly wrong for a small team on a few thousand minutes a month, when nobody wants to own runner images, or for a public repository. The runner limits page is a reminder that self-hosting buys you some headroom (jobs can run for up to five days rather than six hours), but it also binds you to GitHub's queue rules: a job that sits in the queue for 24 hours is cancelled, and a runner group holds at most 10,000 runners at once.

There is also an operational tax. GitHub's self-hosted runner reference says that if you disable automatic updates you must install a new runner release within 30 days, or the service stops queueing jobs to that runner. The runner ships often: v2.338.2 is the current release as I write, published on 26 October 2026. That is a monthly image rebuild forever, plus OS patches and disk cleanup. That platform work does not go away because an agent now writes the workflow files.

Why are self-hosted runners dangerous on public repositories?

GitHub's secure use reference is blunt: self-hosted runners "should almost never be used for public repositories", because anyone can open a pull request and compromise the environment. A fork PR can change the workflow, and it runs with whatever network access, cached credentials and metadata endpoint your machine has.

This is not theoretical. In January 2024, researchers John Stawinski and Adnan Khan showed that PyTorch's self-hosted runners could be reached from a fork pull request, with a path to release credentials and secrets, and they found the same pattern across other large projects. In November 2025 the Shai-Hulud 2.0 npm worm turned the idea around: it used stolen GitHub tokens to register infected developer machines as self-hosted runners named SHA1HULUD, then ran attacker commands through a workflow triggered by opening a Discussion. A self-hosted runner is a remote shell with a YAML front end.

The controls that matter, in rough order of value:

  1. Ephemeral runners. One job, then the runner is deregistered and the machine or pod is destroyed. GitHub's docs describe just-in-time (JIT) runners that "perform at most one job" and also warn that re-using hardware for JIT runners can still leak information. Fresh VM or fresh pod each time is the goal.
  2. Runner groups with explicit repository lists. The runner group API defaults allows_public_repositories to false, and you can restrict a group to selected repositories and even to specific workflow files with restricted_to_workflows. Runner groups on organisations require the Team plan or higher.
  3. Fork PR approval. Require approval before workflows from outside contributors run, configurable per repo, org or enterprise.
  4. No long-lived cloud keys on the runner. Use OIDC federation instead, as in the OIDC guide for AWS, GCP and Azure, and block the instance metadata endpoint from job containers.
  5. Egress control. A build that only needs your registry and package mirrors should not be able to reach the rest of the internet.

The broader supply-chain picture, including tag hijacking and pinning actions by SHA, is in GitHub Actions security in 2026. For runners: if it is persistent and shared, it will eventually run something you did not intend.

Actions Runner Controller vs Terraform on EC2 vs runner services

There are three serious ways to run ephemeral self-hosted runners at scale in 2026.

Actions Runner Controller (ARC) is GitHub's own Kubernetes operator. The current "runner scale set" mode, shipped as the gha-runner-scale-set-controller and gha-runner-scale-set Helm charts, is at 0.15.1, released 23 October 2026. A listener pod long-polls GitHub for jobs assigned to a scale set, the controller creates one ephemeral runner pod per job, and the pod is deleted when the job finishes. It scales to zero by default. Since 0.14.0 in March 2026 a scale set can carry multiple labels, and ARC talks to GitHub through the public actions/scaleset Go library, which means you can build your own autoscaler on the same client. The catch: you now run Kubernetes for CI, and Docker builds need a privileged sidecar or the Kubernetes container mode.

Terraform on EC2, almost always via the community terraform-aws-github-runner module (v8.1.0 on 3 November 2026), wires a GitHub App webhook to API Gateway, Lambda and SQS, then launches EC2 instances (spot by default) and scales to zero. Ephemeral one-job instances need enable_ephemeral_runners = true, which is off by default. Each job gets a full VM, better isolation than a pod and boring Docker builds. You pay with slower cold starts and a pile of Lambdas.

Runner services fall into two groups. Bring-your-own-cloud products such as RunsOn install into your AWS account and charge a flat licence (from EUR 300 a year for under 50,000 jobs a month) while you pay AWS directly. Fully managed services such as Blacksmith, Ubicloud and Depot run the machines themselves and bill per minute; you change runs-on and you are done. To GitHub they are self-hosted runners, which is why the $0.002 proposal mattered so much to them.

Option Isolation per job Scale to zero Ops burden Cost shape Source
ARC on Kubernetes Pod (privileged if DinD) Yes, default Cluster, images, upgrades Your nodes ARC releases
terraform-aws-github-runner EC2 VM, spot Yes Lambdas, AMIs, quotas Your EC2 Module repo
RunsOn (BYO AWS) EC2 VM Yes Low; vendor stack in your account AWS cost plus EUR 300 to 3,600 a year RunsOn pricing
Blacksmith Vendor VM n/a None $0.004 a minute Ubuntu x64, 3,000 free minutes Blacksmith pricing
Ubicloud Vendor VM n/a None $0.00125 a minute standard-2 Ubicloud pricing
Depot Vendor VM n/a None $0.006 a minute 2 vCPU after included minutes Depot pricing

How much can you save, honestly?

Per-minute list prices for a 2 vCPU Linux x64 runner make a tidy chart, as long as you remember what is missing from it.

List price per minute, 2 vCPU Linux runner (US dollars, November 2026) GitHub-hosted x64 $0.006 Depot, after included $0.006 GitHub-hosted arm64 $0.005 Blacksmith Ubuntu x64 $0.004 Ubicloud standard-2 $0.00125 RunsOn, AWS spot $0.0009 plus licence Postponed GitHub fee $0.002 proposed, not charged Excludes engineer time, NAT and egress, cache storage, idle nodes and image builds. Vendor vCPU and RAM differ between vendors. Hosted rates from GitHub's pricing reference.
Per-minute list prices from GitHub's runner pricing reference and the RunsOn, Blacksmith, Ubicloud and Depot pricing pages, checked 10 November 2026. The dashed bar is the self-hosted platform charge GitHub announced for March 2026 and then postponed.

Take a team burning 100,000 Linux 2-core minutes a month. On GitHub-hosted x64 that is $600. Switching to arm64 hosted runners takes it to $500 with no new infrastructure. On Ubicloud's list price the same minutes are $125, and on RunsOn's spot example about $90 of EC2 plus roughly $7 a month of control plane and a EUR 300 annual licence. Real savings, but hundreds of dollars a month, which is less than an engineer's day, and an ARC or Terraform fleet takes at least that once you count upgrades, broken nodes and stuck queues.

The arithmetic changes at ten times that volume, or when the jobs are large. A 16-core hosted runner is $0.042 a minute; the equivalent spot instance in RunsOn's own comparison is $0.0037. If your monorepo build runs on big machines all day, self-hosting in some form usually wins, and the remaining question is whether you want to operate it or rent it. The comparison of GitHub Actions, GitLab CI and Jenkins has the cross-vendor numbers if you are also weighing a bigger move.

Also remember the price is per wall-clock minute: faster disks and warm caches that shorten a build beat any discount on the rate.

Which should you pick: ARC or Terraform EC2 runners?

If you already run Kubernetes well and jobs are mostly checkout, install, test, ARC is the natural fit. GitHub maintains it, it scales to zero, it exposes Prometheus metrics like gha_assigned_jobs and gha_idle_runners, and Karpenter or cluster autoscaler handles the nodes. The friction is Docker. Docker-in-Docker needs a privileged container, which undoes much of the isolation you wanted, and the Kubernetes container mode needs persistent volumes or the newer kubernetes-novolume mode plus RBAC that lets the runner create pods. ARC also cannot be upgraded with a plain helm upgrade: GitHub's deployment docs say to uninstall scale sets, uninstall the controller, update CRDs and reinstall, because Helm does not upgrade CRDs.

If builds are Docker-heavy or CI should not share a cluster with production, the Terraform EC2 module gives each job its own instance.

If neither sounds like a good use of a quarter, a runner service is legitimate; review it like any vendor that runs your code with your secrets. (Disclosure: I run PandaStack, a Firecracker microVM cloud for code execution, so I have opinions about VM isolation, but it is not a CI runner service and I am not selling one here.)

I wrote a step-by-step setup for the ARC route, including GitHub App auth, both container modes, runner groups and the stuck-queue fixes, in how to set up Actions Runner Controller on Kubernetes. Here is the minimal shape of a scale set values file, so you can see how little ARC needs to get started:

# values.yaml for gha-runner-scale-set chart 0.15.1
githubConfigUrl: "https://github.com/acme-org"   # org-level scale set
githubConfigSecret: arc-github-app               # Secret with github_app_id, github_app_installation_id, github_app_private_key
runnerScaleSetName: "arc-linux"                   # what workflows put in runs-on
runnerGroup: "arc-private"                        # group must already exist
minRunners: 0                                     # scale to zero when idle
maxRunners: 20                                    # cap on concurrent jobs
containerMode:
  type: "dind"                                    # privileged Docker sidecar; or "kubernetes"

FAQ

Are GitHub Actions self-hosted runners free in 2026?

Yes, as of November 2026. GitHub announced a $0.002 per minute platform charge for self-hosted runners starting 1 March 2026, then postponed it with no new date. GitHub's runner pricing reference currently lists no self-hosted charge, but you still pay for your own compute.

Can I use self-hosted runners on a public repository?

GitHub says they should almost never be used with public repositories, because any fork pull request can run code on the runner. If you must, use ephemeral runners on an isolated network, require approval for outside contributors, and restrict the runner group to that repository.

What is the difference between ephemeral and persistent self-hosted runners?

A persistent runner stays registered and runs job after job on the same machine, so files, caches and credentials from one job can leak into the next. An ephemeral runner, registered with --ephemeral or as a just-in-time runner, takes one job and is then deregistered and destroyed. ARC scale sets always use ephemeral runners; the Terraform EC2 module supports them but needs enable_ephemeral_runners = true.

Is Actions Runner Controller production ready?

The runner scale set mode is GitHub's supported approach for Kubernetes and is actively released, at 0.15.1 as of October 2026. The version number is below 1.0, upgrades require reinstalling rather than helm upgrade, and GitHub also publishes experimental rewritten charts that it says are not for production. Pin chart versions and test upgrades in staging.

This quarter, if I owned a GitHub Actions bill, I would first move every Linux job that tolerates it to arm64 hosted runners and right-size the slow ones onto bigger machines, because that is a one-line change. Then I would look at the remaining minutes by workflow. If the expensive part is a handful of large builds or jobs that need the private network, I would put those on ephemeral runners (ARC if the team already runs Kubernetes, EC2 VMs if the jobs are Docker-heavy) inside a runner group locked to private repositories, and leave everything else on GitHub-hosted. And I would keep a line in the budget for the $0.002 fee, because a postponed price is not a cancelled one.

Related: GitHub Actions Security in 2026: 76 Hijacked Tags and the Fixes That Work, How to Speed Up GitHub Actions CI: Caching, Sharding, Docker (2026) and How to Set Up Karpenter on EKS: NodePools, Spot and Consolidation (2026).

Spot an error or something out of date? Email me and I will fix it and log it on the corrections page.

I'm Ajay Kumar — I build and operate PandaStack, an open-source Firecracker microVM cloud for AI agents, and write about the infrastructure and tooling around it.

Need this kind of infrastructure work? See what I do or email hello@ajayk.sh.


Related