# ARC vs managed runners for GitHub Actions

> ARC vs managed runners: what Helm actually installs, the list GitHub Support will not help with, and the one line each option changes in a job.

Source: https://latchkey.dev/learn/runners/actions-runner-controller-vs-managed-runners  
Updated: 2026-09-21

ARC vs managed runners is a staffing question before it is a pricing question: GitHub recommends that organizations adopting Actions Runner Controller have staff with expert-level knowledge of container orchestration, and its support page lists cluster setup, networking, storage provisioners and cluster policy as outside what GitHub Support will help with. A managed runner moves the same jobs with an app install and one label, and leaves nothing behind for anyone to operate.

Actions Runner Controller is a Kubernetes operator that creates self-hosted runners as pods. You install it with Helm, declare a runner scale set, and jobs select it by name in `runs-on`. The runners are ephemeral: a pod is created for a job, registers itself, runs the job and is deleted.

Managed runners are the same `runs-on` change pointed at somebody else's fleet. There is no controller, no node pool and no Helm release; the setup is a GitHub App install and a label. Latchkey is one of several vendors selling that shape, and this page prices it against ARC rather than against the other vendors.

The two options are not close on any axis except the one everybody compares, which is the per-minute rate. ARC does not have one. Everything below was read from GitHub's own ARC documentation, from the actions/actions-runner-controller repository and from latchkey.dev/pricing on 21 September 2026.

## Comparison

|  | ARC on your Kubernetes cluster | Managed runners (Latchkey) |
| --- | --- | --- |
| What you pay per minute | Nothing to ARC itself, which is Apache 2.0. The bill is the cluster: nodes, disks, load balancers and whatever your provider charges for the control plane | **$0.0025** a minute on `latchkey-small` at 2 vCPU with 8 GB, up to $0.02 at 16 vCPU |
| Free tier | The software is free. Idle capacity is not: a node pool sized for your peak bills through the night | 2,000 included minutes a month on the $5 Developer plan, 4,000 on the $19 Launch plan, 6,000 on the $49 Scale plan, and a 30-day trial with no card |
| Billing granularity | Node-hours, from your cloud provider. The gap between one job ending and the next starting is billed at the same rate as the job | Per minute, and only while a job is running |
| Sizes and OS | Whatever your node pools have, including arm64 and Windows nodes. The runner image is a container you pick and maintain | 2 to 16 vCPU, Ubuntu on x86_64 only |
| Cache | `actions/cache` still writes to GitHub across the public internet, under the same 10 GB per repository that hosted runners get | `latchkey-dev/cache-action@v1` in place of `actions/cache`, carrying the same `path`, `key` and `restore-keys` |
| Docker builds | Docker in Docker, or the container hooks in ARC's Kubernetes mode. GitHub Support names both as out of scope | `latchkey-dev/docker-cache-action@v1`, one step wrapping `docker buildx build` |
| Concurrency limits | Your cluster capacity, under GitHub's self-hosted ceilings: 10,000 runners registered at once per runner group, and 1,500 runner registrations per five minutes | 20 busy runners per workspace by default, raisable on request |
| Where jobs run | Your cluster, in your account, on your network | AWS us-east-1 |
| Self-healing or retries | The EphemeralRunner controller retries creating a **runner pod** up to five times. That is pod creation, not your job: a job that died on a registry timeout is still a failed job | Transient failures diagnosed and retried inside the run |
| Setup change required | A Helm release for `gha-runner-scale-set-controller`, another for each `gha-runner-scale-set`, then `runs-on: <scale set name>` per job | A GitHub App install, then a `runs-on` label |

## What Helm actually puts in the cluster

ARC is two charts. The first deploys a controller manager pod. The second creates an `AutoScalingRunnerSet`, and its controller calls GitHub to fetch or create a runner scale set before deploying a second pod, the Runner ScaleSet Listener. That listener opens an HTTPS long poll to the Actions service and sits idle until a Job Available message arrives.

