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:

Code
GitHub Actions
      |
      v
Runner Controller
      |
      v
Kubernetes
      |
      +-- Runner Pods
      +-- Jobs
      +-- Scaling

Understanding 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:

Code
                 GitHub
                   |
                   v
           GitHub Actions Job
                   |
                   v
          Actions Runner Controller
                   |
                   v
              Kubernetes
             /     |     \
            /      |      \
       Runner    Runner   Runner
         Pod       Pod      Pod

This 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:

Code
GitHub
  |
  v
Virtual Machine
  |
  +-- Runner
  +-- Tools
  +-- SDKs
  +-- Build Environment

This works, but the machine remains allocated even when no workflow is running.

With Kubernetes:

Code
GitHub
  |
  v
Kubernetes
  |
  +-- Runner Pod
  +-- Runner Pod
  +-- Runner Pod

Runner 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:

Code
Actions Runner Controller
          |
          v
   Runner Scale Set
          |
          v
      Runner Pods

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

Code
Runner Scale Set
       |
       +-- Runner 1
       +-- Runner 2
       +-- Runner 3
       +-- Runner 4

GitHub 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:

Code
jobs:
  build:
    runs-on: arc-runner-set
    steps:
      - uses: actions/checkout@v4

      - name: Build
        run: dotnet build --configuration Release

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

Code
helm repo add actions-runner-controller \
    https://actions-runner-controller.github.io/actions-runner-controller

helm repo update

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

Code
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:

VB.NET
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:

Code
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:

Code
Job Waiting
    |
    v
Runner Pod Created
    |
    v
Runner Registers
    |
    v
Job Executes
    |
    v
Job Completes
    |
    v
Runner Environment Removed

This 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:

Code
Runner VM
 |
 +-- Build A
 +-- Cached files
 +-- Build B
 +-- Temporary files
 +-- Build C

Over time, the machine can accumulate state.

An ephemeral runner has a different lifecycle:

Code
Build A
  |
  v
Runner Pod
  |
  v
Destroyed

Build B
  |
  v
New Runner Pod
  |
  v
Destroyed

This 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:

Code
steps:
  - uses: actions/checkout@v4

  - name: Restore
    run: dotnet restore

  - name: Build
    run: dotnet build --no-restore

If 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:

Code
nodeSelector:
  workload: ci

A 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:

Code
Kubernetes Cluster
        |
        +-- Application Nodes
        |
        +-- CI Nodes
              |
              +-- Runner Pod
              +-- Runner Pod

This 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:

Code
GitHub
   |
   v
Authentication
   |
   v
ARC Controller
   |
   v
Runner Scale Set
   |
   v
Runner Pod
   |
   v
GitHub Actions Job

A problem at any stage can prevent the workflow from running.

Start with controller logs:

Code
kubectl logs \
  -n arc-system \
  deployment/<controller>

Then inspect runner resources:

Code
kubectl get pods -n arc-system

And check events:

Code
kubectl get events \
  -n arc-system \
  --sort-by=.lastTimestamp

Kubernetes events can reveal scheduling, image-pull, permission, or resource problems.

Troubleshooting Pods That Never Start

If runner pods remain pending, check:

Code
kubectl describe pod <runner-pod> \
  -n arc-system

Look for:

  • Insufficient CPU

  • Insufficient memory

  • Node selector mismatch

  • Taints

  • Missing tolerations

  • Image pull errors

  • Service-account problems

For example, a runner requiring:

Code
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

  1. Pin ARC and Helm chart versions in production.

  2. Review release notes and migration requirements before upgrading.

  3. Use GitHub App authentication with appropriately limited permissions.

  4. Keep secrets outside source control.

  5. Use ephemeral runners where isolation is important.

  6. Set realistic CPU and memory requests and limits.

  7. Consider dedicated Kubernetes nodes for CI workloads.

  8. Avoid granting runners unnecessary Kubernetes permissions.

  9. Treat untrusted pull requests as potentially hostile workloads.

  10. Monitor controller, runner, and Kubernetes events together.

  11. Test upgrades in a non-production cluster first.

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