|
| 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 |
0 commit comments