From there the path is worth knowing, because it is where the queue time goes. The listener acknowledges the message, patches the Ephemeral RunnerSet with a desired replica count through the Kubernetes API, and the EphemeralRunner controller requests a just-in-time configuration token and creates a pod. The runner application in that pod registers itself with the token, opens its own long poll, and only then receives the job. Every one of those steps happens after the job was queued.

Two numbers from that path are worth writing down before you commit. If the pod's status is failed, the controller retries up to five times. And if no runner accepts a job, the Actions service unassigns it after 24 hours, which is the self-hosted queue ceiling rather than an ARC one.

## The support boundary, in GitHub's own words

There are two ARCs. The project was adopted by GitHub and released as Autoscaling Runner Sets; the older autoscaling modes are now legacy. GitHub's support page is explicit about what that means: GitHub only supports the latest Autoscaling Runner Sets version, and support for the legacy ARC is provided by the community in the repository only. If you find a tutorial older than the split, check which one it installs.

The same page recommends that organizations have staff with expert-level knowledge of container orchestration, then lists what falls outside GitHub Support: the legacy version, installing and maintaining dependencies, template spec customization, container orchestration including Kubernetes and OpenShift setup, networking and building images in Docker in Docker, applying cluster policies, managed provider configuration, runner container hooks with the Kubernetes mode, installation tooling other than Helm, storage provisioners and PersistentVolumeClaims, and best practices such as metrics servers and image caching.

Read that list as a job description rather than as boilerplate, because it is the honest inventory of what your team picks up, and it is why the stable adoption pattern is a platform team already running Kubernetes for something else. ARC is only supported on GitHub Enterprise Server 3.9 and later.

## Speed

Not benchmarked yet. The runner benchmark report will replace this paragraph with one workload measured on both, and until it exists no number here would mean anything, because ARC's wall clock is a property of your node pool rather than of ARC.

What the documented path does support is a warning about the shape of the delay. A managed vendor hands you a warm machine; ARC has to create a pod, pull the runner image, register a just-in-time runner and open a second long poll before the first step of your job starts. On a cluster with spare capacity that is fast. On a cluster that has to scale up a node first, the job waits for the node, and neither GitHub nor ARC has anything to say about how long that takes on your provider.

## Cost, and the number this page will not invent

Ten thousand Linux minutes a month on a 2 vCPU runner costs $25 on Latchkey at $0.0025 a minute, plus $5 to $49 for the plan, against $60 at GitHub's $0.006 list rate for the same size. Those are rate cards and the arithmetic is one multiplication.

ARC has no rate card, and the honest answer is that its cost depends on facts this page does not have: which instance types your node pools use, what your reserved or spot mix is, how long nodes sit warm between jobs, and what your provider charges for the control plane. A page that produced a single dollar figure for that would be guessing, and guessing is what the old version of this hub did. The usable shape is that ARC converts a per-minute bill into a utilization problem, and utilization is the variable: a node pool that is busy most of the working day is cheap per job, and the same pool at 10% utilization is the most expensive option on this page.

## Reliability: two different things can go wrong

On a managed runner the failures you see are GitHub's and the vendor's. On ARC you inherit a third category, which is your own cluster: an evicted pod, a node that scaled down mid-job, a PersistentVolumeClaim that would not bind, an image pull that hit a registry rate limit. Those arrive as red builds in the Actions UI with no indication that the cause was infrastructure, which is why the ARC troubleshooting guide exists and why the support scope list above matters.

It is also why the five-retry number in the table needs reading carefully. The EphemeralRunner controller retrying pod creation five times means ARC keeps trying to hand the Actions service a runner. It does not mean a job that ran and failed gets another attempt. Conflating those two events is the most common way teams over-estimate what the controller does for reliability.

## Why there is no recorded run on this page

We could stand up a cluster, install both charts and publish a log. It would prove that a cluster we configured behaved a certain way on the day we configured it, and every quantity a reader actually needs, from node scale-up time to image pull latency, would be a property of our node pool rather than of ARC. The evidence that decides this comparison is a support-scope document and a Helm chart, and both are linked below where you can read them yourself.

