Windows on Actions Runner Controller

Introduction

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.

How to Set Up Windows Runners

To set this up, you will need:

  • A Kubernetes cluster with a Windows node pool; we’re using AKS with Windows 2022 nodes.
  • A Docker image for the Windows runners; we use 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