📄 Extensive documentation is available via our GitHub Pages Docs site.
📢 We maintain the project as a truly open-source project on a best effort basis. We welcome contributions from the community. Feel free to help us answering issues, reviewing PRs, or maintaining and improving the project.
This Terraform module creates the required infrastructure needed to host GitHub Actions self-hosted, auto-scaling runners on AWS spot instances. It provides the required logic to handle the lifecycle for scaling up and down using a set of AWS Lambda functions. Runners are scaled down to zero to avoid costs when no workflows are active.

Check out the detailed instructions in the Getting Started section of the docs. On a high level, the following steps are required to get started: - Setup your AWS account - Create and configure a GitHub App - Download or build the required lambdas - Deploy the module using Terraform - Install the GitHub App to your organization or repositories and add your repositories to the runner group(s).
Check out the provided Terraform examples in the examples directory for different scenarios.
Please check the configuration section of the docs for major configuration options. See the Terraform module documentation for all available options.
This repository was originally founded and maintained by Philips Labs. We gratefully acknowledge their initial contributions and stewardship of this project. You can find the original repository here: https://github.com/philips-labs/terraform-aws-github-runner
This project is licensed under the MIT License - see the LICENSE file for details.
We welcome contributions, please check out the contribution guide. Be aware we use pre commit hooks to update the docs.
Join our discord community via this invite link.
Terraform root module documentation
| Name | Version |
|---|---|
| terraform | >= 1.3.0 |
| aws | >= 6.33 |
| random | ~> 3.0 |
| Name | Version |
|---|---|
| aws | >= 6.33 |
| random | ~> 3.0 |
| Name | Source | Version |
|---|---|---|
| ami_housekeeper | ./modules/ami-housekeeper | n/a |
| instance_termination_watcher | ./modules/termination-watcher | n/a |
| runner_binaries | ./modules/runner-binaries-syncer | n/a |
| runners | ./modules/runners | n/a |
| ssm | ./modules/ssm | n/a |
| webhook | ./modules/webhook | n/a |
| Name | Type |
|---|---|
| aws_sqs_queue.queued_builds | resource |
| aws_sqs_queue.queued_builds_dlq | resource |
| aws_sqs_queue_policy.build_queue_dlq_policy | resource |
| aws_sqs_queue_policy.build_queue_policy | resource |
| random_string.random | resource |
| aws_iam_policy_document.deny_insecure_transport | data source |
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
| ami | AMI configuration for the action runner instances. This object allows you to specify all AMI-related settings in one place. |
Parameters:
filter: Map of lists to filter AMIs by various criteria (e.g., { name = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-*"], state = ["available"] })
owners: List of AMI owners to limit the search. Common values: ["amazon"], ["self"], or specific AWS account IDs
id_ssm_parameter_arn: ARN of an SSM parameter containing the AMI ID. If specified, this overrides both AMI filter and parameter name
kms_key_arn: Optional KMS key ARN if the AMI is encrypted with a customer managed key
Defaults to null, in which case the module falls back to individual AMI variables (deprecated). |
object({
filter = optional(map(list(string)), { state = ["available"] })
owners = optional(list(string), ["amazon"])
id_ssm_parameter_arn = optional(string, null)
kms_key_arn = optional(string, null)
})
| null | no |
| ami_housekeeper_cleanup_config | Configuration for AMI cleanup.
`amiFilters` - Filters to use when searching for AMIs to cleanup. Default filter for images owned by the account and that are available.
`dryRun` - If true, no AMIs will be deregistered. Default false.
`launchTemplateNames` - Launch template names to use when searching for AMIs to cleanup. Default no launch templates.
`maxItems` - The maximum number of AMIs that will be queried for cleanup. Default no maximum.
`minimumDaysOld` - Minimum number of days old an AMI must be to be considered for cleanup. Default 30.
`ssmParameterNames` - SSM parameter names to use when searching for AMIs to cleanup. This parameter should be set when using SSM to configure the AMI to use. Default no SSM parameters. | <pre>object({
amiFilters = optional(list(object({
Name = string
Values = list(string)
})),
[{
Name : "state",
Values : ["available"],
},
{
Name : "image-type",
Values : ["machine"],
}]
)
dryRun = optional(bool, false)
launchTemplateNames = optional(list(string))
maxItems = optional(number)
minimumDaysOld = optional(number, 30)
ssmParameterNames = optional(list(string))
}) | {} | no |
| ami_housekeeper_lambda_s3_key | S3 key for syncer lambda function. Required if using S3 bucket to specify lambdas. | string | null | no |
| ami_housekeeper_lambda_s3_object_version | S3 object version for syncer lambda function. Useful if S3 versioning is enabled on source bucket. | string | null | no |
| ami_housekeeper_lambda_schedule_expression | Scheduler expression for action runner binary syncer. | string | "rate(1 day)" | no |
| ami_housekeeper_lambda_timeout | Time out of the lambda in seconds. | number | 300 | no |
| ami_housekeeper_lambda_zip | File location of the lambda zip file. | string | null | no |
| associate_public_ipv4_address | Associate public IPv4 with the runner. Only tested with IPv4 | bool | false | no |
| aws_partition | (optiona) partition in the arn namespace to use if not 'aws' | string | "aws" | no |
| aws_region | AWS region. | string | n/a | yes |
| block_device_mappings | The EC2 instance block device configuration. Takes the following keys: device_name, delete_on_termination, volume_type, volume_size, encrypted, iops, throughput, kms_key_id, snapshot_id, volume_initialization_rate. |
list(object({
delete_on_termination = optional(bool, true)
device_name = optional(string, "/dev/xvda")
encrypted = optional(bool, true)
iops = optional(number)
kms_key_id = optional(string)
snapshot_id = optional(string)
throughput = optional(number)
volume_initialization_rate = optional(number)
volume_size = number
volume_type = optional(string, "gp3")
}))
| [| no | | cloudwatch_config | (optional) Replaces the module's default cloudwatch log config. See https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-Agent-Configuration-File-Details.html for details. |{
"volume_size": 30}
]
string | null | no |
| create_service_linked_role_spot | (optional) create the service linked role for spot instances that is required by the scale-up lambda. | bool | false | no |
| delay_webhook_event | The number of seconds the event accepted by the webhook is invisible on the queue before the scale up lambda will receive the event. | number | 30 | no |
| disable_runner_autoupdate | Disable the auto update of the github runner agent. Be aware there is a grace period of 30 days, see also the GitHub article | bool | false | no |
| ec2_dynamic_labels_policy | Experimental! Can be removed / changed without trigger a major release.
Optional policy for dynamic EC2 override labels evaluated by the webhook
dispatcher. Only effective when enable_dynamic_labels = true.
Jo
$ claude mcp add terraform-aws-github-runner \
-- python -m otcore.mcp_server <graph>