## When each one is right

- **ARC** when you already run Kubernetes in production, have the team that operates it, and want runners inside your own network for data residency or private service access.
- **ARC** when the runner needs something a managed vendor cannot sell you: a GPU node pool you already own, a Windows node, an arm64 node, or an image with licensed software baked in.
- **A managed runner** when nobody on the team wants to own a controller, and the workload is ordinary Linux CI. The adoption cost is an app install; the exit cost is changing one line back.
- **Neither, yet**, when the complaint is that CI is slow and nothing has been profiled. Caching and skipping unchanged work are free, and a Kubernetes migration is the most expensive way to not fix a cache miss.

## Switching: the exact diff

An ARC scale set is referenced by the installation name you gave the Helm release, so the label is whatever your cluster calls it. A vendor label is published and identical for everyone. Both are one line per job, and both can sit in the same workflow while you compare them.

```.github/workflows/ci.yml
jobs:
   build:
-    runs-on: ubuntu-latest
+    runs-on: arc-runner-set          # the Helm installation name, not a fixed label

   test:
-    runs-on: ubuntu-latest
+    runs-on: latchkey-small          # 2 vCPU, 8 GB, $0.0025 a minute

   lint:
     runs-on: ubuntu-latest           # left alone: it finishes in 40 seconds
```

## The verdict

ARC is the right answer for a narrower set of teams than its popularity suggests, and GitHub has written down who they are: organizations with staff who have expert-level knowledge of container orchestration. If that describes you, ARC puts runners in your own network at your own cluster's cost and the support boundary is a known quantity. If it does not, you are proposing to run a Kubernetes operator so that a CI job can start, and the list of things GitHub Support will not help with is the list of things you will be debugging at 6pm. A managed runner is the same `runs-on` edit with nothing behind it, at $0.0025 a minute against GitHub's $0.006 at 2 vCPU, and the way back out is the same line.

## FAQ

### Does GitHub support Actions Runner Controller?

Partly, and the boundary is published. GitHub supports only the latest Autoscaling Runner Sets version; the legacy community-maintained ARC is supported in the repository by the community. Even on the supported version, cluster setup, networking, storage provisioners, cluster policies, Docker in Docker image builds and any installation tooling other than Helm are outside GitHub Support's scope.

### Is ARC cheaper than managed runners?

It has no per-minute rate, which is not the same thing. ARC converts a metered bill into a utilization problem: your node pool bills whether or not a job is running, so a busy cluster is cheap per job and an idle one is expensive. Add the engineer who maintains the controller, the runner image and the node pools before comparing it to $0.0025 a minute.

### Does ARC retry failed jobs?

No. The EphemeralRunner controller retries creating a runner pod up to five times when the pod fails to come up. That is about delivering a runner to the Actions service, not about your job: a job that started and failed on a flaky dependency is a failed job, on ARC exactly as on a GitHub-hosted runner.

### What Kubernetes version and GitHub plan does ARC need?

GitHub documents ARC as supported on GitHub Enterprise Server 3.9 and greater, and the Autoscaling Runner Sets mode works with both Kubernetes and OpenShift. The practical prerequisite is not a version number, it is having somebody who can operate the cluster it runs on.

## References

- [GitHub: Actions Runner Controller components and the scale set message path](https://docs.github.com/en/actions/concepts/runners/actions-runner-controller)
- [GitHub: scope of support for Actions Runner Controller](https://docs.github.com/en/actions/concepts/runners/support-for-actions-runner-controller)
- [actions/actions-runner-controller, including the legacy autoscaling modes](https://github.com/actions/actions-runner-controller)
- [GitHub Actions limits, including the self-hosted runner ceilings](https://docs.github.com/en/actions/reference/limits)
- [Latchkey runner sizes, rates and plans](https://latchkey.dev/pricing)

---

Latchkey runs CI/CD that repairs its own failures. Agent entry points: https://latchkey.dev/agent.txt, https://latchkey.dev/openapi.json, https://latchkey.dev/llms.txt
