The setup outlined here simplifies security by supporting runners for a single repository only. Supporting multiple repos or organizations requires additional controls like restricting runner use or managing access with groups.
DuploCloud recommends using the Actions Runner Controller (ARC) and isolating workloads by running the controller in one tenant and the runners in another ( gharc01 and ghrun01 in the example below). If runners need access to other tenants, grant cross-tenant permissions only to the runner tenant.
The Helm charts for the controller and scale sets are pinned to specific versions for stability, while runners run the latest image to stay updated. Keeping runners patched is critical despite possible compatibility risks.
Example code below is adapted from Terraform and can also be used with Helm CLI. Both charts were tested at version 0.9.3, with variable references replaced by explicit values for clarity.
Deploying Self-Hosted Runners
Deploy the Actions Runner Controller (ARC)
Deploy the controller Helm chart in the gharc01 tenant using this Terraform resource as an example:
resource"helm_release""runner_scale_set_controller"{name ="runner-scale-set-controller"namespace ="duploservices-gharc01"repository ="oci://ghcr.io/actions/actions-runner-controller-charts"chart ="gha-runner-scale-set-controller"version ="0.9.3"atomic =truetimeout =600values =[yamlencode({nodeSelector={# Only place the controller pods on hosts for this tenant.tenantname="duploservices-gharc01"}})]}
resource "helm_release" "runner_scale_set" {
# This is the value you enter in the `runs-on` key of GitHub Actions workflows to make them use these runners.
name = "duplo-ghrun01-myorg-myrepo"
namespace = "duploservices-ghrun01"
repository = "oci://ghcr.io/actions/actions-runner-controller-charts"
chart = "gha-runner-scale-set"
version = "0.9.3"
atomic = true
timeout = 600
values = [
yamlencode({
# Name of a Duplo k8s secret containing your preferred auth values:
# https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners-with-actions-runner-controller/authenticating-to-the-github-api
githubConfigSecret = "github-auth"
githubConfigUrl = "https://github.com/myorg/myrepo"
listenerTemplate = {
spec = {
# Required when setting spec. Set to empty to clear validation error. Setting to empty didn't change defaults.
containers = []
nodeSelector = {
# Only place the listener pods on hosts for the tenant that runs ARC.
# The listener is run in the ARC controller namespace, not the namespace of the runners.
tenantname = "duploservices-gharc01"
}
}
}
minRunners = 1 # With the default min of 0, jobs never triggered scaling.
template = {
spec = {
containers = [
{
name = "runner"
# Set memory to guaranteed QoS mode so hungry jobs get killed before they take down the host (which may
# have other runners on it).
resources = {
limits = {
memory = "1Gi"
}
requests = {
memory = "1Gi"
}
}
# Setting resources removes these. This sets them back to the default.
command = ["/home/runner/run.sh"]
image = "ghcr.io/actions/actions-runner:latest"
}
]
nodeSelector = {
# Only place the runner pods on hosts for this tenant.
tenantname = "duploservices-ghrun01"
}
}
}
})
]
}