p.enthalabs

GitHub - mkdev-me/terraform-aws-github-runner-lambda-microvms: Run fully serverless billed per-second GHA Runners on Lambda MicroVMs

Terraform module (`terraform-aws-github-runner-lambda-microvms`) that runs **ephemeral, auto-scaling GitHub Actions self-hosted runners inside AWS Lambda MicroVMs** - Firecracker-isolated, snapshot-resumable, Graviton/arm64 compute.

A webhook-driven dispatcher launches one Lambda MicroVM per queued CI job; the VM resumes from a prebuilt snapshot in seconds, registers a single-use runner (JIT runner), runs the job (Docker-in-runner supported), and terminates itself the moment the job ends - so you never pay for idle runners. One `terraform apply` builds the image, deploys the dispatcher behind a public Lambda Function URL, and (optionally) wires the GitHub webhook.

See **docs/ARCHITECTURE.md** for how it works and the cost comparison vs GitHub-hosted runners, and **docs/USAGE.md** for the full setup guide.

Usage

[](https://github.com/mkdev-me/terraform-aws-github-runner-lambda-microvms/tree/main#usage)

provider "aws" { region = "eu-west-1" # a region where Lambda MicroVMs are available }

provider "awscc" { region = "eu-west-1" # same region as aws; builds the MicroVM image }

Only needed when manage_webhooks = true.

provider "github" { owner = "my-org" app_auth { id = "123456" installation_id = "12345678" pem_file = file("app.pem") } }

module "gha_runner" { source = "mkdev-me/github-runner-lambda-microvms/aws" version = "~> 1.0.0"

github_app = { app_id = "123456" installation_id = "12345678" private_key = file("app.pem") # PEM contents }

github_organization = "my-org" manage_webhooks = true }

Then run a workflow with `runs-on: [self-hosted, linux, arm64, microvm]`. A runnable copy is in `examples/github-app`.

Prerequisites

[](https://github.com/mkdev-me/terraform-aws-github-runner-lambda-microvms/tree/main#prerequisites)

- **A GitHub App** - the dispatcher's only credential (App ID, installation ID, private key). You create it once; see docs/USAGE.md#you-need-a-github-app.

- **On the machine that runs `terraform apply`** (the MicroVM image is a native Cloud Control resource; only the dispatcher zip is built via `local-exec`): Terraform ≥ 1.9, AWS credentials, `python3` + `pip`, `zip`, `bash`.

Notes & caveats

[](https://github.com/mkdev-me/terraform-aws-github-runner-lambda-microvms/tree/main#notes--caveats)

- **arm64 / Graviton only** - Lambda MicroVMs are Graviton-only, so runners are `linux-arm64`.

- **Public ingress** - the default Lambda Function URL is internet-facing; request authenticity rests on the `X-Hub-Signature-256` HMAC check.

- **Not fully hermetic** - the dispatcher zip is built with pip/zip via `local-exec` at apply time.

- **First apply takes a few minutes** - it waits for the MicroVM image build to reach ACTIVE.

Requirements

[](https://github.com/mkdev-me/terraform-aws-github-runner-lambda-microvms/tree/main#requirements) | Name | Version | | --- | --- | | terraform | >= 1.9.0 | | archive | >= 2.4 | | aws | >= 6.0, < 7.0 | | awscc | >= 1.0 | | github | >= 6.2 | | random | >= 3.6 |

Providers

[](https://github.com/mkdev-me/terraform-aws-github-runner-lambda-microvms/tree/main#providers) | Name | Version | | --- | --- | | archive | 2.8.0 | | aws | 6.52.0 | | awscc | 1.90.0 | | github | 6.12.1 | | random | 3.9.0 | | terraform | n/a |

Resources

[](https://github.com/mkdev-me/terraform-aws-github-runner-lambda-microvms/tree/main#resources) | Name | Type | | --- | --- | | aws_cloudwatch_log_group.dispatcher | resource | | aws_cloudwatch_log_group.runner | resource | | aws_iam_role.build | resource | | aws_iam_role.dispatcher | resource | | aws_iam_role.exec | resource | | aws_iam_role_policy.build | resource | | aws_iam_role_policy.dispatcher | resource | | aws_iam_role_policy.exec | resource | | aws_lambda_function.dispatcher | resource | | aws_lambda_function_url.webhook | resource | | aws_s3_bucket.artifacts | resource | | aws_s3_bucket_public_access_block.artifacts | resource | | aws_s3_bucket_server_side_encryption_configuration.artifacts | resource | | aws_s3_object.microvm_code | resource | | aws_ssm_parameter.dispatcher | resource | | awscc_lambda_microvm_image.runner | resource | | github_organization_webhook.runner | resource | | github_repository_webhook.runner | resource | | random_password.webhook | resource | | terraform_data.dispatcher_build | resource |

Inputs

[](https://github.com/mkdev-me/terraform-aws-github-runner-lambda-microvms/tree/main#inputs) | Name | Description | Type | Default | Required | | --- | --- | --- | --- | --- |

| github_app | GitHub App credentials - the dispatcher's machine identity. app_id: numeric App ID as a string, e.g. "123456" (NOT the Client ID). installation_id: App installation ID. Optional for the dispatcher (it derives it per-repo), but REQUIRED when manage_webhooks = true because the github provider's app_auth block needs it. private_key: App private key PEM _contents_ (not a file path). | `object({ app_id = string installation_id = optional(string) private_key = string })` | n/a | yes |

| additional_os_capabilities | additionalOsCapabilities for the MicroVM image. ["ALL"] enables nested Docker / privileged ops (needed for `docker`/`services:` jobs). Set [] to tighten for non-Docker workloads. | `list(string)` | `[ "ALL" ]` | no | | artifacts_bucket_name | Override the S3 artifacts bucket name. Default (null) creates one named '<name_prefix>-artifacts-<account_id>'. | `string` | `null` | no | | base_image_name | AWS-managed MicroVM base image short name, resolved to arn:aws:lambda::aws:microvm-image:. | `string` | `"al2023-1"` | no | | base_image_version | Major version of the AWS-managed base image (base_image_name), as a single number (e.g. "0"). Cloud Control requires it and validates the format. | `string` | `"0"` | no | | dispatcher_memory_size | Dispatcher Lambda memory (MB). | `number` | `256` | no | | dispatcher_timeout | Dispatcher Lambda timeout (seconds). | `number` | `30` | no | | github_api_url | GitHub REST API base URL. Set for GitHub Enterprise Server. -> GH_API_URL. | `string` | `"https://api.github.com"` | no | | github_organization | GitHub org (or user) that owns the runners/webhooks. Used as the github provider `owner` in your root module, and (with empty github_repositories) selects an org-level webhook. | `string` | `null` | no | | github_repositories | Repositories to attach runner webhooks to, as 'owner/name'. Empty => an org-level webhook (requires github_organization). All entries must belong to the github provider's owner. | `list(string)` | `[]` | no |

| image_version | Pin the runner image version the dispatcher launches (e.g. "3.0"). Default (null) makes the dispatcher resolve the latest ACTIVE version at runtime, so image rebuilds are picked up with no redeploy. | `string` | `null` | no | | log_retention_days | CloudWatch retention (days) for the dispatcher and runner log groups. | `number` | `14` | no |

| manage_webhooks | If true, Terraform creates the workflow_job webhooks via the github provider (configure it in your root module). If false, wire them up yourself from the webhook_payload_url + webhook_secret outputs. | `bool` | `true` | no | | max_duration_seconds | maximumDurationInSeconds passed to RunMicrovm - the hard cap on one job's runtime, after which the MicroVM is auto-terminated (cost backstop). | `number` | `1200` | no | | name_prefix | Prefix for every created resource (roles, lambda, secret, bucket, image). | `string` | `"gha-microvm"` | no | | required_labels | Labels a workflow_job must ALL carry for the dispatcher to launch a MicroVM (subset match). -> REQUIRED_LABELS. | `list(string)` | `[ "self-hosted", "microvm" ]` | no | | runner_labels | Labels the ephemeral runner registers with (arm64 only). -> RUNNER_LABELS. | `list(string)` | `[ "self-hosted", "linux", "arm64", "microvm" ]` | no |

| runner_memory_mib | MicroVM memory (minimumMemoryInMiB) baked into the image. Tiers: 512,1024,2048,4096,8192,... Defaults to 8192 because the default image enables Docker (additional_os_capabilities = ["ALL"]) and real Docker builds OOM at less; drop to 4096/2048 for lightweight jobs to cut cost. | `number` | `8192` | no | | tags | Tags applied to all taggable resources. | `map(string)` | `{}` | no |

Outputs

[](https://github.com/mkdev-me/terraform-aws-github-runner-lambda-microvms/tree/main#outputs) | Name | Description | | --- | --- | | artifacts_bucket | Name of the S3 bucket holding the MicroVM code artifact. | | dispatcher_function_name | Name of the dispatcher Lambda function. | | exec_role_arn | ARN of the MicroVM execution role (executionRoleArn for RunMicrovm). | | image_arn | ARN of the MicroVM image (imageIdentifier for RunMicrovm). | | image_version | Active version of the MicroVM image (e.g. '8.0'); increments on each rebuild. | | secret_param_arn | ARN of the SSM SecureString parameter. | | secret_param_name | Name of the SSM Parameter Store SecureString holding the webhook secret + GitHub credential. | | webhook_payload_url | Payload URL for the GitHub webhook (content type: application/json). Already wired up when manage_webhooks = true. | | webhook_secret | HMAC secret shared with GitHub (X-Hub-Signature-256). Sensitive: read with `terraform output -raw webhook_secret`. Set as the webhook 'Secret' if wiring up manually (manage_webhooks = false). |

Examples

[](https://github.com/mkdev-me/terraform-aws-github-runner-lambda-microvms/tree/main#examples)

- `examples/github-app` - GitHub App auth + Terraform-managed webhooks.

Documentation

[](https://github.com/mkdev-me/terraform-aws-github-runner-lambda-microvms/tree/main#documentation)

- **docs/USAGE.md** - full setup: creating the GitHub App, all inputs/options, ingress, scope, runbook.

- **docs/ARCHITECTURE.md** - deep dive: components, snapshot/DNS/self-terminate mechanics, cost model.

License

[](https://github.com/mkdev-me/terraform-aws-github-runner-lambda-microvms/tree/main#license) MIT.