3 min read
Self-Hosted GitHub Runner in Kubernetes
GitHub’s hosted runners are convenient, but they bill by the minute, can’t reach anything inside your network and start from a clean machine every time. If you already run a Kubernetes cluster, you can run the jobs there instead. Actions Runner Controller (ARC) starts a fresh runner pod for each job and removes it afterwards.
This note uses the current setup GitHub maintains: the gha-runner-scale-set charts. Older guides use the community summerwind controller with RunnerDeployment resources. Don’t mix the two.
What you need
- A Kubernetes cluster and
kubectlaccess to it. A bare-metal cluster works fine. - Helm 3.
- Owner rights in the GitHub organisation that should get the runners.
1. Create a GitHub App
ARC needs credentials to register runners. A GitHub App is better than a personal access token: it isn’t tied to a person and its permissions are narrow.
In the organisation settings, under Developer settings → GitHub Apps → New GitHub App:
- Give it a name and any homepage URL. Turn off the webhook.
- Under Organization permissions, set Self-hosted runners to Read and write.
- Create the app, then generate a private key. A
.pemfile is downloaded. - Install the app on your organisation.
Note three values:
- the App ID, shown on the app’s settings page,
- the installation ID, the number at the end of the URL after you open the installed app under the organisation’s GitHub Apps settings,
- the path to the private key file.
Keep the key file out of any repository.
2. Install the controller
The controller gets its own namespace:
helm install arc \
--namespace arc-systems \
--create-namespace \
oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller
Without --version, Helm installs the latest chart. Pin a version once it works, so an upgrade happens when you decide.
3. Store the app credentials
The runners get a second namespace. The secret has to live in the same namespace as the runner scale set:
kubectl create namespace arc-runners
kubectl create secret generic arc-github-app \
--namespace arc-runners \
--from-literal=github_app_id=<APP_ID> \
--from-literal=github_app_installation_id=<INSTALLATION_ID> \
--from-file=github_app_private_key=<PATH_TO_PRIVATE_KEY.pem>
The key names (github_app_id, github_app_installation_id, github_app_private_key) are fixed. ARC looks for exactly these.
4. Install a runner scale set
A runner scale set is a pool of runners for one organisation. Its installation name becomes the label you use in workflows:
helm install arc-runner-set \
--namespace arc-runners \
--set githubConfigUrl="https://github.com/<ORGANISATION>" \
--set githubConfigSecret=arc-github-app \
oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set
5. Check that it runs
helm list -A
kubectl get pods -n arc-systems
Both the controller and a listener pod should be Running. The scale set also appears in the organisation settings under Actions → Runners.
There are no runner pods yet. They start when a job arrives.
6. Use it in a workflow
Set runs-on to the installation name:
name: Runner test
on:
workflow_dispatch:
jobs:
hello:
runs-on: arc-runner-set
steps:
- run: echo "Running on $(hostname)"
Start the workflow from the Actions tab and watch the pods:
kubectl get pods -n arc-runners -w
A runner pod appears, runs the job and disappears again.
Things to know
- Each job gets a fresh pod. Nothing survives between jobs, which is good for security and bad for caches. Use
actions/cacheor a registry cache for build layers. - The default runner image has no Docker daemon. Jobs that build images need the scale set’s
containerModeset todind(Docker in Docker) orkubernetes. Read the chart’svalues.yamlbefore you choose. - Self-hosted runners and public repositories don’t mix. A pull request from a fork could run code on your cluster. Use them for private repositories only.