Any Dockerfile β a real Firecracker microVM on AWS β public HTTPS URL. One command.
Quick start Β· Example apps Β· How it works Β· MicroVM or Lambda? Β· Costs Β· Troubleshooting
Lambda functions are great, right up until you need one of these:
- state that survives between requests
- a WebSocket that stays open
- a job that runs longer than 15 minutes
- to run untrusted code in real isolation
- a full OS: background processes,
dnf install, a real filesystem
The classic answer was "get an EC2 box and babysit it." AWS Lambda MicroVMs change that: full Firecracker virtual machines with Lambda ergonomics. They suspend when idle, resume in seconds when a request arrives, and cost $0 in compute while suspended.
This starter kit is the missing on-ramp. It turns "read the docs for a week" into:
./deploy.sh apps/playgroundWhat you get:
- π₯οΈ A real server: persistent processes, disk, root, your choice of runtime
- π WebSockets & long jobs: hours-long connections, no 15-minute ceiling
- π΄ Suspend/resume built in: idle 30 min β suspended β auto-resumes on the next request
- π¦ Ships like a container: if it has a
Dockerfile, it deploys - π Public URL out of the box: CloudFront + Lambda@Edge handle HTTPS, auth, and WSS
- π Or fully private: backend-only mode with short-lived auth tokens
- ποΈ IaC included: a CDK stack and CloudFormation templates for production
- π 20 gotchas pre-debugged: TROUBLESHOOTING.md is the week you don't have to lose
Prerequisites: AWS CLI β₯ 2.35.10 with an authenticated session. Supported regions:
us-east-1,us-east-2,us-west-2,eu-west-1,ap-northeast-1(scripts default toeu-west-1; changeREGIONat the top ofdeploy.sh)
git clone https://github.com/vidanov/lambda-microvm-starter
cd lambda-microvm-starter
# Public: anyone can open it in a browser
./deploy.sh apps/playground
# Private: a backend API, reachable only with an auth token
./deploy.sh apps/code-runner --private
./apps/code-runner/example-client.sh # smoke-test it
# Done playing?
./destroy.sh playgroundThe deploy script creates the S3 bucket and IAM roles, packages your app, builds the MicroVM image (a Firecracker snapshot), launches the VM, and, in public mode, puts CloudFront and Lambda@Edge in front of it. Then it prints your URL.
- Public mode: CloudFront terminates HTTPS and passes WebSockets through; a Lambda@Edge function injects the MicroVM auth token on every origin request. Visitors just see a URL that works.
- Private mode: no CloudFront at all. Your backend mints short-lived tokens (
create-microvm-auth-token) and calls the MicroVM endpoint directly. Nobody without a token gets in. - Lifecycle: after 30 idle minutes the VM is snapshotted and suspended (configurable). The next request wakes it with memory and disk intact. You pay per second of active time only.
Five working apps, each a single folder with a Dockerfile. Pick the one closest to what you're building:
| App | What it shows | Mode | Deploy |
|---|---|---|---|
π¨ playground |
Interactive marimo notebook with live data viz, open to anyone in a browser | public | ./deploy.sh apps/playground |
π marimo |
Reactive Python notebook: sliders, charts, pip install, persistent state |
public | ./deploy.sh apps/marimo |
π code-runner |
Sandboxed Python execution API, the classic AI-agent tool backend | private | ./deploy.sh apps/code-runner --private |
π pdf-generator |
HTML β PDF service (WeasyPrint) with an invoice template | private | ./deploy.sh apps/pdf-generator --private |
π€ shape-agent |
AI agent with Shape governance: phases, budgets, and an audit trail | private | ./deploy.sh apps/shape-agent --private |
π§ kiro |
AI development workspace: Kiro CLI + shell access + S3-synced workspace | private | ./deploy.sh apps/kiro --private |
Every private app ships with an example-client.sh that mints a token and exercises the API.
API details per app
POST /run:{"code": "print(1+1)", "timeout": 5}β{"stdout": "2\n", "exit_code": 0, "duration_ms": 12}GET /health: health check
Use it for: AI-agent tool execution, automated testing, REPL backends, code evaluation in LMS platforms.
POST /generate:{"html": "<h1>Hello</h1>"}β PDF binaryPOST /invoice:{"company": "...", "items": [...]}β formatted invoice PDF
Use it for: invoices, reports, certificates, or any other HTML-to-PDF pipeline.
POST /agent/explore: call READ tools (writes blocked)POST /agent/decide: evaluate options (writes blocked)POST /agent/commit: execute actions (writes allowed, budget-gated)GET /agent/status: budget, time, audit trailPOST /agent/reset: reset agent state
Governance rules: WRITE/IRREVERSIBLE tools only in COMMIT phase, irreversible tools blocked above 75% budget, everything blocked above 90% budget or time limit. MicroVMs provide the isolation boundary; Shape controls what happens inside it.
POST /kiro:{"prompt": "Create a Python script"}β{"output": "...", "exit_code": 0}POST /shell:{"command": "ls -la", "timeout": 30}β{"stdout": "...", "exit_code": 0}GET /files: list workspace filesGET /health: health check + Kiro CLI version
Use it for: AI-agent tool backends, automated code generation, remote dev environments, CI/CD prototyping.
An app is a folder with a Dockerfile that builds on the MicroVM base image and EXPOSEs a port. That's the whole contract. Python, Node, Go, whatever runs in a container:
# apps/my-app/Dockerfile
FROM public.ecr.aws/lambda/microvms:al2023-minimal
RUN dnf install -y python3 python3-pip && dnf clean all
RUN pip install --no-cache-dir flask
COPY app.py /app/app.py
WORKDIR /app
EXPOSE 5000
CMD ["python3", "app.py"]# apps/my-app/app.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Running in a Firecracker MicroVM!"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)./deploy.sh apps/my-appThe deploy script auto-detects the exposed port. The base image is ARM64 (Graviton), so fetch ARM builds when your Dockerfile downloads binaries.
Not every workload needs a VM. The honest decision table:
| Signal | MicroVM | Lambda function |
|---|---|---|
| State must survive between requests | β | |
| Untrusted / user-submitted code | β | |
| Runs longer than 15 minutes | β | |
| WebSocket / persistent connection | β | |
| Needs a full OS (FUSE, eBPF, background daemons) | β | |
| High-volume, stateless | β | |
| Event-driven (S3, SQS, EventBridge) | β | |
| Must scale to thousands of concurrent executions | β |
Rule of thumb: isolation + state + long runtime β MicroVM. Stateless + short + high-volume β Lambda function. (Example: pdf-generator works great as a demo here, but at high scale a plain Lambda function with a WeasyPrint layer is cheaper; the MicroVM version earns its keep when you need persistent caches, dependencies beyond layer limits, or renders longer than 15 minutes.)
eu-west-1, Graviton pricing:
| Usage pattern | 2 vCPU / 4 GB | 4 vCPU / 8 GB |
|---|---|---|
| Always-on 24/7 | ~$96/mo | ~$191/mo |
| 4 h/day, 20 days/mo | ~$11/mo | ~$22/mo |
| Bursty with auto-suspend | pay active seconds only | $0 while suspended |
| CloudFront + Lambda@Edge | ~$0-1/mo | ~$0-1/mo |
The economics in one sentence: MicroVMs win for anything bursty (dev environments, demos, per-user sandboxes, internal tools); for true always-on 24/7, EC2 is still 3-5x cheaper.
deploy.sh is built for fast iteration. For repeatable, reviewable infrastructure there are two IaC paths. The AWS::Lambda::MicrovmImage resource type works in both:
| Resource | Managed by | Why |
|---|---|---|
| MicroVM image, IAM, S3, CloudFront | CloudFormation / CDK | static, versioned infrastructure |
| Running MicroVMs | API/SDK at runtime | dynamic, per-user, ephemeral |
CloudFormation: two templates
Matching the two-phase lifecycle:
infra/cfn/microvm-image.yaml: image + IAM roles + S3 bucketinfra/cfn/cloudfront-public.yaml: CloudFront + Lambda@Edge (needs a running MicroVM endpoint)
# Phase 1: build the image
./deploy-cfn.sh apps/playground
# Phase 2: run a MicroVM, then point CloudFront at it
aws lambda-microvms run-microvm \
--image-identifier arn:aws:lambda:eu-west-1:ACCT:microvm-image:playground \
--execution-role-arn arn:aws:iam::ACCT:role/MicroVMExecRole-playground \
--idle-policy '{"maxIdleDurationSeconds":1800,"suspendedDurationSeconds":28800,"autoResumeEnabled":true}' \
--region eu-west-1
aws cloudformation deploy \
--template-file infra/cfn/cloudfront-public.yaml \
--stack-name microvm-playground-cdn \
--parameter-overrides \
MicrovmId=microvm-XXXX \
MicrovmEndpoint=XXXX.lambda-microvm.eu-west-1.on.aws \
AppPort=2718 \
--capabilities CAPABILITY_NAMED_IAM \
--region us-east-1CDK: one stack, including the MicroVM lifecycle
cd infra/cdk
pip install aws-cdk-lib constructs
# Bundle the orchestrator's boto3 (includes the lambda-microvms service model)
./orchestrator/build.sh
# Upload your app code
aws s3 cp app.zip s3://microvm-artifacts-ACCT-eu-west-1/images/playground.zip
# Deploy everything: image build β run MicroVM β CloudFront
cdk deploy -c app_name=playground -c app_port=2718 --profile YOUR_PROFILEThe stack uses a custom-resource orchestrator Lambda that runs the MicroVM, creates the Lambda@Edge function in us-east-1 (a CloudFront requirement), bakes the MicroVM ID into it, and wires it into CloudFront. cdk destroy tears all of it down.
Implementation notes worth knowing:
- The orchestrator bundles its own boto3 with the
lambda-microvmsservice model (not yet in the stock SDK) - IAM actions live in the
lambda:namespace (e.g.lambda:RunMicrovm), notlambda-microvms: - Lambda@Edge must be in us-east-1, so the orchestrator creates it cross-region
MicroVMs don't sit inside your VPC (same model as Lambda functions). To reach private resources, attach a Lambda Network Connector; outbound traffic then flows through your subnets:
aws lambda-microvms run-microvm \
--image-identifier ... \
--egress-network-connectors '["arn:aws:lambda:REGION:ACCT:network-connector:my-vpc-connector"]'Default is INTERNET_EGRESS (public internet).
Every app runs locally without AWS:
cd apps/playground && pip install -r requirements.txt && marimo edit app.py
cd apps/code-runner && pip install -r requirements.txt && uvicorn main:app --port 8080./destroy.sh playground # tears down the MicroVM (+ CloudFront if public)
# or manually:
aws lambda-microvms terminate-microvm --microvm-identifier MICROVM_ID --region eu-west-1It's a new service; sharp edges exist. TROUBLESHOOTING.md documents 20 real failures with fixes: IAM trust policies, WebSocket proxying, Lambda@Edge region traps, CDK custom-resource timeouts. Every entry is something I actually hit while building this kit. If you hit something new, open an issue.
βββ apps/ # Example apps (one folder + Dockerfile each)
β βββ playground/ # marimo notebook (public)
β βββ marimo/ # reactive notebook: sliders, charts (public)
β βββ code-runner/ # sandboxed Python execution API (private)
β βββ pdf-generator/ # HTML β PDF service (private)
β βββ shape-agent/ # governed AI agent (private)
β βββ kiro/ # AI dev workspace with Kiro CLI (private)
βββ infra/
β βββ cfn/ # CloudFormation templates (image, CloudFront)
β βββ cdk/ # CDK stack + custom-resource orchestrator
β βββ edge-auth/ # Lambda@Edge auth-token injector
βββ docs/ # README assets
βββ deploy.sh # One-command CLI deploy (fast iteration)
βββ deploy-cfn.sh # CloudFormation deploy (production)
βββ destroy.sh # Tear a deployment down
βββ TROUBLESHOOTING.md # 20 gotchas, pre-debugged
The most valuable contribution is a new example app. The pattern that fits is isolation + state + long runtime. Ideas where a MicroVM beats a Lambda function:
| Idea | Why a MicroVM | Effort |
|---|---|---|
playwright-runner |
pre-loaded Chromium in the snapshot = no cold browser start | medium |
jupyter-workspace |
per-user notebooks with pip install + persistent files |
medium |
game-server |
stateful multiplayer over WebSockets | medium |
ci-runner |
isolated builds, Docker-in-Docker | medium |
vulnerability-scanner |
untrusted tooling against customer code | low |
llm-sandbox |
per-tenant local LLM (Ollama / llama.cpp) | high |
Fork, branch, PR. A bug report with the failing deploy.sh log attached is gold.
See CONTRIBUTING.md for details.
MIT. Do whatever you want with it. If it saved you a debugging week, a star β helps the next person find it.