Skip to content

Commit 04df3d3

Browse files
authored
Merge pull request #3181 from rrmarq/lambda-microvms-code-server
Add Lambda MicroVM code-server pattern
2 parents a55067c + 4ab00bd commit 04df3d3

10 files changed

Lines changed: 997 additions & 0 deletions

File tree

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
# VS Code Server (code-server) on AWS Lambda MicroVMs
2+
3+
This pattern deploys **VS Code Server (code-server)** inside AWS Lambda MicroVMs. It gives you a full VS Code IDE accessible from your browser, running in a Firecracker-isolated sandbox with Python, AWS CLI, and project persistence.
4+
5+
The Lambda MicroVM image is built declaratively via CloudFormation using the `AWS::Lambda::MicrovmImage` resource with full lifecycle hook management (ready, run, suspend, resume, terminate).
6+
7+
Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/lambda-microvms-code-server
8+
9+
Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example.
10+
11+
## How it works
12+
13+
1. **Image Build**: CloudFormation creates the `AWS::Lambda::MicrovmImage` resource. Lambda downloads the zip from S3, executes the Dockerfile (installs code-server, Python, AWS CLI), starts the lifecycle hooks handler on port 9000, and waits for `/ready`. Once ready, Lambda takes a snapshot.
14+
2. **Run**: The Lambda MicroVM resumes from snapshot in under a second. The `/run` hook fires with the `microVmId`.
15+
3. **Connect**: You generate an auth token and use the local proxy (`proxy.py`) to access code-server through your browser.
16+
4. **Suspend/Resume**: After idle, the VM suspends (the `/suspend` hook fires). On the next request it resumes (the `/resume` hook fires). State is preserved across cycles.
17+
5. **Terminate**: When the VM is terminated, the `/terminate` hook fires for graceful cleanup.
18+
19+
20+
## Prerequisites
21+
22+
- Updated [AWS CLI v2](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html) (with `lambda-microvms` subcommand available)
23+
- [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
24+
- Python 3.14+ — for the local proxy
25+
- `aiohttp` — for the WebSocket-capable proxy
26+
27+
### Install proxy dependencies
28+
29+
```bash
30+
pip3 install aiohttp
31+
```
32+
33+
## Deployment
34+
35+
### Quick Start (deploy.sh)
36+
37+
`deploy.sh` automates the entire deployment (bucket creation, packaging, CloudFormation deploy, and running the MicroVM):
38+
39+
```bash
40+
export ACCOUNT_ID="YOUR-ACCOUNT-ID"
41+
export AWS_REGION="us-east-2" # optional, defaults to us-east-2
42+
chmod +x deploy.sh
43+
./deploy.sh
44+
```
45+
46+
The script prints the MicroVM ID and endpoint when complete. Then get a token and connect:
47+
48+
```bash
49+
MICROVM_ID="<from deploy output>"
50+
ENDPOINT="<from deploy output>"
51+
52+
TOKEN=$(aws lambda-microvms create-microvm-auth-token \
53+
--microvm-identifier "${MICROVM_ID}" \
54+
--expiration-in-minutes 60 \
55+
--allowed-ports allPorts={} \
56+
--region "${AWS_REGION}" \
57+
--query 'authToken."X-aws-proxy-auth"' --output text)
58+
59+
python3 proxy.py "https://${ENDPOINT}" "${TOKEN}"
60+
```
61+
62+
### Manual Step-by-Step
63+
64+
If you prefer to run each step individually:
65+
66+
#### Step 1: Set configuration
67+
68+
```bash
69+
export ACCOUNT_ID="YOUR-ACCOUNT-ID"
70+
export AWS_REGION="us-east-2"
71+
export S3_BUCKET="microvm-artifacts-${ACCOUNT_ID}"
72+
```
73+
74+
#### Step 2: Create S3 bucket
75+
76+
The image build pulls the code artifact from S3.
77+
78+
```bash
79+
aws s3 mb "s3://${S3_BUCKET}" --region "${AWS_REGION}"
80+
```
81+
82+
#### Step 3: Package and upload
83+
84+
Zip the `src/` directory (Dockerfile + app.py + start.sh) and upload to S3.
85+
86+
```bash
87+
cd src && zip -r /tmp/app.zip . && cd -
88+
aws s3 cp /tmp/app.zip "s3://${S3_BUCKET}/deployments/code-server.zip" --region "${AWS_REGION}"
89+
```
90+
91+
#### Step 4: Deploy infrastructure (CloudFormation)
92+
93+
The template creates two IAM roles (build + execution) and builds the MicroVM image with full lifecycle hooks. The image build takes 3–5 minutes (installs code-server, Python, AWS CLI).
94+
95+
```bash
96+
aws cloudformation deploy \
97+
--template-file template.yaml \
98+
--stack-name lambda-microvm-code-server \
99+
--parameter-overrides \
100+
S3Bucket="${S3_BUCKET}" \
101+
S3Key="deployments/code-server.zip" \
102+
ImageName="code-server" \
103+
--capabilities CAPABILITY_NAMED_IAM \
104+
--region "${AWS_REGION}"
105+
```
106+
107+
#### Step 5: Run the MicroVM
108+
109+
Start the Lambda MicroVM with the `HTTP_INGRESS` connector for browser access.
110+
111+
```bash
112+
IMAGE_ARN=$(aws cloudformation describe-stacks \
113+
--stack-name microvm-code-server --region "${AWS_REGION}" \
114+
--query 'Stacks[0].Outputs[?OutputKey==`ImageArn`].OutputValue' --output text)
115+
116+
EXEC_ROLE_ARN=$(aws cloudformation describe-stacks \
117+
--stack-name microvm-code-server --region "${AWS_REGION}" \
118+
--query 'Stacks[0].Outputs[?OutputKey==`ExecutionRoleArn`].OutputValue' --output text)
119+
120+
LAUNCH=$(aws lambda-microvms run-microvm \
121+
--image-identifier "${IMAGE_ARN}" \
122+
--execution-role-arn "${EXEC_ROLE_ARN}" \
123+
--ingress-network-connectors '["arn:aws:lambda:'"${AWS_REGION}"':aws:network-connector:aws-network-connector:HTTP_INGRESS"]' \
124+
--egress-network-connectors '["arn:aws:lambda:'"${AWS_REGION}"':aws:network-connector:aws-network-connector:INTERNET_EGRESS"]' \
125+
--idle-policy '{"maxIdleDurationSeconds":3600,"suspendedDurationSeconds":1800,"autoResumeEnabled":true}' \
126+
--logging '{"cloudWatch":{"logGroup":"/aws/lambda-microvms/code-server"}}' \
127+
--region "${AWS_REGION}")
128+
129+
echo "${LAUNCH}" | python3 -c "import sys,json; d=json.load(sys.stdin); print(f'MicroVM ID: {d[\"microvmId\"]}'); print(f'Endpoint: https://{d[\"endpoint\"]}')"
130+
```
131+
132+
Note the `microvmId` and `endpoint` from the output.
133+
134+
#### Step 6: Connect via proxy
135+
136+
```bash
137+
# Get an auth token (60-minute TTL)
138+
TOKEN=$(aws lambda-microvms create-microvm-auth-token \
139+
--microvm-identifier "${MICROVM_ID}" \
140+
--expiration-in-minutes 60 \
141+
--allowed-ports allPorts={} \
142+
--region "${AWS_REGION}" \
143+
--query 'authToken."X-aws-proxy-auth"' --output text)
144+
145+
# Open code-server in your browser via local proxy
146+
python3 proxy.py "https://${ENDPOINT}" "${TOKEN}"
147+
```
148+
149+
The proxy opens `http://127.0.0.1:8080` in your browser with VS Code ready.
150+
151+
## Lifecycle Hooks
152+
153+
The CloudFormation template configures all lifecycle hooks on port 9000:
154+
155+
| Hook | Purpose | Timeout |
156+
|------|---------|---------|
157+
| `/ready` | Signals image build is complete, snapshot taken | 300s |
158+
| `/run` | Fires when MicroVM starts, provides `microVmId` | 5s |
159+
| `/suspend` | Fires before suspension — flush state | 5s |
160+
| `/resume` | Fires after resumption — restore connections | 5s |
161+
| `/terminate` | Fires before termination — graceful shutdown | 5s |
162+
163+
The `app.py` handler responds to these hooks and logs each transition for observability.
164+
165+
## Project Structure
166+
167+
```
168+
├── README.md # This file
169+
├── template.yaml # CloudFormation (IAM + MicrovmImage with hooks)
170+
├── deploy.sh # Full deployment script
171+
├── proxy.py # Local HTTP+WebSocket proxy for browser access
172+
├── example-pattern.json # Serverless Land pattern metadata
173+
└── src/
174+
├── Dockerfile # code-server container image
175+
├── app.py # Lifecycle hooks + health endpoint
176+
└── start.sh # Entrypoint: hooks + code-server
177+
```
178+
179+
## Testing
180+
181+
1. Deploy the stack and run a MicroVM (see above).
182+
183+
2. Get a token and start the proxy:
184+
```bash
185+
python3 proxy.py "https://${ENDPOINT}" "${TOKEN}"
186+
```
187+
188+
3. Open `http://127.0.0.1:8080` — you should see VS Code.
189+
190+
4. Open a terminal inside VS Code and verify:
191+
```bash
192+
python3 --version
193+
aws --version
194+
```
195+
196+
5. Test health endpoint:
197+
```bash
198+
curl "https://${ENDPOINT}/healthz" -H "X-aws-proxy-auth: ${TOKEN}"
199+
```
200+
201+
## Cleanup
202+
203+
```bash
204+
# 1. Terminate the running MicroVM
205+
aws lambda-microvms terminate-microvm \
206+
--microvm-identifier "${MICROVM_ID}" \
207+
--region us-east-2
208+
209+
# 2. Delete the CloudFormation stack (removes IAM roles + image)
210+
aws cloudformation delete-stack --stack-name microvm-code-server --region us-east-2
211+
212+
# 3. Delete S3 artifacts
213+
aws s3 rm "s3://${S3_BUCKET}/deployments/" --recursive
214+
```
215+
216+
---
217+
218+
Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved.
219+
220+
SPDX-License-Identifier: MIT-0
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#!/bin/bash
2+
# deploy.sh — Deploy the AWS Lambda MicroVMs Code Server
3+
#
4+
# Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved.
5+
# SPDX-License-Identifier: MIT-0
6+
7+
set -euo pipefail
8+
9+
AWS_REGION="${AWS_REGION:-us-east-2}"
10+
ACCOUNT_ID="${ACCOUNT_ID:-$(aws sts get-caller-identity --query Account --output text)}"
11+
S3_BUCKET="${S3_BUCKET:-microvm-artifacts-${ACCOUNT_ID}}"
12+
IMAGE_NAME="lambda-mvm-code-server"
13+
STACK_NAME="microvm-${IMAGE_NAME}"
14+
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
15+
S3_KEY="deployments/${IMAGE_NAME}-${TIMESTAMP}.zip"
16+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
17+
18+
echo "=== AWS Lambda MicroVMs Code Server ==="
19+
echo "Region: ${AWS_REGION}"
20+
echo "Account: ${ACCOUNT_ID}"
21+
echo "Stack: ${STACK_NAME}"
22+
echo ""
23+
24+
# ── Step 1: S3 bucket ────────────────────────────────────────────────────────
25+
echo ">>> Step 1: Ensuring S3 bucket..."
26+
if ! aws s3api head-bucket --bucket "${S3_BUCKET}" 2>/dev/null; then
27+
aws s3 mb "s3://${S3_BUCKET}" --region "${AWS_REGION}"
28+
fi
29+
30+
# ── Step 2: Package and upload ───────────────────────────────────────────────
31+
echo ">>> Step 2: Packaging source..."
32+
cd "${SCRIPT_DIR}/src"
33+
zip -r /tmp/app.zip .
34+
aws s3 cp /tmp/app.zip "s3://${S3_BUCKET}/${S3_KEY}" --region "${AWS_REGION}"
35+
rm -f /tmp/app.zip
36+
cd - > /dev/null
37+
echo " Uploaded to s3://${S3_BUCKET}/${S3_KEY}"
38+
39+
# ── Step 3: Deploy CloudFormation stack (IAM + image build) ──────────────────
40+
echo ">>> Step 3: Deploying CloudFormation stack..."
41+
aws cloudformation deploy \
42+
--template-file "${SCRIPT_DIR}/template.yaml" \
43+
--stack-name "${STACK_NAME}" \
44+
--parameter-overrides \
45+
S3Bucket="${S3_BUCKET}" \
46+
S3Key="${S3_KEY}" \
47+
ImageName="${IMAGE_NAME}" \
48+
--capabilities CAPABILITY_NAMED_IAM \
49+
--region "${AWS_REGION}"
50+
51+
# ── Step 4: Get outputs ──────────────────────────────────────────────────────
52+
IMAGE_ARN=$(aws cloudformation describe-stacks \
53+
--stack-name "${STACK_NAME}" --region "${AWS_REGION}" \
54+
--query 'Stacks[0].Outputs[?OutputKey==`ImageArn`].OutputValue' --output text)
55+
EXEC_ROLE_ARN=$(aws cloudformation describe-stacks \
56+
--stack-name "${STACK_NAME}" --region "${AWS_REGION}" \
57+
--query 'Stacks[0].Outputs[?OutputKey==`ExecutionRoleArn`].OutputValue' --output text)
58+
59+
echo " Image: ${IMAGE_ARN}"
60+
echo " Role: ${EXEC_ROLE_ARN}"
61+
62+
# ── Step 5: Run MicroVM with HTTP_INGRESS ────────────────────────────────────
63+
echo ">>> Step 5: Running MicroVM..."
64+
LAUNCH=$(aws lambda-microvms run-microvm \
65+
--image-identifier "${IMAGE_ARN}" \
66+
--execution-role-arn "${EXEC_ROLE_ARN}" \
67+
--ingress-network-connectors '["arn:aws:lambda:'"${AWS_REGION}"':aws:network-connector:aws-network-connector:HTTP_INGRESS"]' \
68+
--egress-network-connectors '["arn:aws:lambda:'"${AWS_REGION}"':aws:network-connector:aws-network-connector:INTERNET_EGRESS"]' \
69+
--idle-policy '{"maxIdleDurationSeconds":3600,"suspendedDurationSeconds":1800,"autoResumeEnabled":true}' \
70+
--logging '{"cloudWatch":{"logGroup":"/aws/lambda-microvms/'"${IMAGE_NAME}"'"}}' \
71+
--region "${AWS_REGION}")
72+
73+
MICROVM_ID=$(echo "${LAUNCH}" | python3 -c "import sys,json; print(json.load(sys.stdin)['microvmId'])" 2>/dev/null || \
74+
echo "${LAUNCH}" | grep -o '"microvmId":"[^"]*"' | cut -d'"' -f4)
75+
MICROVM_EP=$(echo "${LAUNCH}" | python3 -c "import sys,json; print(json.load(sys.stdin)['endpoint'])" 2>/dev/null || \
76+
echo "${LAUNCH}" | grep -o '"endpoint":"[^"]*"' | cut -d'"' -f4)
77+
78+
echo ""
79+
echo "=== Deployment Complete ==="
80+
echo ""
81+
echo "MicroVM ID: ${MICROVM_ID}"
82+
echo "Endpoint: https://${MICROVM_EP}"
83+
echo ""
84+
echo "Next: get a token and open VS Code in your browser:"
85+
echo ""
86+
echo " TOKEN=\$(aws lambda-microvms create-microvm-auth-token \\"
87+
echo " --microvm-identifier \"${MICROVM_ID}\" \\"
88+
echo " --expiration-in-minutes 60 \\"
89+
echo " --allowed-ports allPorts={} \\"
90+
echo " --region \"${AWS_REGION}\" \\"
91+
echo " --query 'authToken.\"X-aws-proxy-auth\"' --output text)"
92+
echo ""
93+
echo " python3 proxy.py \"https://${MICROVM_EP}\" \"\$TOKEN\""
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
{
2+
"title": "VS Code Server (code-server) on AWS Lambda MicroVMs",
3+
"description": "Deploy VS Code Server (code-server) inside Lambda MicroVMs for a full browser-based IDE running in an isolated Firecracker sandbox. Includes Python, AWS CLI, and persistent workspace with automatic suspend/resume lifecycle management.",
4+
"language": "Python",
5+
"level": "200",
6+
"framework": "AWS CLI",
7+
"introBox": {
8+
"headline": "How it works",
9+
"text": [
10+
"This pattern deploys VS Code Server (code-server) inside Lambda MicroVMs, giving you a full IDE accessible from your browser running in an isolated Firecracker sandbox.",
11+
"The Lambda MicroVM image is built declaratively via CloudFormation using the AWS::Lambda::MicrovmImage resource. During the build, Lambda executes the Dockerfile server-side (installs code-server, Python, AWS CLI), waits for the /ready lifecycle hook on port 9000, and takes a snapshot for sub-second resume.",
12+
"Lifecycle hooks (run, suspend, resume, terminate) on port 9000 manage the MicroVM state transitions — enabling graceful suspend/resume without losing IDE state. The VM auto-suspends after idle and resumes on demand.",
13+
"A local Python proxy (proxy.py) injects the auth token header so your browser can reach the MicroVM endpoint over HTTP and WebSocket, giving you a seamless VS Code experience."
14+
]
15+
},
16+
"gitHub": {
17+
"template": {
18+
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/lambda-microvms-code-server",
19+
"templateURL": "serverless-patterns/lambda-microvms-code-server",
20+
"projectFolder": "lambda-microvms-code-server",
21+
"templateFile": "template.yaml"
22+
}
23+
},
24+
"resources": {
25+
"bullets": [
26+
{
27+
"text": "AWS Lambda MicroVMs Documentation",
28+
"link": "https://docs.aws.amazon.com/lambda/latest/dg/lambda-microvms.html"
29+
},
30+
{
31+
"text": "code-server (VS Code in the browser)",
32+
"link": "https://github.com/coder/code-server"
33+
},
34+
{
35+
"text": "Firecracker - Secure and fast microVMs for serverless computing",
36+
"link": "https://aws.amazon.com/blogs/opensource/firecracker-open-source-secure-fast-microvm-serverless/"
37+
}
38+
]
39+
},
40+
"deploy": {
41+
"headline": "Deploy the pattern",
42+
"text": [
43+
"pip3 install aiohttp",
44+
"export ACCOUNT_ID=\"YOUR-ACCOUNT-ID\"",
45+
"export AWS_REGION=\"YOUR-ACCOUNT-REGION\"",
46+
"chmod +x deploy.sh" ,
47+
"bash deploy.sh"
48+
]
49+
},
50+
"testing": {
51+
"text": [
52+
"See the GitHub repo for detailed testing instructions."
53+
]
54+
},
55+
"cleanup": {
56+
"text": [
57+
"See Cleanup section in README.md"
58+
]
59+
},
60+
"authors": [
61+
{
62+
"name": "Ricardo Marques",
63+
"image": "./images/rrmarq.jpg",
64+
"bio": "Sr Serverless Specialist, AWS",
65+
"linkedin": "ricardo-marques-aws"
66+
}
67+
]
68+
}
56.3 KB
Loading

0 commit comments

Comments
 (0)