Sahan Serasinghe

Senior Software Engineer | Master of Data Science

Deploying GitHub Self-Hosted Runners on Your Home Kubernetes Cluster with ARC

2025-07-27distributed systems 5 min read

If you followed my last post, Building a Home Lab Kubernetes Cluster with Old Hardware and k3s, you now have a proper x86 Kubernetes cluster humming away on your old laptops. So, what’s next? Time to put that cluster to work—let’s run GitHub Actions jobs on your own hardware!

Why? Because self-hosting your runners is faster, gives you full control (no GitHub minutes limit!), and lets you run bigger jobs (CI, builds, ML, you name it) on your home infra. And with Actions Runner Controller (ARC), managing runners at scale on Kubernetes is surprisingly easy.

Here’s how to set it all up.

What is ARC and Why Should You Care?

ARC (Actions Runner Controller) is an open-source Kubernetes operator from GitHub. It spins up and manages GitHub Actions runners as Kubernetes pods—no more manually registering runners, no more pets, just cattle. Runners auto-scale up and down as jobs arrive. It’s perfect for CI/CD, especially on clusters you own.

Here’s a high level view of how it works under the hood

ARC Architecture

Prerequisites

  • Working Kubernetes cluster (see previous post)
  • kubectl and helm installed on your machine
  • A GitHub Personal Access Token (PAT) with repo and admin:org scopes

1️⃣ Pre-Setup: Quick Checks

Make sure you have what you need:

which helm
kubectl version --client
helm list -A

If those commands work, you’re good to go.

2️⃣ Install ARC Controller

Let’s install the ARC controller into your control plane namespace:

NAMESPACE="actions-runner-controller"
helm install arc \
  --namespace "${NAMESPACE}" \
  --create-namespace \
  oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller

This deploys the controller which manages your runners.

3️⃣ Deploy a Runner Scale Set

Time to create the runners that will actually do the work. Replace the example GitHub URL and PAT with your details:

INSTALLATION_NAME="arc-runner-set"
NAMESPACE="arc-runners"
GITHUB_CONFIG_URL="https://github.com/youruser/yourrepo"
GITHUB_PAT="ghp_123456..."

helm install "${INSTALLATION_NAME}" \
  --namespace "${NAMESPACE}" \
  --create-namespace \
  --set githubConfigUrl="${GITHUB_CONFIG_URL}" \
  --set githubConfigSecret.github_token="${GITHUB_PAT}" \
  oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set
  • GITHUB_CONFIG_URL: The repo or org you want to run jobs for.
  • GITHUB_PAT: Your Personal Access Token.

4️⃣ Check That It’s Working

Verify the controller and runner pods are up:

# Controller
kubectl get pods -n actions-runner-controller

# Runners
kubectl get pods -n arc-runners

# Runner set status
kubectl get AutoscalingRunnerSet -A
kubectl describe AutoscalingRunnerSet arc-runner-set -n arc-runners

You should see your runners show up as pods. If you trigger a workflow in your GitHub repo, you’ll see a pod spin up, do the job, and then shut down—magic.

5️⃣ Testing It Out

Here’s the fun part. Create a simple GitHub Actions workflow in your repo to test the runners:

name: Test ARC Runners
on: [push]

jobs:
  build:
    runs-on: arc-runners # This tells GitHub to use your self-hosted runners. Use the NAMESPACE name you defined in step 3.
    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Run a script
        run: echo "Hello from ARC Runner!"
      - name: List files
        run: ls -la

Here’s ci.yaml from my githubstats repo if you need a working example: ci.yaml

Make a commit to trigger the workflow. You should see the runner pod spin up, execute the job, and then terminate.

arc self hosted github runners k8s 1

Here's the workflow running on my ARC self-hosted runner

5️⃣ Monitoring (Bonus: Grafana)

Want to geek out and monitor your runners? If you’ve set up Prometheus/Grafana (see my upcoming post if not!), you can:

  • Check pod CPU/memory usage
  • Track how many runners are running
  • See logs for each pod

Handy queries for Grafana dashboards:

# Number of ARC runner pods
kube_pod_status_phase{namespace="arc-runners", phase="Running"}

# Pod CPU usage
rate(container_cpu_usage_seconds_total{namespace="arc-runners"}[5m])

arc self hosted github runners k8s 2

Here's what the workflow looks like in action

6️⃣ Useful Commands for Day-to-Day Ops

# Watch runner scaling in real-time
kubectl get AutoscalingRunnerSet -n arc-runners -w

# See events and troubleshoot
kubectl get events -n arc-runners --sort-by=.metadata.creationTimestamp

# Pod logs (for a specific runner)
kubectl logs -n arc-runners <pod-name>

Troubleshooting Tips

  • Runner not connecting? Double-check your PAT and network access.
  • Pods stuck or crash-looping? Check logs for clues and make sure your cluster has enough resources.
  • Can’t see runners in GitHub? Make sure the config URL matches your repo/org and the PAT has correct scopes.

That’s It! You’re Running GitHub Actions on Your Own Cluster

You now have GitHub Actions jobs running at home on your cluster, scaling up and down automatically. No more slow or limited runners. Your home lab just levelled up—CI/CD, builds, ML, you name it.

Stay tuned for my next post where I’ll show you how to get beautiful observability dashboards and set up alerting for your home cluster.
Happy automating!


Questions? Want to show off your setup? Ping me on X (Twitter) or drop a comment below!

Sahan Serasinghe - Engineering Blog

Sahan Serasinghe Senior Software Engineer at Canva | Azure Solutions Architect Expert | Master of Data Science at UIUC | CKAD