Skip to main content

cdk-github-microvm-runners

npm PyPI CI License

A CDK construct library that runs GitHub Actions jobs on AWS Lambda MicroVMs. Each job runs on its own MicroVM in your AWS account, created when the job starts and removed once it finishes.

Why this exists

AWS Lambda MicroVMs provides the ability to launch a MicroVM, but nothing that connects one to GitHub Actions: there is no runner registration, no path from an incoming job to a running VM, and no cleanup when a VM or a runner is left behind. AWS CDK provides the individual building blocks — Lambda functions, a queue, a table, IAM roles — but not an assembled system. This construct is that system. It receives GitHub's workflow_job events, launches a MicroVM for each job, registers a single-use runner, and reconciles the result afterward, so that adopting per-job MicroVM runners is a matter of configuring a construct rather than building and operating the surrounding machinery yourself.

Two design decisions shape the rest of the library. The runner VMs hold no AWS credentials by default, because a MicroVM's instance metadata service would otherwise return any attached role's credentials to the code running in the job; a job that needs AWS obtains its own short-lived credentials through GitHub OIDC instead. Routing is per job: a workflow job opts in by naming the runner's label in its runs-on, and every other job continues to run on GitHub-hosted runners, so a repository can be moved across one job at a time.

How it works

A single runner set — one GithubMicrovmRunners in a stack — serves a GitHub organization or a set of repositories. It is configured with two required properties: how it authenticates to GitHub, and which organization or repositories it serves.

A runner class pairs a runs-on label with the MicroVM size and image that jobs carrying that label run on, and a runner set can define more than one. Each class builds its own image and runs at its own size, so a workflow can send small jobs to one class and memory-heavy jobs to a larger one by choosing the matching label. The runner VMs are ARM64, so the tools and container images a job pulls need arm64 builds.

flowchart TD
    GH["GitHub Actions — workflow_job event"] --> WH["Webhook Lambda — verifies the signature"]
    WH --> Q["SQS job queue — launch and terminate intents"]
    Q --> LN["Launcher Lambda — claims the job, obtains a MicroVM"]
    LN --> VM["MicroVM — runs one job"]
    LN -.->|registers a single-use runner| GH
    LN <--> DB[("DynamoDB runner table")]
    JAN["Janitor Lambda — scheduled sweep"] -.->|reaps stranded VMs, reconciles state| VM
    JAN <--> DB

When GitHub sends a workflow_job event, the webhook handler verifies its signature and places the job on an SQS queue. The launcher reads the queue, records a claim so that a given job is only ever launched once, starts a MicroVM (or resumes one from a warm pool, if the runner class keeps one), and registers a single-use runner with GitHub. The VM runs that one job and is then removed. A janitor runs on a schedule to reconcile the runner set against the running VMs and GitHub's view of its runners, terminating anything that was stranded and cleaning up records that are no longer needed. The architecture guide describes the lifecycle, the idempotency model, and the checks that keep the janitor from terminating a runner that is still working.

Install

One source, compiled by jsii into two packages. The API is the same in each, with names rendered in the target language's conventions.

npm install cdk-github-microvm-runners
pip install cdk-github-microvm-runners

Getting started

To deploy, you need an AWS account, a GitHub App connected to the runner set, and the App's private key and webhook secret stored in AWS Secrets Manager. The getting-started guide walks through all three, and includes a helper that performs the GitHub App setup for you.

The example below is TypeScript; API.md carries the same surface for every language.

from aws_cdk import Environment
from aws_cdk import App, CfnOutput, Stack
from aws_cdk.aws_secretsmanager import Secret
from cdk_github_microvm_runners import GithubMicrovmRunners, GithubAppId, GithubAppKey, GithubAuth, RunnerScope, MicrovmSize

app = App()
stack = Stack(app, "Runners", env=Environment(region="us-east-1"))

# The GitHub App's ID, private key, and webhook secret are read from Secrets
# Manager at run time, so this stack deploys before the App is created.
app_id = Secret.from_secret_name_v2(stack, "AppId", "microvm-runner/dev/app-id")
private_key = Secret.from_secret_name_v2(stack, "AppKey", "microvm-runner/dev/app-private-key")
webhook_secret = Secret.from_secret_name_v2(stack, "WebhookSecret", "microvm-runner/dev/webhook-secret")

runners = GithubMicrovmRunners(stack, "Runners",
    github=GithubAuth.app(
        app_id=GithubAppId.from_secret(app_id),
        private_key=GithubAppKey.from_secret(private_key),
        webhook_secret=webhook_secret
    ),
    scope=RunnerScope.org("my-org")
)

# A runner class pairs a label with the MicroVM size it runs on. Workflows
# reach it through `runs-on: [self-hosted, microvm]`.
runners.add_runner_class("microvm", size=MicrovmSize.GB4)

CfnOutput(stack, "WebhookUrl", value=runners.webhook_url)

Once the stack is deployed, point the GitHub App's webhook at the stack's WebhookUrl output — the setup helper reads it from the stack and wires it for you, or you can set it by hand — and change a workflow job's runs-on to [self-hosted, microvm].

Examples

The samples below extend the runner set above, and each shows only the part it adds.

A runner class with its own image

A runner class that names no image builds from the base image, which carries the operating system, the Actions runner, Docker, and the AWS CLI. RunnerImage.fromOptions describes what to add to that base: system packages, setup commands, environment variables, and files.

runners.add_runner_class("build",
    size=MicrovmSize.GB8,
    image=RunnerImage.from_options(
        system_packages=["jq", "ripgrep"],
        setup_commands=["npm install -g pnpm@10"]
    )
)

