,

Build Your First AWS Lambda MicroVM: Full Tutorial

By the end of this tutorial you’ll have a working AWS Lambda MicroVM deployment running a stateful Flask API — built from scratch, packaged into a Firecracker snapshot image, launched with a configurable idle policy, secured with short-lived auth tokens, and verified to retain state across a suspend/resume cycle. This is the real thing, not a toy demo. Let’s build it.

Prerequisites

  • An AWS account with access to a supported region (US East N. Virginia, US East Ohio, US West Oregon, Europe Ireland, or Asia Pacific Tokyo)
  • AWS CLI installed and configured with credentials that have permission to create IAM roles, S3 buckets, and Lambda MicroVM resources
  • Docker installed locally (for building and testing the image)
  • Python 3.11+ installed locally
  • Two IAM roles created in advance: one build role (MicroVMBuildRole) and one execution role (MicroVMExecutionRole) — scoped in the next step

Step 1: Create the Required IAM Roles

The build role needs to read from your S3 bucket and write to CloudWatch Logs. The execution role is what the running MicroVM assumes at runtime.

# Create the build role trust policy
cat > build-trust.json <<'EOF'
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "lambda-microvms.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}
EOF
aws iam create-role \
--role-name MicroVMBuildRole \
--assume-role-policy-document file://build-trust.json
aws iam attach-role-policy \
--role-name MicroVMBuildRole \
--policy-arn arn:aws:iam::aws:policy/CloudWatchLogsFullAccess
# Create the execution role (same trust policy, different permissions)
aws iam create-role \
--role-name MicroVMExecutionRole \
--assume-role-policy-document file://build-trust.json
aws iam attach-role-policy \
--role-name MicroVMExecutionRole \
--policy-arn arn:aws:iam::aws:policy/CloudWatchLogsFullAccess

Grab the ARNs from the output — you’ll need them in steps 4 and 5. Store your AWS account ID handy too: aws sts get-caller-identity --query Account --output text

Step 2: Write the Flask Application

This is a deliberately simple stateful API. It maintains an in-memory counter per session, which lets us prove that state survives a suspend/resume cycle in step 9.

# app.py
from flask import Flask, jsonify, request
app = Flask(__name__)
session_state = {"counter": 0, "history": []}
@app.route("/increment", methods=["POST"])
def increment():
session_state["counter"] += 1
entry = request.json.get("label", f"call-{session_state['counter']}")
session_state["history"].append(entry)
return jsonify({"counter": session_state["counter"], "history": session_state["history"]})
@app.route("/state", methods=["GET"])
def get_state():
return jsonify(session_state)
@app.route("/health", methods=["GET"])
def health():
return jsonify({"status": "ok"})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)
# requirements.txt
flask==3.0.3

Step 3: Write the Dockerfile

The base image is public.ecr.aws/lambda/microvms:al2023-minimal. Your Dockerfile extends it, installs dependencies, and sets the entrypoint. The initialization that runs at image-build time — in this case Flask starting up and reaching the ready state — becomes the frozen snapshot every MicroVM resumes from.

# Dockerfile
FROM public.ecr.aws/lambda/microvms:al2023-minimal
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
EXPOSE 8080
CMD ["python", "app.py"]

Test it locally before uploading anything. You shouldn’t be debugging a Dockerfile in the cloud when you can catch problems in 10 seconds on your laptop:

docker build -t microvm-flask-test .
docker run -p 8080:8080 microvm-flask-test
curl http://localhost:8080/health

Step 4: Package and Upload to S3

Lambda MicroVMs expects your application code as a zip artifact in S3 alongside the Dockerfile. Create a bucket, zip your code, and upload.

BUCKET_NAME="microvm-artifacts-$(aws sts get-caller-identity --query Account --output text)"
REGION="us-east-1"
aws s3 mb s3://$BUCKET_NAME --region $REGION
zip -r app-artifact.zip app.py requirements.txt Dockerfile
aws s3 cp app-artifact.zip s3://$BUCKET_NAME/flask-microvm/app-artifact.zip
MicroVM auto-resume from suspended state on incoming request

Step 5: Create the MicroVM Image

This is where Lambda runs your Dockerfile, initializes your application, and captures the Firecracker memory snapshot. Build times vary based on your dependencies — expect 2 to 5 minutes for a simple Flask app. Build logs stream to CloudWatch automatically.

ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
aws lambda-microvms create-microvm-image \
--code-artifact uri=s3://$BUCKET_NAME/flask-microvm/app-artifact.zip \
--name flask-session-image \
--base-image-arn arn:aws:lambda:us-east-1:aws:microvm-image:al2023-1 \
--build-role-arn arn:aws:iam::$ACCOUNT_ID:role/MicroVMBuildRole \
--region $REGION

