Key Takeaway
AWS Lambda MicroVMs, announced on 22 June 2026, give each user or agent session its own Firecracker microVM that starts from a snapshot, suspends when idle and resumes with memory and disk intact, for up to 8 hours. They beat shared-kernel containers when untrusted code needs per-session state, and lose on stateless work.
The question I wanted answered was narrow. If every user of an AI agent product gets their own sandbox, what is the cheapest isolation boundary that still remembers what that user was doing when they come back tomorrow morning? Containers are cheap but share a kernel. Virtual machines isolate properly but take minutes. Lambda functions forget everything between calls.
Lambda MicroVMs is AWS's answer, and the launch post gives no Fargate comparison at all. So I read the announcement dated 22 June 2026, then the developer guide pages on lifecycle, images, snapshots, networking and quotas, and the Fargate storage and security pages for the other side of the table. I have not deployed it. Every number below comes from a page I read, and where the pages disagree or stay silent I say so.
What shipped, and the one idea behind it
The AWS News Blog post, published on 22 June 2026, describes Lambda MicroVMs as a new serverless compute primitive for running user-generated or AI-generated code in isolated, stateful environments. Each MicroVM serves one tenant, session or job, runs Amazon Linux 2023, has its own dedicated HTTPS endpoint, and is built on Firecracker, which the post says underpins over 15 trillion monthly Lambda function invocations. The post's own framing of the alternatives is the useful part: virtual machines isolate strongly but take minutes to start, containers start in seconds but share a kernel and need significant hardening for untrusted code, and functions suit event-driven request-response work rather than long interactive sessions that keep state.
The one idea is that a MicroVM is not booted, it is restored. When you create an image, Lambda runs your Dockerfile, starts your application, waits for it to say it is ready, and captures a Firecracker snapshot of disk and memory including every running process. Every MicroVM you run from that image version resumes from the same snapshot, so initialisation cost is paid once at build time. Size is chosen per image as a baseline with automatic vertical scaling up to four times that baseline, billed per second above it. The documentation lists these baselines.
| Baseline | Peak | Max disk |
|---|---|---|
| 0.5 GB memory, 0.25 vCPU | 2 GB memory, 1 vCPU | 8 GB |
| 1 GB memory, 0.5 vCPU | 4 GB memory, 2 vCPU | 8 GB |
| 2 GB memory, 1 vCPU (default) | 8 GB memory, 4 vCPU | 8 GB |
| 4 GB memory, 2 vCPU | 16 GB memory, 8 vCPU | 16 GB |
| 8 GB memory, 4 vCPU | 32 GB memory, 16 vCPU | 32 GB |
That table reconciles the launch post's headline limits of 16 vCPUs, 32 GB of memory and 32 GB of disk: they are the peak of the largest baseline, not what you get by default. The launch post also says MicroVMs are ARM64 only, and listed US East (N. Virginia and Ohio), US West (Oregon), Europe (Ireland) and Asia Pacific (Tokyo) at launch. I did not re-verify the region list after launch, so check it before you plan around a region.
Building the image: your Dockerfile becomes a memory snapshot
An image is a zip in Amazon S3 holding a Dockerfile and your artifacts, built on a Lambda-managed base image selected with base-image-arn. Lambda starts a fresh MicroVM from the base, executes the Dockerfile, launches your ENTRYPOINT or CMD, optionally waits for a ready hook to return HTTP 200, then snapshots. The sketch below follows the shape of the announcement's Flask example, with the ready hook path and the container base image taken from the documentation.
# Dockerfile, packaged with app.py and requirements.txt into agent-image.zip
FROM public.ecr.aws/lambda/microvms:al2023-minimal
WORKDIR /app
COPY . .
RUN pip install --no-cache-dir -r requirements.txt
# Whatever this process holds in memory when /ready returns 200
# is frozen into the snapshot and shared by every MicroVM from this version.
CMD ["python", "app.py"]
# Upload, then build. base-image-arn and build-role-arn are required
# on create, and on every update-microvm-image that triggers a build.
aws s3 cp agent-image.zip s3://my-bucket/agent-image.zip
aws lambda-microvms create-microvm-image \
--name agent-sandbox \
--code-artifact uri=s3://my-bucket/agent-image.zip \
--base-image-arn arn:aws:lambda:us-east-1:aws:microvm-image:al2023-1 \
--build-role-arn arn:aws:iam::123456789012:role/MicrovmBuildRole
# Build logs land in CloudWatch under /aws/lambda/microvms/agent-sandbox
# (per the announcement); the image becomes CREATED when the snapshot is done.Two details matter for an agent product. First, the image holds your runtime, code and background processes, so a pre-warmed interpreter, a loaded model client or an indexed repository checkout can live in the snapshot and cost nothing at session start. Second, a build hook named validate runs a request against a MicroVM started from the finished image, and the guide says Lambda samples which parts of the snapshot that request touches so it can prefetch them at run time. Running a mock agent request in validate is therefore a latency optimisation, not just a test.
Rebuild images on a schedule. Lambda publishes patched base image versions, an older one is marked DEPRECATED for 60 days and then EXPIRING for 30, after which you cannot create images, and an EXPIRED version cannot build or run. A rebuild is one update-microvm-image call with no base-image-version argument, which picks up the latest.
The lifecycle: six states and a clock you must set
A MicroVM moves through PENDING, RUNNING, SUSPENDING, SUSPENDED, TERMINATING and TERMINATED. The transitions are what make it a session primitive rather than a job runner, so it is worth reading them as a table rather than a diagram.
| State | What the guide says about it |
|---|---|
| PENDING | Resources are being allocated and the snapshot is loading. |
| RUNNING | Accepting traffic through its endpoint. The run hook has completed. Compute charges accrue. |
| SUSPENDING | The suspend hook is executing while disk and memory are checkpointed. |
| SUSPENDED | State preserved, no compute charges, snapshot storage charges instead. Resumes on traffic if autoResumeEnabled is true, or on an explicit resume call. |
| TERMINATING | The terminate hook is executing and resources are being released. Reached by an explicit call, by the maximum duration, or when suspendedDurationSeconds runs out. |
| TERMINATED | Terminal. It cannot be resumed or restarted, and incurs no charges. |
Three clocks govern the lifetime, and they are easy to confuse. maxIdleDurationSeconds is how long the endpoint can see no traffic before the MicroVM is suspended, with a maximum of 28,800. suspendedDurationSeconds is how long it may stay suspended before Lambda terminates it. maximumDurationInSeconds, from 1 to 28,800, caps total lifespan including suspended time, which is where the 8 hours in the launch post comes from. This is the launch command I would start an agent session with, using the values from the documentation's own example.
aws lambda-microvms run-microvm \
--image-identifier arn:aws:lambda:us-east-1:123456789012:microvm-image:agent-sandbox \
--execution-role-arn arn:aws:iam::123456789012:role/MicroVMExecutionRole \
--ingress-network-connectors arn:aws:lambda:us-east-1:aws:network-connector:aws-network-connector:ALL_INGRESS \
--egress-network-connectors arn:aws:lambda:us-east-1:aws:network-connector:aws-network-connector:INTERNET_EGRESS \
--idle-policy '{"autoResumeEnabled":true,"maxIdleDurationSeconds":900,"suspendedDurationSeconds":1800}' \
--maximum-duration-in-seconds 14400 \
--run-hook-payload 'tenant-4711'
# The response carries microvmId and an endpoint URL. One endpoint, one MicroVM:
# there is no load balancing across MicroVMs behind a single endpoint.The idle policy has a trap for agents specifically. Activity is defined as traffic through the endpoint, and the guide notes that for asynchronous applications that do not actively send or receive traffic through it, you should disable automatic suspension or pick a suitable idle duration. An agent that spends twenty minutes compiling a project inside the MicroVM while no request is open looks idle to Lambda. Since the idle maximum is 28,800 seconds, setting it to that value is the practical way to turn suspension off for long jobs.
Hooks: the code that makes a snapshot safe to share
Lambda calls four HTTP hooks on your application at lifecycle events: run after the MicroVM starts from the snapshot, suspend before it suspends, resume after it comes back and terminate before it goes away, all POST requests under /aws/lambda-microvms/runtime/v1. Traffic only starts after run returns HTTP 200, and a MicroVM stays in SUSPENDED until the resume hook returns. The run hook receives a JSON body with microvmId and the runHookPayload you passed, which is how a per-tenant value reaches the VM without baking it into the image. Here is a minimal agent runtime with all four, plus a route I use to prove uniqueness.
# app.py - the process that gets frozen into the snapshot
import hashlib, os, secrets, subprocess
from flask import Flask, jsonify, request
app = Flask(__name__)
HOOKS = "/aws/lambda-microvms/runtime/v1"
# Wrong: generated at import time, so it exists BEFORE the snapshot is taken
# and every MicroVM from this image version wakes up holding the same value.
BUILD_NONCE = secrets.token_hex(8)
# Right: filled in by the run hook, after this MicroVM has started.
SESSION = {"microvm_id": None, "tenant": None, "run_nonce": None}
@app.post(HOOKS + "/ready")
def ready():
return "", 200 # build time only; answer 503 immediately if not ready
@app.post(HOOKS + "/run")
def run():
body = request.get_json(silent=True) or {}
SESSION["microvm_id"] = body.get("microvmId")
SESSION["tenant"] = body.get("runHookPayload")
SESSION["run_nonce"] = secrets.token_hex(8) # CSPRNG, reads /dev/urandom
return "", 200 # traffic is forwarded only after this returns 200
@app.post(HOOKS + "/suspend")
def suspend():
os.sync() # flush workspace writes before the checkpoint
return "", 200
@app.post(HOOKS + "/resume")
def resume():
# Re-open upstream connections and refresh short-lived credentials here.
# The MicroVM stays SUSPENDED until this returns 200.
return "", 200
@app.post(HOOKS + "/terminate")
def terminate():
os.sync() # runs on final termination only, not on suspend
return "", 200
@app.get("/whoami")
def whoami():
fp = hashlib.sha256(BUILD_NONCE.encode()).hexdigest()[:8]
return jsonify(SESSION, build_nonce_fp=fp)
@app.post("/exec")
def exec_cmd():
argv = request.get_json()["argv"]
out = subprocess.run(argv, capture_output=True, text=True, timeout=60, cwd="/work")
return jsonify(code=out.returncode, stdout=out.stdout, stderr=out.stderr)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080) # 8080 is the default inbound portThe documentation gives a reason to treat randomness as a first-class concern: unique IDs, secrets and entropy generated during the build are shared across every MicroVM run from that image version. It recommends language CSPRNGs that read from /dev/random or /dev/urandom, names Java 11 and later, Node.js crypto.randomBytes, Python 3.12 and later and .NET 8 and later as correct by default, and says to use the AWS base image for OpenSSL because it ships an AWS-patched, snapshot-compatible build. Note one thing I did not find: the pages I read say the image must declare the port your hook listener uses, but the flag name for it was not on them, so take it from the create-microvm-image reference.
Driving it from an orchestrator, one session at a time
Every request to a MicroVM endpoint needs a JWE token in the X-aws-proxy-auth header, with no unauthenticated option. create-microvm-auth-token mints one scoped to a single MicroVM, a set of ports and an expiry, and requests go to port 8080 unless X-aws-proxy-port names another port the token allows. That gives you a clean pattern: your control plane owns the AWS credentials, and the browser or agent client only ever holds a short-lived token for exactly one session. The call names below are the ones the guide's SDK examples use; suspend and terminate follow the same naming as their CLI commands.
import boto3, requests
client = boto3.client("lambda-microvms")
IMAGE = "arn:aws:lambda:us-east-1:123456789012:microvm-image:agent-sandbox"
PORT = 8080
def start_session(tenant_id):
r = client.run_microvm(
imageIdentifier=IMAGE,
runHookPayload=tenant_id, # reaches the run hook, never the snapshot
idlePolicy={
"autoResumeEnabled": True,
"maxIdleDurationSeconds": 900,
"suspendedDurationSeconds": 1800,
},
maximumDurationInSeconds=14400,
)
return r["microvmId"], r["endpoint"]
def session_token(microvm_id):
r = client.create_microvm_auth_token(
microvmIdentifier=microvm_id,
expirationInMinutes=30,
allowedPorts=[{"port": PORT}], # one port, not allPorts
)
return r["authToken"]["X-aws-proxy-auth"]
def run_in_sandbox(endpoint, token, argv):
# The first call after an idle suspend is held while Lambda auto-resumes
# the VM, so allow a generous timeout. A failed resume returns 502.
resp = requests.post(
"https://" + endpoint + "/exec",
headers={"X-aws-proxy-auth": token},
json={"argv": argv},
timeout=120,
)
resp.raise_for_status()
return resp.json()
def end_session(microvm_id):
client.terminate_microvm(microvmIdentifier=microvm_id) # stops all chargesRun-time throttling is part of the design, not an edge case: the documented default for RunMicrovm is 5 calls per second with a burst of 5, and the guide's retry example uses exponential backoff with jitter on ThrottlingException. A product that starts a sandbox on every page load will hit that before it hits the memory quota, so start sessions on first agent use, not on sign-in.
MicroVM or Fargate: where the real difference sits
The tempting comparison is isolation, and it is the weaker one. A 2018 AWS Open Source Blog post says Fargate tasks moved from a dedicated EC2 VM per task to Firecracker microVMs without weakening kernel-level isolation between tasks. So both are VM-isolated. The difference I found in the documentation is what happens to a session while nobody is talking to it, and how much of the machine you are allowed to own.
| Concern | Lambda MicroVM | Fargate task |
|---|---|---|
| Unit of work | One tenant, session or job, with its own HTTPS endpoint | A task started from a task definition with run-task |
| Isolation | Own MicroVM, no shared kernel between users, per the launch post | Firecracker microVM per task, per the 2018 AWS blog post |
| While idle | Suspend with memory and disk kept, no compute charge, auto-resume on traffic | The ECS pages I read describe no suspend that keeps memory; I would not assume one |
| Storage | 8 to 32 GB disk depending on baseline size | 20 GiB ephemeral by default, up to 200 GiB with the ephemeralStorage parameter |
| Linux capabilities | additionalOsCapabilities accepts ALL, applied inside the VM boundary, for mounts, network namespaces or eBPF | Only SYS_PTRACE can be added |
| Starting state | Restored from a pre-initialised memory snapshot | The task pulls its container image, stored on the task's ephemeral storage |
That is the whole argument. If your sandbox is a fresh, short-lived worker that runs one tool call and disappears, Fargate or a plain container with a hardened runtime is simpler and has more disk. If the sandbox is a long-lived workspace with a shell history, an installed toolchain and a half-finished edit that the user returns to, suspend-and-resume is the feature you would otherwise build yourself with volumes and a state machine. Lambda functions stay the right place for the orchestration code in front of it, which the launch post itself recommends: a function can call a MicroVM for the steps that run untrusted code.
Where it bites: quotas, silent terminations and shared snapshots
The quotas page is where the economics show up. The default memory allocated across all MicroVMs is 400 GB per account per Region, which the page translates as 200 MicroVMs at 2 GB, with 1,024 GB in US East (N. Virginia), US West (Oregon), US East (Ohio) and Asia Pacific (Tokyo), burstable to four times the quota. The page also says new accounts get reduced concurrency and memory quotas that AWS raises automatically based on usage, so a proof of concept on a fresh account will understate what production gets and a launch day will not.
Per-MicroVM limits are tighter than they look and none are adjustable. Concurrent connections are 8 for a 1 vCPU MicroVM and 128 for 16 vCPUs, requests per second are 40 at 4 vCPU and 160 at 16 vCPU, and the endpoint's bandwidth scales with size, at 4 MB/s for the 2 GB default and 16 MB/s for 8 GB. An agent that streams build artefacts or large file diffs through the endpoint will feel that long before it feels the CPU.
| Limit | Documented value | Where an agent product feels it |
|---|---|---|
| Memory across all MicroVMs | 400 GB default per Region, 1,024 GB in four Regions, burst to 4 times | Concurrent active and suspended sessions; suspended MicroVMs still count |
| RunMicrovm rate | 5 per second, burst 5, adjustable | Session start storms after a login spike |
| SuspendMicrovm rate | 2 per second, burst 2, adjustable | Mass explicit suspend at end of a shift; prefer the idle policy |
| Concurrent connections per MicroVM | 8 at 1 vCPU up to 128 at 16 vCPU, not adjustable | An agent UI opening many parallel streams to one sandbox |
| Total duration | 28,800 seconds, not adjustable | Multi-day workspaces must be rebuilt or checkpointed elsewhere |
Three silent failures worth designing around. If the run hook fails or times out, the MicroVM may go straight to TERMINATING without ever reaching RUNNING. If auto-resume does not succeed, the caller gets a 502 Bad Gateway. And anything generated before the snapshot is shared: a token, an ID or a seeded random generator created at import time is identical across every session from that image version.
There is also a documentation inconsistency I would not paper over. The RunMicrovm guide says the run hook payload can be up to 16 KB, while the API reference lists a length of 0 to 4096 for the same field and itself flags the conflict. Treat 4096 characters as the safe ceiling and pass a pointer, such as a secret path, rather than the secret.
What to do on Monday
I would not move a production agent onto this on the strength of an announcement, but I would spend an afternoon proving four things. The checks below are cheap and each one answers a question the documentation cannot answer for your workload.
- Build the image above in a sandbox account and confirm /whoami shows the same build_nonce_fp in two MicroVMs and a different run_nonce in each; that is the uniqueness contract working.
- Measure auto-resume on your own state: fill the workspace to realistic size, suspend, and time the first request. The guide says resume latency depends on the size of the suspended state and your resume hook, and publishes no figure.
- Set maxIdleDurationSeconds by watching a real agent run. If a single step can go quiet longer than your idle value, raise it toward 28,800 or add a keepalive request.
- Request quota increases for memory and RunMicrovm rate before launch, and note that a new account starts lower.
- Write the terminate path first. Suspended MicroVMs hold snapshot storage and count toward the memory quota, so a session ledger and a sweeper are not optional.
# 1. Uniqueness check: same image, two MicroVMs.
for i in 1 2; do
ID=$(aws lambda-microvms run-microvm --image-identifier "$IMAGE_ARN" \
--query microvmId --output text)
EP=$(aws lambda-microvms get-microvm --microvm-identifier "$ID" \
--query endpoint --output text)
TOK=$(aws lambda-microvms create-microvm-auth-token \
--microvm-identifier "$ID" --expiration-in-minutes 10 \
--allowed-ports '[{"port":8080}]' \
--query 'authToken."X-aws-proxy-auth"' --output text)
curl -s "https://$EP/whoami" -H "X-aws-proxy-auth: $TOK"; echo
done
# Expect: identical build_nonce_fp, different run_nonce.
# 2. Suspend, then time the first request back.
aws lambda-microvms suspend-microvm --microvm-identifier "$ID"
time curl -s "https://$EP/whoami" -H "X-aws-proxy-auth: $TOK"
# 3. Always finish by terminating.
aws lambda-microvms terminate-microvm --microvm-identifier "$ID"Two of those commands, get-microvm and the JMESPath query paths, are my reading of the API reference rather than something I ran, so run the script against a throwaway account first. If the resume time in step two is longer than your users will wait, that is your answer on Lambda MicroVMs for chat-style sessions, and it cost you an afternoon rather than a migration.
Choose the sandbox by what it does while nobody is looking. A MicroVM earns its restrictions, ARM64 only, 8 hours at most, per-VM connection and bandwidth caps, when the session has state worth keeping and code you do not trust. For everything stateless, keep the cheaper tool and use Lambda functions to orchestrate. And whichever you pick, generate secrets after the sandbox starts, never before it is frozen.
Sources and further reading
- AWS News Blog: Run isolated sandboxes with full lifecycle control, AWS Lambda introduces MicroVMs (22 June 2026)
- AWS Lambda Developer Guide: Lambda MicroVMs core concepts and lifecycle
- AWS Lambda Developer Guide: Running and using MicroVMs (idle policy, hooks, tokens)
- AWS Lambda Developer Guide: Working with MicroVM snapshots and uniqueness
- AWS Lambda Developer Guide: MicroVM images, sizing and base image lifecycle
- AWS Lambda Developer Guide: MicroVM networking and bandwidth
- AWS Lambda Developer Guide: Lambda quotas, including Lambda MicroVMs
- Amazon ECS Developer Guide: Fargate task ephemeral storage
- AWS Open Source Blog: Firecracker, secure and fast microVMs for serverless computing
- Amazon ECS Developer Guide: Fargate security best practices

