Actions Runner Controller (ARC) deploys and autos-scales self-hosted GitHub Actions runners inside a Kubernetes cluster. It suits teams that need control of runner images, pod resource limits, network access, and cluster utilization rather than relying on hosted runners. ARC supports Linux-based Kubernetes nodes only. section 6.5.1
Runners are ephemeral by default: a container executes one job and exits, while Kubernetes ReplicaSets create replacements. This limits leftover job state and supports cleaner, safer execution environments. Configure scale sets at enterprise, organization, or repository scope; separate teams can use distinct images, rules, namespaces, and network boundaries. section 6.5.1 section 6.6
Choose a scaling approach that matches workload behavior:
- Scale on a schedule when demand is predictable. section 6.5.1
- Scale from the percentage of busy runners, adjusting capacity by a configured runner count. section 6.5.1
- Create runners just in time from a GitHub webhook when a pull request or queued workflow job triggers demand. section 6.5.1 section 6.6
Use a GitHub App for organization- or repository-level runner registration. Store its installation ID and PEM private key as a Kubernetes secret; ARC obtains fresh installation tokens to register and deregister runners. A PAT is required for enterprise-level runners, but is otherwise discouraged because it inherits a user’s broad access and can break when that user leaves. section 6.5.2
Plan monitoring separately. Native runner views and APIs expose availability, busy status, and labels, but not ARC utilization metrics. Monitor Kubernetes pod counts, queue time, and scale-up/down events; alert on rapid oscillation or insufficient labeled capacity. Bursty workloads and large matrix runs can lengthen queues or cause uncontrolled expansion unless scaling rules handle spikes deliberately. section 6.5.3 section 7.2.1
Table 7.1 Scaling out runners (view table figure)
| Duration (mins) |
Action |
Number of runners |
Jobs queued |
Jobs running |
Percentage busy |
|---|---|---|---|---|---|
| 0 |
100 jobs get queued |
50 |
50 |
50 |
100% |
| 10 |
Scale out by 25% |
62 |
38 |
62 |
100% |
| 20 |
Scale out by 25% |
77 |
23 |
77 |
100% |
| 30 |
Scale out by 25% |
96 |
4 |
96 |
100% |
| 40 |
Scale out by 25% |
120 |
0 |
100 |
83% |
This scaling sequence shows why utilization-based expansion can leave queued jobs for several intervals: capacity rises from 50 to 120 runners while the queue drains from 50 to zero. section 7.2.1
