GitHub Actions works well for normal CI/CD workloads, but large engineering teams often need more control over where their runners execute jobs.
Some workloads need Kubernetes because the organization already operates a Kubernetes platform, needs isolated build environments, or wants runners to scale with demand.
GitHub Actions Runner Controller, commonly called ARC, provides a way to run GitHub Actions self-hosted runners on Kubernetes.
The release of ARC 0.15 brings changes that Kubernetes and DevOps teams should understand before upgrading or designing a new runner environment.
The important point is that ARC is not simply another Kubernetes application. It connects two systems with very different responsibilities:
GitHub Actions
|
v
Runner Controller
|
v
Kubernetes
|
+-- Runner Pods
+-- Jobs
+-- ScalingUnderstanding that relationship makes it easier to troubleshoot runner registration, scaling, scheduling, and job execution.
What Is Actions Runner Controller?
Actions Runner Controller is a Kubernetes operator that manages self-hosted GitHub Actions runners.
Instead of installing a runner manually on a virtual machine, you can allow Kubernetes to create runner pods when GitHub Actions jobs need them.
A simplified architecture looks like this:
GitHub
|
v
GitHub Actions Job
|
v
Actions Runner Controller
|
v
Kubernetes
/ | \
/ | \
Runner Runner Runner
Pod Pod PodThis is useful for organizations that already use Kubernetes and want CI workloads to follow their existing infrastructure model.
Why Run GitHub Actions Runners on Kubernetes?
A traditional self-hosted runner often looks like this:
GitHub
|
v
Virtual Machine
|
+-- Runner
+-- Tools
+-- SDKs
+-- Build EnvironmentThis works, but the machine remains allocated even when no workflow is running.
With Kubernetes:
GitHub
|
v
Kubernetes
|
+-- Runner Pod
+-- Runner Pod
+-- Runner PodRunner capacity can be created and removed according to workload.
This can be particularly useful for organizations with highly variable CI demand.
What Changed in ARC 0.15?
The exact behavior developers see during an upgrade depends on how their existing ARC installation is configured, but the 0.15 release is part of the continued transition toward the current Actions Runner Controller architecture.
One important area to understand is the distinction between the controller itself and the runner scale sets it manages.
The current architecture uses:
Actions Runner Controller
|
v
Runner Scale Set
|
v
Runner PodsThe scale-set model is designed to make runner provisioning and scaling more Kubernetes-native.
Before upgrading, teams should review their existing manifests, custom resources, Helm configuration, and GitHub authentication setup rather than assuming that an older ARC installation can simply be replaced with a newer chart.
Runner Scale Sets
Runner scale sets are central to modern ARC deployments.
Instead of thinking about each runner individually, you define a pool of runners:
Runner Scale Set
|
+-- Runner 1
+-- Runner 2
+-- Runner 3
+-- Runner 4GitHub Actions jobs can then target that runner scale set.
For example, a workflow can specify a self-hosted runner label associated with the scale set:
jobs:
build:
runs-on: arc-runner-set
steps:
- uses: actions/checkout@v4
- name: Build
run: dotnet build --configuration ReleaseThe exact runner name depends on your ARC configuration.
The key idea is that the workflow requests a runner from a pool rather than identifying a specific machine.
Why Kubernetes Teams Care About This
Kubernetes gives teams several capabilities that are useful for CI workloads.
Isolation
Each job can execute inside its own runner environment.
Scheduling
Kubernetes can decide where runner pods should execute based on resources and scheduling rules.
Scaling
Runner capacity can grow when workload increases and shrink when demand decreases.
Infrastructure Integration
Teams can use Kubernetes concepts such as namespaces, resource limits, node selectors, taints, and tolerations.
Installing ARC With Helm
ARC deployments are commonly managed through Helm.
A simplified installation pattern looks like this:
helm repo add actions-runner-controller \
https://actions-runner-controller.github.io/actions-runner-controller
helm repo updateThe exact chart and configuration should match the ARC version and architecture you are deploying.
Production installations should pin versions rather than relying on an automatically changing latest release.
For example:
helm upgrade --install arc \
<chart> \
--namespace arc-system \
--create-namespace \
--version <pinned-version>Pinning the version makes upgrades predictable.
Authentication
ARC needs a way to communicate with GitHub.
Organizations typically use GitHub App authentication or another supported authentication mechanism.
The credentials should not be placed directly inside a Git repository.
A Kubernetes Secret can provide the required configuration:
apiVersion: v1
kind: Secret
metadata:
name: github-app-secret
type: Opaque
stringData:
github_app_id: "<app-id>"
github_app_installation_id: "<installation-id>"
github_app_private_key: "<private-key>"The actual secret values should come from a secure secret-management process.
For production environments, consider integrating Kubernetes with your organization's existing secret-management platform rather than manually maintaining sensitive credentials in YAML files.
Resource Requests and Limits
A runner is still a workload running on Kubernetes.
That means CPU and memory planning matters.
For example:
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "2"
memory: "4Gi"The correct values depend on the workloads running in your CI environment.
A small documentation build may require very little memory.
A .NET application with large builds, tests, containers, or native dependencies may need substantially more.
Do not copy resource values from another environment without measuring your workloads.
Runner Pods and Ephemeral Workloads
One of the advantages of Kubernetes-based runners is that jobs can run in isolated environments.
A simplified lifecycle looks like this:
Job Waiting
|
v
Runner Pod Created
|
v
Runner Registers
|
v
Job Executes
|
v
Job Completes
|
v
Runner Environment RemovedThis can reduce the risk of one build leaving unexpected files or state for another build.
It also makes the runner environment easier to reproduce.
Why Ephemeral Runners Matter
Consider a traditional long-lived runner:
Runner VM
|
+-- Build A
+-- Cached files
+-- Build B
+-- Temporary files
+-- Build COver time, the machine can accumulate state.
An ephemeral runner has a different lifecycle:
Build A
|
v
Runner Pod
|
v
Destroyed
Build B
|
v
New Runner Pod
|
v
DestroyedThis provides cleaner isolation, although teams still need to consider container images, caches, credentials, and artifacts carefully.
Caching Build Dependencies
Ephemeral runners can make caching more important.
For example, a .NET workflow might use dependency caching:
steps:
- uses: actions/checkout@v4
- name: Restore
run: dotnet restore
- name: Build
run: dotnet build --no-restoreIf the workflow downloads large dependencies on every execution, build time and network usage can increase.
Caching can help, but the cache design should not compromise correctness.
Never assume that cached build artifacts are always safe to reuse.
Kubernetes Scheduling
Runner pods compete for Kubernetes resources just like other workloads.
You can use scheduling features to place CI workloads on appropriate nodes.
For example:
nodeSelector:
workload: ciA dedicated node pool can keep build workloads separate from application workloads.
This can be useful in clusters where production applications and CI jobs share infrastructure.
The architecture could look like:
Kubernetes Cluster
|
+-- Application Nodes
|
+-- CI Nodes
|
+-- Runner Pod
+-- Runner PodThis helps avoid a large build consuming resources needed by a production service.
Common Mistakes
Treating Runners Like Normal Application Pods
CI runners have different lifecycle and security requirements.
A runner may execute arbitrary code from a repository.
That makes isolation particularly important.
Giving Runners Excessive Kubernetes Permissions
A build job does not automatically need permission to control the Kubernetes cluster.
Avoid giving runner workloads broad service-account privileges.
Running Untrusted Pull Requests on Sensitive Runners
A pull request can contain arbitrary build instructions.
Be careful when workflows execute code from forks or untrusted repositories.
Ignoring Resource Limits
Without appropriate resource requests and limits, a large build can consume too much cluster capacity.
Upgrading Without Reviewing the Architecture
ARC has evolved significantly.
Before upgrading, review the current deployment model, Helm values, custom resources, authentication configuration, and runner scale-set setup.
Troubleshooting Runner Registration
If runner pods are running but jobs are not being assigned, check the complete chain:
GitHub
|
v
Authentication
|
v
ARC Controller
|
v
Runner Scale Set
|
v
Runner Pod
|
v
GitHub Actions JobA problem at any stage can prevent the workflow from running.
Start with controller logs:
kubectl logs \
-n arc-system \
deployment/<controller>Then inspect runner resources:
kubectl get pods -n arc-systemAnd check events:
kubectl get events \
-n arc-system \
--sort-by=.lastTimestampKubernetes events can reveal scheduling, image-pull, permission, or resource problems.
Troubleshooting Pods That Never Start
If runner pods remain pending, check:
kubectl describe pod <runner-pod> \
-n arc-systemLook for:
Insufficient CPU
Insufficient memory
Node selector mismatch
Taints
Missing tolerations
Image pull errors
Service-account problems
For example, a runner requiring:
resources:
requests:
cpu: "4"
memory: "8Gi"cannot start if the cluster has no node with enough available capacity.
Advantages of ARC
Kubernetes-Native Runner Management
Teams can manage runners using familiar Kubernetes concepts.
Dynamic Scaling
Runner capacity can follow CI workload instead of requiring permanent machines.
Isolation
Ephemeral runner environments can reduce state leakage between jobs.
Infrastructure Reuse
Organizations can use existing Kubernetes clusters, node pools, policies, and observability systems.
Disadvantages and Trade-Offs
More Operational Complexity
You now have to operate both GitHub Actions and Kubernetes infrastructure.
Cluster Capacity Matters
A Kubernetes cluster without enough CPU or memory cannot create runners when demand increases.
Security Requires Care
Runner workloads execute CI code and should not automatically receive broad access to the Kubernetes environment.
Debugging Can Span Multiple Systems
A failed workflow may involve GitHub, ARC, Kubernetes scheduling, container images, authentication, or the application build itself.
Best Practices
Pin ARC and Helm chart versions in production.
Review release notes and migration requirements before upgrading.
Use GitHub App authentication with appropriately limited permissions.
Keep secrets outside source control.
Use ephemeral runners where isolation is important.
Set realistic CPU and memory requests and limits.
Consider dedicated Kubernetes nodes for CI workloads.
Avoid granting runners unnecessary Kubernetes permissions.
Treat untrusted pull requests as potentially hostile workloads.
Monitor controller, runner, and Kubernetes events together.
Test upgrades in a non-production cluster first.
Keep runner images reproducible and regularly maintained.
Summary
GitHub Actions Runner Controller allows organizations to run GitHub Actions self-hosted runners inside Kubernetes.
The modern runner scale-set architecture makes it possible to treat CI capacity as a Kubernetes-managed workload. This can provide better scaling, isolation, and infrastructure integration than maintaining a collection of long-lived runner machines.
At the same time, ARC introduces additional operational and security considerations. Teams should pin versions, review upgrade requirements, protect authentication credentials, limit runner permissions, configure resources carefully, and monitor both Kubernetes and GitHub Actions.
For Kubernetes teams, the main value is not simply running a runner inside a pod. It is the ability to make CI capacity part of the same scalable infrastructure model used for the rest of the platform.


Join the conversation! Your thoughts help the community grow.