Poll until the image status is ACTIVE:

aws lambda-microvms get-microvm-image \
--image-identifier arn:aws:lambda:$REGION:$ACCOUNT_ID:microvm-image:flask-session-image \
--query 'Status' --output text

Step 6: Launch the MicroVM with an Idle Policy

The idle policy below suspends the MicroVM after 15 minutes of no traffic, retains it in the suspended state for 5 minutes, and auto-resumes it on the next request. State is preserved through the suspension.

MICROVM_OUTPUT=$(aws lambda-microvms run-microvm \
--image-identifier arn:aws:lambda:$REGION:$ACCOUNT_ID:microvm-image:flask-session-image \
--execution-role-arn arn:aws:iam::$ACCOUNT_ID:role/MicroVMExecutionRole \
--idle-policy '{"maxIdleDurationSeconds":900,"suspendedDurationSeconds":300,"autoResumeEnabled":true}' \
--region $REGION)
MICROVM_ID=$(echo $MICROVM_OUTPUT | jq -r '.MicroVMId')
MICROVM_ENDPOINT=$(echo $MICROVM_OUTPUT | jq -r '.EndpointUrl')
echo "MicroVM ID: $MICROVM_ID"
echo "Endpoint: $MICROVM_ENDPOINT"

Step 7: Generate an Auth Token

Every request to the MicroVM endpoint requires a short-lived auth token. Generate one and store it for the next step.

TOKEN=$(aws lambda-microvms create-microvm-token \
--microvm-id $MICROVM_ID \
--duration-seconds 300 \
--region $REGION \
--query 'Token' --output text)
echo "Auth token: $TOKEN"

Step 8: Send Test Requests and Build State

With the token in hand, hit the endpoint. The X-aws-proxy-auth header is what authenticates you to the MicroVM.

# Check health
curl -H "X-aws-proxy-auth: $TOKEN" $MICROVM_ENDPOINT/health
# Increment the counter twice
curl -X POST \
-H "X-aws-proxy-auth: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"label": "first-call"}' \
$MICROVM_ENDPOINT/increment
curl -X POST \
-H "X-aws-proxy-auth: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"label": "second-call"}' \
$MICROVM_ENDPOINT/increment
# Read back the current state
curl -H "X-aws-proxy-auth: $TOKEN" $MICROVM_ENDPOINT/state

The state response should show "counter": 2 and a history array with both labels. That state lives in memory inside the MicroVM — the Firecracker snapshot mechanism is what makes it survive the next step.

Step 9: Suspend and Verify State Survival

For demonstration purposes, manually suspend the MicroVM rather than waiting for the idle timer:

aws lambda-microvms suspend-microvm \
--microvm-id $MICROVM_ID \
--region $REGION

Wait 10 seconds, then generate a fresh token (your previous one may have expired) and send another request. With autoResumeEnabled: true, the MicroVM wakes up automatically:

NEW_TOKEN=$(aws lambda-microvms create-microvm-token \
--microvm-id $MICROVM_ID \
--duration-seconds 300 \
--region $REGION \
--query 'Token' --output text)
curl -H "X-aws-proxy-auth: $NEW_TOKEN" $MICROVM_ENDPOINT/state

The response should still show "counter": 2 with your full history intact. That’s the entire point — the MicroVM was suspended (billed at idle rate, not full rate), then resumed in milliseconds, and your application state was exactly where you left it.

What You Built

You just went from zero to a fully operational Lambda MicroVM deployment. Specifically: a Flask API packaged into a Firecracker snapshot image with pre-initialized application state, launched as a dedicated isolated VM with a configurable idle-suspend policy, secured with short-lived auth tokens, and proven to preserve in-memory state across a suspend/resume cycle — all without managing a single virtual machine, auto-scaling group, or host operating system.

From here, the logical next steps are replacing the toy counter with your actual application logic, wiring the MicroVM launch and token generation into your backend API (a Lambda Function makes an excellent orchestration layer), and tuning the idle policy based on your users’ actual session patterns.

At Enkompass (enkompass.net), this is our home turf. We build event-driven, serverless systems that scale to zero and bill like it. If you’d rather have seasoned cloud people handle it instead of learning the hard way at 2 AM, let’s talk. Reach out through our contact page or call us directly at +1-828-436-5667 — we’ll help you turn this from a someday-project into a done-deal.

Ready when you are

Let’s map your route to AWS

A free 60-minute architecture review with a senior engineer. No slides, no SDR, just a plan.