Terraform module that deploys self-hosted GitHub Actions runners on Kubernetes using the official Actions Runner Controller (ARC).
This module automates the deployment of:
- ARC Controller: Manages the lifecycle of runner scale sets
- Runner Scale Sets: Auto-scaling GitHub Actions runners
- Runner Groups: Organized runner access control (optional)
- Authentication: Support for both GitHub Apps and Personal Access Tokens
- âś… Multiple scale sets with independent configurations
- âś… Auto-scaling runners (min/max configuration)
- âś… GitHub App or PAT authentication
- âś… Private container registry support
- âś… Runner group management with repository and workflow restrictions
- âś… Flexible runner image configuration
- âś… Container mode support (Docker-in-Docker or Kubernetes)
- âś… Automatic namespace creation
module "github_runners" {
source = "path/to/module"
github_org = "my-organization"
github_token = var.github_token
}module "github_runners" {
source = "path/to/module"
github_org = "my-organization"
github_token = var.github_token
controller = {
name = "arc-controller"
namespace = "arc-system"
create_namespace = true
version = "0.13.0"
}
scale_sets = {
"default-runners" = {
namespace = "arc-runners-default"
create_namespace = true
min_runners = 1
max_runners = 10
runner_image = "ghcr.io/actions/actions-runner:latest"
container_mode = "dind"
visibility = "all"
create_runner_group = true
}
"production-runners" = {
namespace = "arc-runners-prod"
create_namespace = true
min_runners = 2
max_runners = 20
runner_group = "production"
visibility = "selected"
repositories = ["repo1", "repo2"]
create_runner_group = true
}
"ci-runners" = {
namespace = "arc-runners-ci"
create_namespace = true
min_runners = 3
max_runners = 15
runner_group = "ci"
visibility = "selected"
workflows = [".github/workflows/ci.yml"]
create_runner_group = true
}
}
}module "github_runners" {
source = "path/to/module"
github_org = "my-organization"
github_app_id = 123456
github_app_installation_id = 789012
github_app_private_key = file("${path.module}/github-app-key.pem")
}module "github_runners" {
source = "path/to/module"
github_org = "my-organization"
github_token = var.github_token
private_registry = "registry.example.com"
private_registry_username = var.registry_username
private_registry_password = var.registry_password
scale_sets = {
"custom-image-runners" = {
runner_image = "registry.example.com/custom/runner:latest"
}
}
}| Name | Version |
|---|---|
| terraform | >= 1.0 |
| github | ~> 6.0 |
| helm | ~> 3.0 |
| kubernetes | ~> 2.0 |
| Name | Version |
|---|---|
| github | ~> 6.0 |
| helm | ~> 3.0 |
| kubernetes | ~> 2.0 |
| Name | Type |
|---|---|
| github_actions_runner_group.this | resource |
| helm_release.controller | resource |
| helm_release.scale_set | resource |
| kubernetes_namespace.controller | resource |
| kubernetes_namespace.scale_set | resource |
| kubernetes_secret.github_creds | resource |
| kubernetes_secret.private_registry_creds | resource |
| github_repositories.all | data source |
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
| github_org | GitHub organization name | string |
n/a | yes |
| controller | Controller configuration | object({ |
{ |
no |
| scale_sets | Scale sets configuration (map) | map(object({ |
{ |
no |
| github_token | GitHub Token (use either token or GitHub App credentials) | string |
null |
no |
| github_app_id | GitHub App ID (use either token or GitHub App credentials) | number |
null |
no |
| github_app_installation_id | GitHub App Installation ID | number |
null |
no |
| github_app_private_key | GitHub App private key (PEM format) | string |
null |
no |
| github_repositories | All repositories in the organization. If not provided, they will be fetched by the module | any |
null |
no |
| private_registry | Private container registry URL | string |
null |
no |
| private_registry_username | Private container registry username | string |
null |
no |
| private_registry_password | Private container registry password | string |
null |
no |
| Name | Description |
|---|---|
| controller | The Helm release name of the controller |
| scale_set | List of scale set names created |
This module supports two authentication methods with GitHub:
module "github_runners" {
source = "path/to/module"
github_org = "my-organization"
github_token = var.github_token # Classic PAT with admin:org scope
}Required Scopes:
admin:org(for runner group management)repo(if managing repository runners)
module "github_runners" {
source = "path/to/module"
github_org = "my-organization"
github_app_id = var.github_app_id
github_app_installation_id = var.github_app_installation_id
github_app_private_key = file("github-app-key.pem")
}Required Permissions:
- Repository permissions:
Actions: Read & Write,Administration: Read & Write - Organization permissions:
Self-hosted runners: Read & Write
dind(Docker-in-Docker): Runs Docker daemon inside the runner containerkubernetes: Uses Kubernetes-native container executionnull: No container mode (bare runner)
all: Runners available to all repositories in the organizationselected: Runners limited to specific repositoriesprivate: Runners available only to private repositories
Runner groups organize runners and control access:
scale_sets = {
"backend-runners" = {
runner_group = "backend-team"
create_runner_group = true
visibility = "selected"
repositories = ["api", "database", "worker"]
workflows = [".github/workflows/deploy.yml"]
}
}This module includes comprehensive Terraform native tests using mock providers:
terraform testTests cover:
- Controller creation
- Multiple scale sets
- GitHub App authentication
- Private registry configuration
- Runner groups
- Variable validations
- Container modes
- Resource dependencies
MIT License - see LICENSE for details.
Contributions are welcome! Please open an issue or submit a pull request.
Created and maintained by [Your Name/Organization]