Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/workflows/aws-runner-target.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: AWS runner target

on:
workflow_dispatch:

permissions:
contents: read

jobs:
target:
runs-on: [self-hosted, lambda-microvm, e2e]
timeout-minutes: 15
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- name: Verify host and Docker
run: |
set -euo pipefail
test "$(uname -m)" = "aarch64"
docker info
docker buildx version
docker compose version
docker run --rm public.ecr.aws/docker/library/busybox:1.37.0 true
62 changes: 62 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: CI

on:
pull_request:
push:
branches: [main]

permissions:
contents: read

jobs:
action:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: "24"
cache: npm
- run: npm ci
- run: npm run check
- name: Verify committed Action bundle
run: git diff --exit-code -- dist
- name: Check shell scripts
run: shellcheck scripts/*.sh
- name: Verify reproducible image artifact
run: |
scripts/package-runner-image.sh
first="$(sha256sum build/runner-image.zip | awk '{print $1}')"
scripts/package-runner-image.sh
second="$(sha256sum build/runner-image.zip | awk '{print $1}')"
test "${first}" = "${second}"

runner-image:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
with:
platforms: arm64
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: Build ARM64 runner image
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: runner-image
platforms: linux/arm64
tags: lambda-microvm-github-runner:test
load: true
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Verify immutable image contents
run: |
docker run --rm --platform linux/arm64 \
lambda-microvm-github-runner:test sh -lc '
set -e
! ldd /opt/actions-runner/bin/Runner.Listener | grep "not found"
test ! -S /var/run/docker.sock
/opt/actions-runner/bin/Runner.Listener --version
docker buildx version
docker compose version
aws --version
'
66 changes: 66 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: Release

on:
push:
tags:
- "v*"

permissions:
contents: write

jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: "24"
cache: npm
- run: npm ci
- run: npm run check
- name: Verify committed Action bundle
run: git diff --exit-code -- dist
- uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
with:
platforms: arm64
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: Build runner image
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: runner-image
platforms: linux/arm64
tags: lambda-microvm-github-runner:release
load: true
- run: mkdir -p build
- name: Generate runner image SBOM
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0
with:
image: lambda-microvm-github-runner:release
format: cyclonedx-json
output-file: build/runner-image-sbom.cdx.json
upload-artifact: false
upload-release-assets: false
dependency-snapshot: false
- name: Package release artifacts
run: |
scripts/package-runner-image.sh
npm sbom --sbom-format=cyclonedx > build/action-sbom.cdx.json
sha256sum \
build/runner-image.zip \
build/runner-image-sbom.cdx.json \
build/action-sbom.cdx.json \
dist/index.js \
> build/SHA256SUMS
- name: Create GitHub release
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create "${GITHUB_REF_NAME}" \
--verify-tag \
--generate-notes \
--title "${GITHUB_REF_NAME}" \
build/runner-image.zip \
build/runner-image-sbom.cdx.json \
build/action-sbom.cdx.json \
build/SHA256SUMS
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
node_modules/
coverage/
build/
__pycache__/
*.py[cod]
*.log
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Changelog

## Unreleased

- Node.js 24 start/stop Action with strict validation.
- Repository-scoped, single-use JIT runners.
- Deterministic launch idempotency and quota-aware retries/polling.
- Partial-failure and explicit cleanup.
- Snapshot-safe AL2023 ARM64 runner image with Docker, Buildx, and Compose.
- Lifecycle supervisor with fresh Docker startup and self-termination.
- Direct AWS CLI bootstrap, image build tooling, CI, release SBOMs, and
examples.
33 changes: 30 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,28 @@ The Action implements and tests:
- idempotent Lambda MicroVM launch, readiness, cleanup, and termination;
- typed GitHub and AWS adapters with mocked-boundary integration tests.

The production AL2023 runner image is implemented and locally validated,
including nested Docker with the documented local `vfs` fallback. AWS image
build and private-repository end-to-end validation remain release gates.
The production AL2023 runner image is implemented and validated locally and
through the AWS image build hooks with production `overlay2`. Private-repository
end-to-end validation remains a release gate.

## Minimal setup

The setup is two direct scripts. It does not require an infrastructure
framework:

```bash
export AWS_REGION=us-east-1
export GITHUB_REPOSITORY=OWNER/PRIVATE_REPOSITORY

scripts/bootstrap-aws.sh
scripts/build-microvm-image.sh
```

The first command idempotently creates the private S3 artifact bucket,
CloudWatch log groups, GitHub OIDC provider, and three least-privilege IAM
roles. It saves the discovered resource values to `build/aws-setup.json`. The
second command consumes that file automatically and saves the active image
details to `build/microvm-image.json`.

## Usage

Expand Down Expand Up @@ -52,6 +71,14 @@ repositories with trusted workflow changes. It has no webhook, queue,
dispatcher, warm pool, shell ingress, persistent runner, or boot-time package
installation.

Detailed guides:

- [installation](docs/installation.md)
- [security model](docs/security.md)
- [operations and quotas](docs/operations.md)
- [testing and release gates](docs/testing.md)
- [runner image](runner-image/README.md)

## License

MIT
103 changes: 103 additions & 0 deletions docs/installation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Installation

Version 1 is for private repositories with trusted workflow changes. It requires
an ARM64-capable Lambda MicroVM Region and an AWS account with enough MicroVM
memory quota for at least one 4 GiB runner.

## 1. Create the AWS resources

Use local AWS credentials that can create IAM roles, an IAM OIDC provider, an S3
bucket, and CloudWatch log groups:

```bash
export AWS_REGION=us-east-1
export GITHUB_REPOSITORY=OWNER/PRIVATE_REPOSITORY

scripts/bootstrap-aws.sh
```

This direct, idempotent script creates:

- one private, encrypted, versioned S3 artifact bucket;
- build and runtime CloudWatch log groups with 30-day retention;
- the account-level GitHub Actions OIDC provider if it is absent;
- an image build role;
- a restricted MicroVM runtime role;
- a GitHub OIDC launch role trusted only for the repository's `main` branch.

It writes the resulting values to `build/aws-setup.json`. Run it again to
reconcile the same resources.

For a different default branch, set `GITHUB_DEFAULT_BRANCH`. For a GitHub
Environment or another exact OIDC subject, set `GITHUB_OIDC_SUBJECT` explicitly.
Do not use a wildcard subject for untrusted pull-request refs.

No IAM user or stored AWS access key is needed by GitHub.

## 2. Build the MicroVM image

The build command reads `build/aws-setup.json` automatically:

```bash
scripts/build-microvm-image.sh
```

It packages and uploads a content-addressed artifact, creates or updates the
image, waits for validation, activates the successful version, and keeps a
bounded rollback set. The active ARN and version are written to
`build/microvm-image.json`.

## 3. Create a GitHub App

Create and install a GitHub App only on the runner repository. Grant repository
Administration read/write permission so it can create, inspect, and delete JIT
runners.

Record its App ID and download its private key. A compatible fine-grained PAT
can be passed directly, but short-lived installation tokens are preferred.

## 4. Configure the GitHub repository

With `gh auth status` working, set the generated AWS and image values:

```bash
scripts/configure-github.sh
```

Then set the GitHub App credentials:

```bash
gh variable set RUNNER_APP_ID --body APP_ID
gh secret set RUNNER_APP_PRIVATE_KEY < path/to/app.private-key.pem
```

Alternatively, configure everything in the helper invocation:

```bash
RUNNER_APP_ID=APP_ID \
RUNNER_APP_PRIVATE_KEY_FILE=path/to/app.private-key.pem \
scripts/configure-github.sh
```

The helper creates these repository variables:

- `MICROVM_AWS_REGION`;
- `MICROVM_LAUNCH_ROLE_ARN`;
- `MICROVM_EXECUTION_ROLE_ARN`;
- `MICROVM_RUNTIME_LOG_GROUP`;
- `MICROVM_RUNNER_IMAGE_ARN`;
- `MICROVM_RUNNER_IMAGE_VERSION`.

Copy [the basic workflow](../examples/basic.yml) into
`.github/workflows/microvm-runner.yml`. Pin every third-party Action and this
Action to reviewed immutable commits before production use.

## 5. Verify

Run the workflow manually and confirm:

1. start emits a unique label and MicroVM ID;
2. the target runs on ARM64 and `docker info`, Buildx, and Compose succeed;
3. the JIT runner processes only that job;
4. the MicroVM reaches `TERMINATED`;
5. no GitHub token or JIT payload appears in Actions or CloudWatch logs.
67 changes: 67 additions & 0 deletions docs/operations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Operations

## Quotas

MicroVM API and memory quotas are shared per AWS account and Region. The
documented baseline includes 5 `RunMicrovm` requests per second with burst 5, 10
`TerminateMicrovm` requests per second with burst 10, and 100 `GetMicrovm`
requests per second with burst 100.

The Action uses bounded full-jitter launch and termination retries, a stable
launch client token, randomized sequential polling, and immediate failure for
capacity exhaustion. For sustained rates above the launch quota, request a quota
increase or shape GitHub workflow concurrency. Do not add an internal queue to
this product.

## Logs

The AWS bootstrap creates build and runtime log groups with retention. Action
logs contain non-secret stage and resource metadata. Supervisor logs contain
lifecycle state, Docker failure tails, process exit codes, and cleanup outcomes,
but never hook bodies or JIT values.

## Orphan audit

Periodically inspect running and suspended MicroVMs:

```bash
aws lambda-microvms list-microvms \
--region us-east-1 \
--query 'items[?state==`RUNNING` || state==`SUSPENDED`].[microvmId,state,startedAt,imageArn]' \
--output table
```

Investigate runners near their maximum duration and terminate confirmed orphans.
Alert on repeated self-termination failures or VMs consistently reaching the
duration backstop.

## Image updates and rollback

The build script activates a version only after its `/ready` and `/validate`
hooks succeed. It then makes older versions inactive and retains a bounded
rollback set. Workflows should pin `image-version`.

To roll back:

```bash
aws lambda-microvms update-microvm-image-version \
--image-identifier IMAGE_ARN \
--image-version PREVIOUS_VERSION \
--status ACTIVE
```

Update the repository's `MICROVM_RUNNER_IMAGE_VERSION` variable after the
version is active.

## Common failures

- `ServiceQuotaExceededException`: inspect regional MicroVM memory quota and
currently running/suspended VMs.
- image inactive or missing: verify the pinned ARN/version and activation
status.
- runner remains offline: inspect `/run`, Docker, DNS, and GitHub egress logs;
start cleanup should terminate the VM.
- Docker validation failure: production must use `overlay2`; never enable the
local `vfs` fallback in AWS.
- self-termination denied: correct the runtime role; the explicit stop job and
maximum duration remain active.
Loading
Loading