Level Up Your CI/CD: Self-Hosted GitHub Actions Runners on Kubernetes with Helm
Introduction
GitHub Actions are fantastic for automating software workflows. However, sometimes you need more control over your environment – perhaps specific hardware, software, or network access. That's where self-hosted runners come in! This tutorial guides you through setting up these runners within your Kubernetes cluster using Helm, simplifying the management process.
Prerequisites
A Kubernetes cluster (Minikube, Kind, or a cloud provider's offering like AKS, EKS, or GKE).
Helm installed and configured to connect to your cluster.
Basic familiarity with Kubernetes concepts like namespaces, deployments, and secrets.
A GitHub account with a repository you want to use with your runners.
TL;DR
Want GitHub Actions with more control? This tutorial shows you how to use Helm to deploy self-hosted runners inside your Kubernetes cluster. You'll create secrets, install the runner controller and scale sets, and configure your GitHub workflow to use the runners.
Steps
1. Create Namespaces (Optional but Recommended)
Namespaces help organize your Kubernetes resources. Let's create one for the Runner Controller and one for our runners.
kubectl create namespace arc-systems
kubectl create namespace arc-runners
2. Create GitHub Token Secret
The runner needs to authenticate with GitHub. We'll use a Personal Access Token (PAT). It is advisable to use GitHub App instead of PAT.
Go to your GitHub account settings -> Developer settings -> Personal access tokens -> Generate new token.
Give the token a descriptive name (e.g., "k8s-runner-token").
Crucially, grant the
repo
scope (this gives the runner permission to listen for workflow triggers in your repositories).Copy the generated token. Store it securely!
Note that for organisation runners, you need to grant the admin:org
scope.
Now, create a Kubernetes secret to store the token:
NAMESPACE="arc-systems"
GITHUB_TOKEN="your-secret" # Replace with your actual token
kubectl create secret generic gha-runners-secret \
--namespace=${NAMESPACE} \
--from-literal=github_token="${GITHUB_TOKEN}"
3. Install the Runner Controller
The runner controller manages the lifecycle of your runners. We'll install it using Helm:
NAMESPACE="arc-systems"
helm install arc \
--namespace "${NAMESPACE}" \
--create-namespace \
--set githubConfigSecret=gha-runners-secret \ # This is the secret we created earlier
oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller
4. Install the Runner Scale Set
This is where we define the runners themselves. A "scale set" lets Kubernetes automatically manage the number of runners based on demand.
INSTALLATION_NAME="arc-runner-set" # you MUST use "runs-on: arc-runner-set" in your workflow
NAMESPACE="arc-runners"
GITHUB_CONFIG_URL="https://github.com/my-github-account/my-repository" # Replace with your GitHub organization or repository URL
GITHUB_PAT=$(kubectl get secret gha-runners-secret -n arc-systems -o jsonpath="{.data.github_token}" | base64 --decode)
helm install "${INSTALLATION_NAME}" \
--namespace "${NAMESPACE}" \
--create-namespace \
--set containerMode.type="dind" \ # This is the Docker In Docker mode, so that you can use docker commands in your workflow
--set githubConfigUrl="${GITHUB_CONFIG_URL}" \
--set githubConfigSecret.github_token="${GITHUB_PAT}" \
oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set
INSTALLATION_NAME
: A name for your runner set. This is important, as you'll reference it in your GitHub workflow. The example above usesarc-runner-set
.GITHUB_CONFIG_URL
: This should be the URL of your GitHub repository or organization. For a repository, it looks likehttps://github.com/your-github-account/your-repo
. For an organization, it's likehttps://github.com/your-org
. The runner will register itself with this entity.GITHUB_PAT
: We're retrieving the token from the Kubernetes secret we created earlier.
5. Configure Your GitHub Workflow
Now, tell your GitHub workflow to use the self-hosted runner. In your workflow file (.github/workflows/your-workflow.yml
), add the runs-on
key:
jobs:
build:
name: Build and Test
runs-on: arc-runner-set # Replace with your INSTALLATION_NAME
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run tests
run: echo "Running tests on self-hosted runner!"
- name: Test docker command in runner
run: docker --version
Replace arc-runner-set
with the INSTALLATION_NAME
you chose during the Helm installation.
Explanation
Helm Charts: Helm simplifies deploying and managing applications on Kubernetes. Charts are like packages containing all the necessary Kubernetes resource definitions.
Runner Controller: This controller watches for workflow jobs assigned to your repository/organization and scales the runners accordingly.
Runner Scale Set: Defines the configuration for the runners.
runs-on
: This key in your workflow tells GitHub Actions to use a runner with the specified label (ourINSTALLATION_NAME
).
Going Further
GitHub App authentication: Use GitHub App instead of PAT as it is the recommended way for authentication.
Scoped deployment: Configure the workload to run on specific nodes, using affinity rules. Configure this in the
template.spec.affinity
section while using-f arc-set.yaml
in the Helm installation.Scaling Strategies: Explore different scaling strategies to optimize runner utilization and cost.
This should give you a solid foundation for setting up self-hosted GitHub Actions runners in Kubernetes. Good luck!