Images are built when the stack deploys, so a job that arrives later boots an image that already exists. Runner images covers the three ways to describe one, including supplying a Dockerfile of your own.

Language versions baked into the image

actions/setup-python and actions/setup-node resolve the version a job asks for from the image's tool cache. A toolchain is a language runtime placed in that cache when the image is built.

runners.add_runner_class("test",
    size=MicrovmSize.GB4,
    image=RunnerImage.from_options(
        toolchains=[
            RunnerToolchain.python("3.12.7"),
            RunnerToolchain.node("22.11.0")
        ]
    )
)

Each toolchain pins a full three-part version, because that version names the cache directory it is baked into. A workflow targeting the class then writes actions/setup-python as it would on a GitHub-hosted runner, and Toolchains covers the rest.

Runners in your own VPC

Network egress belongs to the runner set rather than to a class, and every runner class shares it. RunnerNetwork.vpc has the construct build a Lambda runtime connector from a VPC you pass, so the VMs follow that VPC's subnets, security groups, and route tables.

GithubMicrovmRunners(stack, "Runners",
    github=github,
    scope=RunnerScope.org("my-org"),
    network=RunnerNetwork.vpc(vpc,
        subnets=ec2.SubnetSelection(subnet_type=ec2.SubnetType.PRIVATE_WITH_EGRESS)
    )
)

The connector's network interfaces land in the VPC's private-with-egress subnets and get a new security group unless you pass your own. The security guide covers attaching a connector you already manage, and what the VPC's routing then decides for a job.

Security

The runner VMs carry no AWS identity by default, so a compromised or malicious workflow step has no credentials to read from the instance metadata service. A job that needs AWS assumes a role scoped to that job through GitHub OIDC. The security guide covers the whole model, including the OIDC trust-policy setup and the details that are easy to get wrong.

The webhook is served from a public Lambda Function URL, because GitHub cannot sign its webhook deliveries with SigV4. The handler verifies GitHub's HMAC-SHA256 signature on every request.

A single property attaches an execution role to the VMs — vmExecutionRole — for a runner set whose jobs need a standing AWS identity. Because that role's credentials become readable from inside the job, it is off by default, and the construct never creates a VM identity on your behalf. Runtime console capture (consoleLogs: ConsoleLogs.enabled()) writes its logs through that same role, so it too requires vmExecutionRole, and you grant the role the log-write actions yourself.

Documentation

Guide What it covers
Getting started Prerequisites, GitHub App setup, deploying, and running a first job
Onboarding a repo Moving an existing repository's CI onto the runners
Runner images What an image contains, when it is built, how it changes, and the disk each size allows
Toolchains Baking the Python and Node versions your workflows use into the image
Security The security model, per-job AWS access through GitHub OIDC, and the control plane
Service quotas Sizing for concurrency, and what to do when a quota cannot be raised
Monitoring Opt-in CloudWatch metrics and the ready-made alarms
Logging Image build logs and runtime VM console output, and the tradeoff console capture involves
Architecture The lifecycle, the idempotency model, and the checks that prevent terminating an active runner
API reference Every construct, property, and default, generated from the source
Security policy · Contributing · Changelog Reporting and project information

Configuration

Beyond the two required properties, every option is optional and has a conservative default, so the example above deploys as written. The available options include a customer-managed KMS key applied to the DynamoDB table, the SQS queues, and the log groups (encryptionKey); a permissions boundary applied to every role the construct creates (permissionsBoundary); the removal policy, DynamoDB point-in-time recovery, log retention, dead-letter retention, dead-letter redrive count, and Lambda memory; and control over the VMs' network egress through RunnerNetwork.

Development

Quality checks run through just and are enforced by lefthook git hooks; CI runs the same just verify.

brew install just lefthook
pnpm install
lefthook install
just verify

Feature pull requests target dev, which is promoted to main by pull request.

See CONTRIBUTING.md for local setup and the full workflow.

License

Released under the Apache-2.0 license.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cdk_github_microvm_runners-0.1.4.tar.gz (1.5 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cdk_github_microvm_runners-0.1.4-py3-none-any.whl (1.5 MB view details)

Uploaded Python 3

File details

Details for the file cdk_github_microvm_runners-0.1.4.tar.gz.

File metadata

File hashes

Hashes for cdk_github_microvm_runners-0.1.4.tar.gz
Algorithm Hash digest
SHA256 49a6b92e18af9f4cd67bf442787281bc48831b8ff2f2f4cfb293513a33da5343
MD5 75178f060ec087178a48539b34195530
BLAKE2b-256 144716a7660654d29e3031003319c282efd9285c34f84366a4115b3b9cdad0a9

See more details on using hashes here.

Provenance

The following attestation bundles were made for cdk_github_microvm_runners-0.1.4.tar.gz:

Publisher: release.yml on schuettc/cdk-github-microvm-runners

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file cdk_github_microvm_runners-0.1.4-py3-none-any.whl.

File metadata

File hashes

Hashes for cdk_github_microvm_runners-0.1.4-py3-none-any.whl
Algorithm Hash digest
SHA256 b784f8555cd2606969c0dc53923ddb09af14b8df01888b5f27812d7f2037dbf4
MD5 fd1038ce329ff47435b913323a428707
BLAKE2b-256 caadc0be9333b2330b4a088741a7d7f8a51979f97b4fb0756716b0c957396a19

See more details on using hashes here.

Provenance

The following attestation bundles were made for cdk_github_microvm_runners-0.1.4-py3-none-any.whl:

Publisher: release.yml on schuettc/cdk-github-microvm-runners

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page