{"id":51522320,"url":"https://github.com/djakish/lambda-micro-vm-gitlab-runner","last_synced_at":"2026-07-08T17:00:52.240Z","repository":{"id":368603888,"uuid":"1285921668","full_name":"djakish/lambda-micro-vm-gitlab-runner","owner":"djakish","description":"Run each GitLab CI/CD job in its own throwaway AWS Lambda MicroVM — a GitLab custom executor with per-job Firecracker isolation.","archived":false,"fork":false,"pushed_at":"2026-07-01T09:48:47.000Z","size":45,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-01T11:25:24.501Z","etag":null,"topics":["aws-lambda","gitlab-runner","golang","microvm"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/djakish.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-07-01T09:13:49.000Z","updated_at":"2026-07-01T09:48:51.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/djakish/lambda-micro-vm-gitlab-runner","commit_stats":null,"previous_names":["djakish/lambda-micro-vm-gitlab-runner"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/djakish/lambda-micro-vm-gitlab-runner","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/djakish%2Flambda-micro-vm-gitlab-runner","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/djakish%2Flambda-micro-vm-gitlab-runner/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/djakish%2Flambda-micro-vm-gitlab-runner/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/djakish%2Flambda-micro-vm-gitlab-runner/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/djakish","download_url":"https://codeload.github.com/djakish/lambda-micro-vm-gitlab-runner/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/djakish%2Flambda-micro-vm-gitlab-runner/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35271852,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-08T02:00:06.796Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aws-lambda","gitlab-runner","golang","microvm"],"created_at":"2026-07-08T17:00:51.495Z","updated_at":"2026-07-08T17:00:52.227Z","avatar_url":"https://github.com/djakish.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# GitLab Runner — AWS Lambda MicroVM executor\n\n[![CI](https://github.com/djakish/lambda-micro-vm-gitlab-runner/actions/workflows/ci.yml/badge.svg)](https://github.com/djakish/lambda-micro-vm-gitlab-runner/actions/workflows/ci.yml)\n\nRun each GitLab CI/CD job in its own throwaway [AWS Lambda MicroVM](https://docs.aws.amazon.com/lambda/latest/dg/lambda-microvms-guide.html): a Firecracker-isolated, snapshot-booted environment that starts in a moment, runs the job, and is terminated when the job ends.\n\nThis is a [GitLab custom executor](https://docs.gitlab.com/runner/executors/custom/) — the runner host stays tiny and stateless; all job work happens inside per-job MicroVMs with VM-level isolation between tenants.\n\n## Why this exists\n\nLambda MicroVMs are purpose-built for \"multi-tenant CI/CD — task executors that require isolation between tenants.\" They give you:\n\n- **Strong isolation** — every job gets its own kernel via Firecracker; nothing is shared between jobs.\n- **Fast starts** — MicroVMs resume from a pre-initialized snapshot, so the environment (and the exec agent) is already running when the VM appears.\n- **No servers to manage** — no warm pool of EC2, no autoscaling group; the runner host only orchestrates.\n- **Cost that tracks usage** — you pay compute while a VM runs and terminate it at job end.\n\n## The key design constraint\n\nLambda MicroVMs expose **no SSH and no native exec API**. The only way in is the VM's dedicated HTTPS endpoint, authenticated with a per-VM [JWE token](https://docs.aws.amazon.com/lambda/latest/dg/microvms-networking.html) (`X-aws-proxy-auth` header, default port 8080).\n\nSo this executor ships in **two halves**:\n\n1. **`microvm-agent`** — a tiny HTTP server baked into the MicroVM image. It answers the Lambda lifecycle hooks (`/run`, etc.) and exposes `POST /exec`, which runs a shell script and **streams** stdout/stderr + the exit code back to the caller.\n2. **`microvm-executor`** — the driver CLI on the runner host. It implements the four custom-executor stages, calling `aws lambda-microvms` to manage the VM lifecycle and talking to the agent to run job scripts.\n\n```\nGitLab Runner host                              AWS Lambda MicroVMs\n┌────────────────────────┐                      ┌──────────────────────────────┐\n│ gitlab-runner (custom) │                      │   MicroVM (per job)          │\n│   │                    │  aws lambda-microvms │  ┌────────────────────────┐  │\n│   ├─ config  ──────────┼──────────────────────▶  │ microvm-agent :8080    │  │\n│   ├─ prepare ──────────┼─ run-microvm ───────▶│  │  /run, /healthz        │  │\n│   │                    │  create-auth-token   │  │  POST /exec  ──▶ bash  │  │\n│   ├─ run \u003cscript\u003e \u003cs\u003e  ┼─ HTTPS endpoint ─────┼─▶│  stream stdout/stderr  │  │\n│   │   (×11 sub-stages) │  X-aws-proxy-auth    │  │  + exit code (JSONL)   │  │\n│   └─ cleanup ──────────┼─ terminate-microvm ─▶│  └────────────────────────┘  │\n└────────────────────────┘                      └──────────────────────────────┘\n```\n\n## How a job flows through the stages\n\n| Stage | Runs | What the driver does |\n|-------|------|----------------------|\n| `config` | once | Prints `builds_dir` (`/builds`), `cache_dir` (`/cache`), `shell` (`bash`) as JSON. No AWS calls. |\n| `prepare` | once | Reaps any VM from a prior attempt; `run-microvm`; saves state; waits for `RUNNING`; mints an auth token scoped to the agent port; waits for the agent `/healthz`. |\n| `run` | 11+ | For each sub-stage (`prepare_script`, `get_sources`, `restore_cache`, `build_script`, `after_script`, `archive_cache`, `upload_artifacts`, …) reads the script GitLab generated, POSTs it to the agent `/exec`, streams output live to the job log, and maps the exit code onto GitLab's failure contract. |\n| `cleanup` | once (always) | `terminate-microvm` and removes state. Failures here are logged but never fail the job. |\n\nState (MicroVM id, endpoint, token) is handed between these separate processes via a per-job file under `MICROVM_STATE_DIR`, keyed by `CI_PROJECT_ID`/`CI_JOB_ID`. See [`internal/state`](internal/state/state.go).\n\nThe exec protocol (JSON Lines over a streamed HTTP body) is documented in [`internal/protocol`](internal/protocol/protocol.go). It was chosen over HTTP trailers because it survives any proxy that preserves ordinary streamed bodies (the MicroVM ingress does — it supports SSE and gRPC) and conveys the exit code inline.\n\n## Repository layout\n\n```\ninstall.sh              one-command bootstrap (build + wizard)\ncmd/microvm-executor/   driver CLI (setup/config/prepare/run/cleanup)\ncmd/microvm-agent/      in-VM HTTP agent (exec + lifecycle hooks)\ninternal/setup/         interactive install wizard\ninternal/agent/         agent handler (exec, streaming, hooks)\ninternal/agentclient/   driver→agent streaming client\ninternal/microvm/       thin `aws lambda-microvms` wrapper\ninternal/config/        env-driven driver configuration\ninternal/state/         per-job state file handoff\ninternal/protocol/      the exec wire format shared by both binaries\nimage/                  MicroVM image Dockerfile + build/publish script\ndeploy/                 config.toml example + IAM policies\n```\n\n## Quickstart\n\nOne command builds the driver and walks you through the whole setup — it detects\nyour AWS account, offers to create the S3 bucket and IAM build role, publishes the\nMicroVM image, and writes a ready-to-use `config.toml`:\n\n```bash\ngit clone https://github.com/djakish/lambda-micro-vm-gitlab-runner.git\ncd lambda-micro-vm-gitlab-runner\n./install.sh          # or: make setup\n```\n\n```\n┌─ GitLab Runner · AWS Lambda MicroVM executor ─┐\n└─ interactive setup                            ┘\n\n1/5  Preflight\n  ✓ found AWS CLI: /usr/local/bin/aws\n  ✓ authenticated as arn:aws:iam::123456789012:user/you (account 123456789012)\n\n2/5  MicroVM image\n  Build \u0026 publish a new MicroVM image now? (Y/n): y\n  ✓ created s3://microvm-ci-123456789012-us-east-1\n  ✓ created role MicrovmBuildRole with S3 + logs permissions\n  ✓ image built: arn:aws:lambda:us-east-1:123456789012:microvm-image:gitlab-ci-runner\n\n3/5  GitLab Runner   …   4/5  Paths \u0026 limits   …   5/5  Generate \u0026 install\n  ✓ wrote ./config.toml\nDone 🎉\n```\n\nPrefer to do it by hand? The manual steps are below.\n\n## Prerequisites\n\n- An AWS account with Lambda MicroVMs available in your region.\n- An S3 bucket for the image build artifact (the wizard can create it).\n- Go 1.26+ (to build the binaries) and AWS CLI v2 new enough to include `lambda-microvms`.\n- A GitLab Runner host (EC2 recommended, so it can use an instance profile) with the AWS CLI installed and credentials that can drive MicroVMs.\n\n## Manual setup\n\n### 1. Build the MicroVM image\n\nCreate the build role (Lambda assumes it to build your image):\n\n```bash\naws iam create-role --role-name MicrovmBuildRole \\\n  --assume-role-policy-document file://deploy/iam/build-role-trust.json\naws iam put-role-policy --role-name MicrovmBuildRole --policy-name build \\\n  --policy-document file://deploy/iam/build-role-policy.json   # edit the bucket first\n```\n\nPublish the image (Lambda builds the Dockerfile — you don't need Docker locally):\n\n```bash\nexport AWS_REGION=us-east-1\nexport MICROVM_S3_BUCKET=my-artifact-bucket\nexport MICROVM_BUILD_ROLE_ARN=arn:aws:iam::123456789012:role/MicrovmBuildRole\nexport MICROVM_BASE_IMAGE_ARN=arn:aws:lambda:us-east-1:aws:microvm-image:al2023-1\nimage/build-and-publish.sh\n```\n\nThe script prints the resulting **image ARN** — use it as `MICROVM_IMAGE_ARN` below.\n\nThe default [image](image/Dockerfile) is Debian + `git`, `git-lfs`, the `gitlab-runner` binary (needed *inside* the VM for `get_sources` and cache/artifact steps), **Docker Engine + buildx** (see [Building Docker images](#building-docker-images)), and the agent. **Add your own build tooling** (compilers, SDKs) in a downstream layer or by editing the Dockerfile.\n\n### 2. Install the driver on the runner host\n\n```bash\nmake executor\nsudo install -D -m 0755 bin/microvm-executor /opt/microvm-executor/bin/microvm-executor\n```\n\nGive the runner host permission to drive MicroVMs by attaching\n[`deploy/iam/runner-host-policy.json`](deploy/iam/runner-host-policy.json) to its instance profile.\n(⚠️ Confirm the IAM action names against the service's IAM reference — see the note in that file.)\n\n### 3. Configure the runner\n\nCopy [`deploy/config.toml.example`](deploy/config.toml.example) to `/etc/gitlab-runner/config.toml`, set your runner token and `MICROVM_IMAGE_ARN`, then restart the runner. That's it.\n\n### 4. Run a job\n\nAny ordinary `.gitlab-ci.yml` works — nothing MicroVM-specific is required:\n\n```yaml\nbuild:\n  script:\n    - echo \"Running inside a Lambda MicroVM\"\n    - uname -a\n    - git --version\n```\n\nA job may override the image per-run with a variable:\n\n```yaml\nbuild:\n  variables:\n    MICROVM_IMAGE: arn:aws:lambda:us-east-1:123456789012:microvm-image:my-custom-image\n  script: [ make ]\n```\n\n## Cost \u0026 lifecycle\n\n- **Running** MicroVMs incur compute charges; **suspended** incur only snapshot storage; **terminated** incur nothing.\n- The driver **terminates** the VM at `cleanup`, so the normal cost is just the job's wall-clock.\n- `MICROVM_MAX_DURATION_SECONDS` (default 4h, max 8h) is a backstop: Lambda terminates a VM that outlives it, so a hung job or a crashed runner can't leak a VM forever.\n- The idle policy auto-suspends a VM with no endpoint traffic; for CI this mainly protects orphans, since active jobs keep the endpoint busy.\n\n## Security\n\n- Each job is a separate Firecracker MicroVM — no shared kernel or filesystem between jobs.\n- The endpoint auth token is a JWE **scoped to a single port** with a short TTL (job timeout + buffer), stored `0600` on the runner host.\n- Lock job egress to your VPC with an egress network connector (`MICROVM_EGRESS_CONNECTORS`) — see [Networking](https://docs.aws.amazon.com/lambda/latest/dg/microvms-networking.html).\n- The runner host holds AWS credentials; the MicroVMs themselves only get the optional `MICROVM_EXECUTION_ROLE_ARN` you grant them.\n\n## Building Docker images\n\nThe default image ships **Docker Engine + buildx**, and the entrypoint starts\n`dockerd` before the agent — so `docker` just works inside a job, no\n`services: docker:dind`, no `--privileged`, no TLS-to-a-sidecar dance. This is\nsafe here because each job is its own Firecracker MicroVM: the VM *is* the\nsandbox, so there is no shared host to escape to. (Docker uses namespaces/cgroups,\nnot nested virtualization, so it runs fine in a microVM.)\n\n```yaml\nbuild-image:\n  variables:\n    REGISTRY: 123456789012.dkr.ecr.us-east-1.amazonaws.com\n  script:\n    # Auth to ECR (VM has internet egress; or grant MICROVM_EXECUTION_ROLE_ARN):\n    - aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin \"$REGISTRY\"\n    - docker buildx build -t \"$REGISTRY/$CI_PROJECT_PATH:$CI_COMMIT_SHA\" --push .\n```\n\nNotes:\n- **Multi-arch** works via `docker buildx` + QEMU (userspace emulation — no nested virt needed).\n- **Sizing:** image builds are heavier than test jobs; run a larger MicroVM size. The guest kernel needs the usual Docker features (overlayfs, br_netfilter) — the standard base image has them.\n- **Disable Docker** for lean test-only images by setting `MICROVM_ENABLE_DOCKER=0` (skips starting `dockerd`).\n- Kaniko is not used — Google [archived it in June 2025](https://github.com/GoogleContainerTools/kaniko/issues/3348); real Docker in a VM is the simpler, better-isolated path here.\n\n## Limitations \u0026 future work\n\n- **`services:` are not started.** `CUSTOM_ENV_CI_JOB_SERVICES` is available to the driver but service containers aren't launched. To support them, launch them inside the VM (Docker is already there) or as companion MicroVMs.\n- **Interactive web terminals** are not supported.\n- The driver shells out to the **AWS CLI** rather than an SDK, since `lambda-microvms` is new; the CLI must be present on the runner host.\n\n## Development\n\n```bash\nmake build      # host binaries into ./bin\nmake test       # unit + end-to-end exec tests (agent ⇄ client over httptest)\nmake vet\nmake agent-linux MICROVM_ARCH=arm64   # cross-compile the agent\n```\n\nThe exec path is covered end-to-end in [`internal/agentclient/exec_test.go`](internal/agentclient/exec_test.go): it runs the real agent handler behind an httptest server and asserts streamed output, exit codes, large-log streaming, and cancellation.\n\n## Versioning \u0026 releases\n\nReleases follow [SemVer](https://semver.org/): tag `vMAJOR.MINOR.PATCH`. The\nversion is stamped into the binary at build time (`-X main.version`) and shown by:\n\n```bash\nmicrovm-executor version\n```\n\nCI (`.github/workflows/ci.yml`) runs gofmt/vet/build/test on every push and PR.\nPushing a `v*` tag triggers `.github/workflows/release.yml`, which cross-compiles\n`microvm-executor` (linux + darwin) and `microvm-agent` (linux) and attaches the\narchives + `checksums.txt` to a GitHub Release:\n\n```bash\ngit tag v0.1.0\ngit push origin v0.1.0\n```\n\nTagging is optional — `install.sh` builds straight from source — but it gives you\nreproducible, checksummed artifacts and lets `go install ...@v0.1.0` pin a version.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdjakish%2Flambda-micro-vm-gitlab-runner","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdjakish%2Flambda-micro-vm-gitlab-runner","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdjakish%2Flambda-micro-vm-gitlab-runner/lists"}