terraform-aws-github-runner: self-hosted GitHub Actions runners that scale to zero on AWS spot
Terraform module for scalable GitHub action runners on AWS
At a glance
- What is it?
- This Terraform module wires GitHub webhooks to a queue, a set of Lambda functions and EC2 spot instances so that self-hosted Actions runners exist only while jobs are waiting. It is built for teams that already run Terraform and want runner capacity under their own AWS account, and it is a poor fit for anyone without that operational base.
- Who is it for?
- Adopt this module if you already manage AWS with Terraform, need Linux x64, arm64 or Windows runners in your own account, and can operate a GitHub App plus a Lambda-based control plane. Do not adopt it if you want a managed runner service, if you cannot run Terraform state safely, or if your jobs need persistent state between runs.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly HCL, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem: GitHub-hosted runners are convenient but not configurable
GitHub-hosted runners give you a fixed machine image, a fixed set of instance sizes and no control over the network they run in. When a workflow needs a larger machine, a GPU, a specific AMI, or a private subnet that can reach an internal database, the hosted option stops being sufficient. The usual workaround is a permanent fleet of self-hosted runners, which is worse in a different way: idle machines cost money every hour and accumulate drift. This module attacks both ends of that trade-off. It creates the infrastructure for self-hosted runners on AWS spot instances and provides logic to scale them down to zero when no workflows are active, so an idle repository costs nothing in compute. The intended user is a team that already treats AWS as its platform and Terraform as its provisioning tool. If your infrastructure lives in a console, the module's value proposition (declarative, versioned runner infrastructure) does not apply to you yet.
How the scaling loop actually works: webhook, SQS, Lambda, spot instance
The repository layout shows six submodules under the root: runners, webhook, runner-binaries-syncer, ssm, ami-housekeeper and termination-watcher. The README describes the mechanism as a set of AWS Lambda functions that handle the lifecycle for scaling up and down, triggered by GitHub events. The root module also declares two SQS queues, queued_builds and queued_builds_dlq, with matching queue policies, plus a random_string resource and an IAM policy document named deny_insecure_transport. That combination tells you the shape of the data flow. A workflow job waiting for a runner produces a GitHub event; the webhook module receives it and places a message on queued_builds; a Lambda consumes that message and starts an instance from your chosen AMI and instance types; the runner registers, takes the job and is terminated after use, because the README describes runners as ephemeral, created on-demand and terminated after use. The dead-letter queue exists for messages the consumer cannot process, which is the part operators usually discover only after jobs silently stop being picked up. The runner-binaries-syncer module is what keeps the GitHub Actions runner agent binaries available to new instances, and the ami-housekeeper module deals with AMI lifecycle, which matters because stale AMIs are a common source of slow starts.
Getting it running: a Terraform module, a GitHub App and prebuilt Lambdas
The README lists the high-level steps plainly: set up your AWS account, create and configure a GitHub App, download or build the required Lambdas, deploy the module with Terraform, then install the GitHub App on your organization or repositories and add those repositories to the runner group. The module is consumed from the Terraform Registry as github-aws-runners/github-runner/aws, and the requirements table pins Terraform at version 1.3.0 or later, the AWS provider at 6.33 or later, and the random provider at roughly 3.0. The examples directory holds Terraform examples for different scenarios, and the docs site has getting-started and configuration sections. Two details deserve attention before you start. First, the GitHub App is not optional and not cosmetic: it is the identity the control plane uses to receive events and register runners, so its permissions and installation scope determine whether the deployment works at all. Second, the Lambdas must be downloaded or built, which means the module is not a single terraform apply against an empty account unless you use the published artifacts. The README states that the project is maintained on a best effort basis, so treat the examples and docs as the primary support channel rather than expecting a fast answer to an issue.
Ephemeral runners are a security choice with an operational price
The README frames ephemeral runners as a security feature: each job gets a fresh machine that is terminated afterwards, so nothing persists between jobs. That is a real benefit for untrusted pull requests, and it is also the module's sharpest constraint. Any workflow that depends on a warm cache, a long-lived container, a mounted volume or state written by a previous job will not work the way it does on a persistent runner. You can bake tooling into a custom AMI, which the module explicitly supports through bring your own AMI, but that moves the problem into image maintenance: every tool update becomes an AMI rebuild and a rollout. The ami-housekeeper submodule exists precisely because this lifecycle needs managing. There is a second constraint in the README's feature list: enterprise level runners are not supported, only organization and repository level. A large enterprise that wants one runner group across many organizations cannot express that here. And because runners run on spot instances, capacity is not guaranteed; the module optimizes cost, but a spot interruption during a long job is a failure mode you have to plan for, not an edge case you can ignore.
Where it sits next to GitHub's own larger runners
The obvious alternative is GitHub's hosted larger runners, which require no AWS account, no Terraform, no GitHub App and no Lambda functions. You pick a size in the workflow file and GitHub handles the machine. The difference in approach is who owns the control plane. With hosted runners, GitHub owns provisioning and you accept its images, its network and its per-minute pricing. With this module, you own the control plane: the webhook receiver, the SQS queue, the scaling Lambdas, the AMI pipeline and the IAM policies all live in your account, and you pay AWS for what you actually consume, which is the point of scaling to zero. The trade is operational surface. If your requirement is simply more CPU than the default runner, hosted larger runners win on effort. If your requirement is a private subnet, a custom AMI, arm64 or Windows alongside Linux, or per-second billing on spot capacity, the hosted option cannot express it and this module can. A second alternative is a static self-hosted fleet managed by hand or by a simpler autoscaling group; it avoids the Lambda layer entirely but gives up scale-to-zero, which is the module's central cost argument.
Maintenance cost: six submodules, a fast release cadence and MIT terms
This is not a deploy-once artifact. The root module composes six submodules, and the release history shows v7.10.1 on 2026-07-31, v7.10.2 on 2026-08-11 and v7.11.0 on 2026-08-17, with the last push to the repository in September 2026. That cadence is healthy but it means you should pin a version and read the release notes before moving, because provider constraints (AWS provider 6.33 or later) and Lambda changes can require coordinated updates. The project is licensed under MIT, which is permissive and places few obligations on how you use or redistribute it; the LICENSE.md file in the repository is the authoritative text, and this is a description, not legal advice. Because the module provisions IAM roles, SQS queues and EC2 instances, the practical upgrade cost is not the Terraform diff but the re-validation: after an upgrade you need to confirm that a test workflow still triggers a runner, that failed messages still land in queued_builds_dlq, and that the GitHub App still has the permissions the new version expects. The README's note that maintenance happens on a best effort basis is the honest framing here: you are adopting a community project, and the operational burden of the control plane sits with you.
What to verify before you commit to the module
Start with scope. Confirm whether you need organization or repository level runners, since the README states enterprise level runners are not supported, and check whether GitHub Enterprise Server or GitHub Cloud with Data Residency applies to you, because both are listed as supported and each changes the endpoint configuration. Next, decide how you will supply the Lambdas: download the released artifacts or build them, and make that part of your pipeline rather than a manual step. Then settle the AMI question early, because bring your own AMI is the mechanism that makes ephemeral runners usable for anything beyond a stock toolchain, and the ami-housekeeper submodule is what keeps those images from rotting. Finally, size your spot strategy against your actual job durations. The module scales to zero, which is its main cost advantage, but the cost of a job that gets interrupted halfway through is measured in developer time, not instance hours. If you cannot answer the AMI and spot questions, the module will still deploy; it just will not be pleasant to operate.
Editorial conclusion
Adopt this module if you already manage AWS with Terraform, need Linux x64, arm64 or Windows runners in your own account, and can operate a GitHub App plus a Lambda-based control plane. Do not adopt it if you want a managed runner service, if you cannot run Terraform state safely, or if your jobs need persistent state between runs. Before applying anything, verify that your account can obtain the spot capacity and instance types you intend to use, that the GitHub App permissions match the runner scope (org or repo, since enterprise level runners are not supported), and that you know which release tag you are pinning, because the recent cadence (v7.10.1, v7.10.2, v7.11.0 within about a month) means upgrades arrive whether or not you are ready.
Community notes