Actions Runner Controller (ARC) is a solution for autoscaling self-hosted GitHub Actions runners using Kubernetes. We had been using the legacy mode with GitHub Enterprise Server, but wanted to try “v2” with GitHub.com.
Getting Linux runners working by following the Quickstart was fairly straightforward, but we also wanted to run a pool of Windows runners. Based on these issues and discussions, we aren’t the only ones. Adi Roiban posted a helpful gist that we used as a starting point.
To set this up, you will need:
ghcr.io/faithlife/actions-runner-image:ltsc2022 created by this Dockerfile.As per the quickstart, first install the arc-system Helm chart:
helm install arc --namespace "arc-system" --create-namespace oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller
Determine how you will perform authentication; we are using a GitHub App. Create a secret for the GitHub App credentials (using your app and installation IDs):
kubectl create namespace arc-windows-runners
kubectl create secret generic app-private-key --namespace=arc-windows-runners --from-literal=github_app_id=1234 --from-literal=github_app_installation_id=56789 --from-file=github_app_private_key=action-runner.private-key.pem
Now create an arc-windows-runners.yaml file that overrides the default Linux runner settings:
githubConfigUrl: "https://github.com/YOURORG"
githubConfigSecret: app-private-key
# set a limit on peak resource usage if desired
# maxRunners: 10
# it's helpful to have at least one runner available to prove that it's working
minRunners: 1
containerMode:
type: "kubernetes-novolume" ## type can be set to "dind", "kubernetes", or "kubernetes-novolume"
# ensure that the listener runs on a Linux node
listenerTemplate:
spec:
nodeSelector:
kubernetes.io/os: linux
containers:
- name: listener
# use our custom runner image on a Windows node
template:
spec:
nodeSelector:
kubernetes.io/os: windows
kubernetes.io/arch: amd64
kubernetes.azure.com/os-sku: Windows2022
containers:
- name: runner
image: ghcr.io/faithlife/actions-runner-image:ltsc2022
dockerdWithinRunnerContainer: true
# NOTE: See our blog post for why we have to exclude this port at container startup
# https://faithlife.codes/blog/2023/02/troubleshooting-azure-dns-timeouts/
command: ["pwsh"]
args: ["-c", "netsh int ipv4 add excludedportrange udp 65330 1 persistent; ./run.cmd --jitconfig $env:ACTIONS_RUNNER_INPUT_JITCONFIG"]
env:
# The runner should be updated via the general image.
- name: DISABLE_RUNNER_UPDATE
value: "true"
# NOTE: see security warnings here: https://docs.github.com/en/actions/tutorials/use-actions-runner-controller/deploy-runner-scale-sets#troubleshooting-kubernetes-mode
- name: ACTIONS_RUNNER_REQUIRE_JOB_CONTAINER
value: "false"
# resources:
# limits: <-- could set limits if desired
Now install the Helm chart with that configuration:
helm install "windows" --namespace "arc-windows-runners" -f arc-windows-runners.yaml oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set
That’s it!
Now at https://github.com/organizations/YOURORG/settings/actions/runners you should see a windows scale set that’s “Online” and one “Idle” Windows runner.
Posted by Bradley Grainger on October 17, 2025