AWS Lambda MicroVMs: measured platform behavior
A compact record of runtime observations. Unless stated otherwise, measurements
used us-east-1, MicroVM API 2025-09-09, and an ARM64 al2023-minimal guest.
Dates apply to observations, not guarantees about current service behavior.
Original experiments and full narratives remain in git history.
For current operation shapes, see the AWS MicroVM documentation. For internet isolation, use Networking: a VPC without an IGW or NAT gateway is required; connector omission and proxy variables do not seal the default network. The September 2026 correction below distinguishes the separate Lambda core API from the MicroVM API.
The service provides no exec and no file transfer
Section titled “The service provides no exec and no file transfer”Lambda MicroVMs have no addressable command-execution or file-transfer API;
agentd supplies those operations. The service does offer a programmatically
usable PTY through CreateMicrovmShellAuthToken (measured 2026-08-15), which
corrects the earlier claim that its shell was console-only. A PTY does not
provide detached exec IDs, separated output streams, or exit-status records.
Hooks are served under a fixed prefix, and two of them are build-time
Section titled “Hooks are served under a fixed prefix, and two of them are build-time”Measured 2026-08-05. Hooks are
POST /aws/lambda-microvms/runtime/v1/<hook> for ready, validate, run,
resume, suspend, and terminate. ready and validate run during image
build, before token delivery, and must succeed without bootstrap. The model
allows 3600-second build-hook timeouts and 60-second runtime-hook timeouts.
runHookPayload arrives wrapped, not as the body
Section titled “runHookPayload arrives wrapped, not as the body”Measured 2026-08-05. The platform sends an outer JSON object containing the
string supplied to RunMicrovm:
{"runHookPayload": "{\"agent_token\": \"...\"}"}Decode both layers. Reading agent_token from the outer object fails the run
hook; AWS can terminate the VM before forwarding traffic. Read stateReason
from GetMicrovm when launch fails.
The runHookPayload ceiling is 4096 bytes, and the service model states it twice, differently
Section titled “The runHookPayload ceiling is 4096 bytes, and the service model states it twice, differently”Measured 2026-08-07; model rechecked 2026-09-16 with botocore 1.43.95. The inclusive limit is 4096 bytes of the serialized payload. The model’s member documentation still says 16,384, but its referenced shape says 4096. The client checks the smaller, measured limit before calling AWS.
| Payload size | Measured response with an invalid image identifier |
|---|---|
| 4096 bytes | Passed length validation, then rejected the image ARN |
| 4097 bytes | Rejected runHookPayload length |
This corrects the earlier 16 KB claim. The budget includes the token, launch environment, identity material, and JSON escaping. See the AWS RunMicrovm documentation for current shape constraints.
Calling an unpriced region returns AccessDeniedException with a null message
Section titled “Calling an unpriced region returns AccessDeniedException with a null message”Measured 2026-08-07 with ListMicrovms. us-east-1, us-east-2,
us-west-2, eu-west-1, and ap-northeast-1 succeeded. eu-central-1,
ap-southeast-2, and sa-east-1 returned AccessDeniedException with a null
message. This can resemble an IAM problem; a null message alone is not proof
of its cause.
The shared Lambda endpoint resolver can list ordinary Lambda regions that do
not support MicroVMs. SDK endpoint availability is not a MicroVM service
availability check. The package validates its known regions and permits an
explicit --unlisted-region override.
Network connectors are ARNs
Section titled “Network connectors are ARNs”Measured 2026-08-05. Managed connectors use ARNs such as
arn:aws:lambda:<region>:aws:network-connector:aws-network-connector:ALL_INGRESS.
The bare value ALL_INGRESS fails with Malformed network connector ARN.
INTERNET_EGRESS, HTTP_INGRESS, and SHELL_INGRESS use the same form.
Correction, 2026-09-16: custom VPC connectors are created by the separate
lambda-core service and attached through egressNetworkConnectors. The
old claim that omitting a managed connector disables egress was disproved in
September. Use a VPC without an IGW or NAT gateway for internet isolation;
see Networking.
CreateMicrovmAuthToken returns a header map
Section titled “CreateMicrovmAuthToken returns a header map”Measured 2026-08-05. authToken is a header map, not a string. Read
authToken["X-aws-proxy-auth"], and send X-aws-proxy-port with the target
port. Preserve the header-map contract rather than assuming the response can
never contain another header.
MicroVM states, and terminal states reached before RUNNING
Section titled “MicroVM states, and terminal states reached before RUNNING”Typical progression is PENDING → RUNNING → SUSPENDING → SUSPENDED, with
resume and termination transitions. Poll for the desired state and stop on
terminal states, reporting stateReason. A VM that terminates before
RUNNING failed startup; continuing to poll hides the useful error. Use the
AWS GetMicrovm documentation for the complete state enum.
The build log group survives Terraform
Section titled “The build log group survives Terraform”Measured 2026-08-05. AWS creates /aws/lambda-microvms/<image-name> outside
the Terraform stack. terraform destroy does not remove it. Verify log
groups separately after VM/image cleanup.
Root in the guest is not enough: sethostname and bind mounts need additionalOsCapabilities
Section titled “Root in the guest is not enough: sethostname and bind mounts need additionalOsCapabilities”Measured 2026-08-06 with al2023-1. Without additionalOsCapabilities,
writing /etc/machine-id succeeded while sethostname and the bind mount over
/proc/sys/kernel/random/boot_id returned EPERM. Requesting ["ALL"] made
all three succeed in that run; ALL is the model’s only capability value.
Later evidence, 2026-09-12: the daemon and child capability bounding sets
lacked CAP_SYS_ADMIN and CAP_NET_ADMIN, including with identity repair
requested. Do not treat the earlier success or ["ALL"] as a portable
privilege guarantee. Inspect identity_degraded on health and the current
capability mask. The metadata section below records the later measurements.
minimumMemoryInMiB selects a baseline, and the guest reports the peak
Section titled “minimumMemoryInMiB selects a baseline, and the guest reports the peak”Measured 2026-08-07 with al2023-1. A 512 MiB baseline produced
MemTotal: 2037648 kB; 2048 MiB produced 8209056 kB. These match the
documented size classes:
| Baseline memory / vCPU | Provisioned ceiling memory / vCPU |
|---|---|
| 0.5 GiB / 0.25 | 2 GiB / 1 |
| 1 GiB / 0.5 | 4 GiB / 2 |
| 2 GiB / 1 | 8 GiB / 4 |
| 4 GiB / 2 | 16 GiB / 8 |
| 8 GiB / 4 | 32 GiB / 16 |
The service team confirmed in August 2026 that the ceiling is provisioned at launch; there is no resize event. AWS documents billing at the requested baseline while running, plus consumption above it. This corrects the earlier inference that the reported peak was the billing floor. Memory-pressure tests must use the guest’s ceiling, not the requested baseline. Swap was absent.
What actually costs money
Section titled “What actually costs money”Queried 2026-08-07 from AWS Pricing in us-east-1, ServiceCode="AWSLambda".
These are dated USD rates, not a current invoice:
| Usage | Rate |
|---|---|
| ARM vCPU-second | 0.0000276944 |
| ARM memory GiB-second | 0.0000036667 |
| Snapshot read GiB | 0.0015467699 |
| Snapshot write GiB | 0.0037977138 |
| Snapshot storage GiB-hour | 0.0001111111 |
The API also returned non-ARM compute rates; MicroVMs support ARM_64 only.
Rates existed in five regions. us-east-2/us-west-2 matched us-east-1; eu-west-1
and ap-northeast-1 were higher. Regional usage types have prefixes that must
be removed before comparing the same dimension.
Image storage has a one-week minimum. Running idle VMs still incur baseline
charges; suspended VMs incur snapshot storage and transitions incur reads and
writes. Data transfer is separate. Server-side build compute billing remains
unverified and is reported as unpriced, not zero. The old $0.08/GiB-month
storage estimate was rounded low; the API rate gives $0.081111103 at 730 hours.
Use microvm cost and scripts/check-live-rates.py rather than copying rates
from this record.
Seeing an OOM: the process case works, the VM case is still unmeasured
Section titled “Seeing an OOM: the process case works, the VM case is still unmeasured”Measured 2026-08-07. dmesg was readable and
/sys/fs/cgroup/memory.events exposed oom, oom_kill, and oom_group_kill,
all zero in the tested VM. No actual OOM was induced: the first probe required
an absent Python interpreter; a second hit the /dev/shm limit instead of
RAM pressure. A guest-wide OOM’s stateReason therefore remains unmeasured.
The daemon remained reachable while processing 64 MiB of output and reported
truncated: true. Unit tests cover signal reporting, but do not establish
what AWS reports after a guest-wide OOM.
Suspend/resume is a freeze and restore, not a stop and start
Section titled “Suspend/resume is a freeze and restore, not a stop and start”Measured 2026-08-05, al2023-1, 1024 MiB baseline, held suspended for 45
seconds. Token, files, exec records, unread output, background process, and
endpoint URL all survived. A one-second ticker had a 51-second gap, then
advanced six times in six seconds after resume.
Resume continues frozen memory and processes; it does not require bootstrap again. Wall-clock leases and credentials can expire during suspension. These observations corrected the earlier claim that an in-memory token was lost.
Traffic ordering around the /run hook
Section titled “Traffic ordering around the /run hook”AWS documents that external traffic is forwarded only after /run returns
HTTP 200. This permits launch-time secret delivery without baking secrets into
a shared image. It does not protect bootstrap from a process already running
inside the guest. See Trust.
The platform’s own hook arrives over loopback
Section titled “The platform’s own hook arrives over loopback”Measured 2026-08-04. Lifecycle hooks and proxied control requests both
arrived from 127.0.0.1 on ephemeral ports. A loopback-address filter cannot
distinguish AWS from a guest process, and rejecting loopback rejects legitimate
bootstrap. Use the one-shot bootstrap contract and prevent workloads from
starting before it completes.
Something probes the port with TLS before bootstrap
Section titled “Something probes the port with TLS before bootstrap”Measured 2026-08-04. TLS ClientHello bytes reached the daemon’s plaintext port before bootstrap and produced HTTP 400. The source component was not identified. Reject malformed traffic without terminating the listener.
Endpoint authentication
Section titled “Endpoint authentication”AWS documents proxy JWEs scoped to a VM, allowed ports, and an expiry of at most 60 minutes. Clients must mint fresh credentials for later requests. Measured 2026-08-15 against a listener on port 8080:
allowedPorts |
HTTPS request to 8080 |
|---|---|
[{"port":9000}] |
403, Access to port denied |
[{"port":9000},{"port":8080}] |
200 |
[{"allPorts":{}}] |
200 |
[{"range":{"startPort":8000,"endPort":9100}}] |
200 |
These are tagged-union wire forms, with one member per item. A permitted port with no listener returned 502. WebSocket failures instead appeared as close code 1006 without a reason; use an authenticated HTTPS request to diagnose port scope versus an unavailable listener.
clientToken is a permanent idempotency key
Section titled “clientToken is a permanent idempotency key”Measured 2026-08-02. Reusing a content-derived create token after deleting
an image replayed the original creation rather than scheduling new builds.
Two images remained CREATING for roughly 15 hours, with builds stuck
PENDING and unchanged timestamps.
Use a fresh token for a new logical build, retaining it only for retries of
that attempt. Detect stalled builds with ListMicrovmImageBuilds after a
grace period. This observation concerns MicroVM image creation; it is not a
claim about every AWS service’s idempotency lifetime.
Build logs go to /aws/lambda-microvms/<image-name>
Section titled “Build logs go to /aws/lambda-microvms/<image-name>”Measured 2026-08-05. The log prefix is /aws/lambda-microvms/, not
/aws/lambda/microvms/. Build roles need CloudWatch permissions on the correct
group and ECR access for private source images. Incorrect logging permissions
can hide the underlying container error.
An image build is three VMs and three log streams, and logStream is an exact name
Section titled “An image build is three VMs and three log streams, and logStream is an exact name”Measured August 2026. A build used a docker-build VM and snapshot VMs for Graviton 3 and 4; application startup logs came from the snapshot VMs. Default logging used a per-image group and separate randomly named streams.
The API’s configured logStream is an exact name, so all build phases write
to that stream. This client adds /<16 hex> to a configured prefix for each
create attempt and returns the resolved name. User prefixes are capped at
495 characters to fit the 512-character shape; : and * are forbidden.
The configured group must be writable by the build role.
A failed build’s stateReason lives on the build, not on the version or the image
Section titled “A failed build’s stateReason lives on the build, not on the version or the image”Measured 2026-08-15 across three failed builds. GetMicrovmImage had no
reason field; version summaries returned stateReason: null; build summaries
from ListMicrovmImageBuilds contained the reason. Follow
latestFailedImageVersion, list its builds, and inspect every failed build.
Observed reasons included The container image build failed. and
Ready hook invocation timed out after PT5M. CloudWatch logs provide the
container-level detail; a summary reason does not replace them.
idlePolicy
Section titled “idlePolicy”AWS documents idleness as inbound endpoint traffic, not guest CPU activity.
Set maxIdleDurationSeconds, suspendedDurationSeconds, and
autoResumeEnabled deliberately. The suspended timeout can terminate a VM
before a later manual resume; the maximum VM duration also applies.
Measured 2026-08-15: GetMicrovm echoed all three policy fields unchanged in
RUNNING and SUSPENDED. The older claim that the suspended timeout existed
only in requests was wrong. External health polls kept a VM running; guest
loopback requests do not traverse the endpoint’s idle accounting.
Pagination cursors are URL-safe base64, and the padding still has to be encoded
Section titled “Pagination cursors are URL-safe base64, and the padding still has to be encoded”Measured 2026-08-15 over 28 cursors. Tokens were 688–800 bytes of URL-safe
base64, including = padding in six samples. An encoded %3D request
succeeded; the otherwise identical raw = request returned HTTP 400 with a
null message. Treat cursors as opaque and percent-encode query values before
signing. The absence of + or / does not make encoding optional.
maxResults is applied before nameFilter, so a page can be empty while matches remain
Section titled “maxResults is applied before nameFilter, so a page can be empty while matches remain”Measured 2026-08-15. With 22 images, ten matching nameFilter=bonk, and
maxResults=1, the first page was empty and the complete listing took 26
pages. Follow nextToken until absent even when a page contains no items.
nameFilter is a substring filter; exact-name lookup must compare names
across the complete listing.
A second CreateMicrovmImage under an existing name is refused, so a client without UpdateMicrovmImage cannot make a second version
Section titled “A second CreateMicrovmImage under an existing name is refused, so a client without UpdateMicrovmImage cannot make a second version”Measured 2026-08-15. Creating an existing image name returned HTTP 400,
ValidationException: A MicroVM image with the name '<name>' already exists in this account. Use UpdateMicrovmImage to create another version. Its
PUT request requires codeArtifact, baseImageArn, and buildRoleArn.
A client limited to create calls cannot produce a multi-version image.
The image ARN separator is a colon, and the slash form fails as AccessDeniedException
Section titled “The image ARN separator is a colon, and the slash form fails as AccessDeniedException”Measured 2026-08-15. Customer image ARNs use
arn:aws:lambda:<region>:<account>:microvm-image:<name>. The colon form
returned 200; an encoded slash form returned 403 AccessDeniedException.
An unencoded slash created extra path segments and returned HTML 404.
One transient gateway 502 with an HTML body preceded consistent 403 responses. Do not interpret a gateway error as service validation, or widen IAM solely because a malformed ARN produced an authorization error.
Most public ARM64 base images have no WORKDIR
Section titled “Most public ARM64 base images have no WORKDIR”Measured 2026-08-05. The inspected al2023-minimal, python:3.12-slim,
and node:20-slim images left WorkingDir empty. Set WORKDIR explicitly
and ensure the workload user can write there.
A WebSocket reaches a guest server through the endpoint, and the proxy strips its own subprotocols
Section titled “A WebSocket reaches a guest server through the endpoint, and the proxy strips its own subprotocols”Measured in two independent runs on 2026-08-15. Both reached a guest echo server with this offered subprotocol list:
lambda-microvmslambda-microvms.authentication.<jwe>lambda-microvms.port.<port>One run obtained credentials through the built Node binding’s
Session.connect_subprotocols and connect_headers, verifying the helper as
well as the protocol. Text frames round-tripped in order. An 899-byte auth
subprotocol remained token-legal without escaping.
The proxy consumed its three subprotocols; none reached the guest. A fourth
application subprotocol did reach it and could be negotiated. When the guest
selected none, the client still observed lambda-microvms, supplied by the
proxy. Client-visible ws.protocol alone is therefore not evidence of guest
negotiation. HTTPS similarly stripped the proxy auth and port headers.
Missing credentials, missing marker, and wrong-port tokens all produced opaque 1006 closes. Diagnose using the HTTPS status as described under endpoint authentication.
Binary frames survive a port-scoped WebSocket, and an upgrade cannot be replayed over HTTPS
Section titled “Binary frames survive a port-scoped WebSocket, and an upgrade cannot be replayed over HTTPS”Measured 2026-08-29 against a guest echo server on 8090. A real port-scoped
wss:// connection returned 101 and preserved binary frames byte-for-byte,
including 00 FF FE 80 7F 00 and a 300-byte extended-length frame.
Forwarding an upgrade through an ordinary HTTPS request returned 400 and
never reached the guest; a tunnel must perform a real WebSocket handshake.
For /v1/tcp?port=5432, scope the proxy token to the daemon’s listening port
(default 9000): the daemon makes the onward connection inside the guest.
Scoping the token to 5432 instead produced the same opaque 1006 failure.
The guest kernel is 6.1, which openat2 needs
Section titled “The guest kernel is 6.1, which openat2 needs”Measured 2026-08-14 with al2023-1: kernel
6.1.166-24.303.amzn2023.aarch64. Tar extraction uses openat2 with
RESOLVE_BENEATH | RESOLVE_NO_SYMLINKS, available since Linux 5.6. Unsupported
kernels fail extraction rather than silently weakening confinement.
A WebSocket reaches a guest server through the endpoint, and the proxy strips its own subprotocols
Section titled “A WebSocket reaches a guest server through the endpoint, and the proxy strips its own subprotocols”The independent 2026-08-15 run confirmed the same handshake, stripping, application negotiation, and opaque failure behavior. Its observations are consolidated in the earlier WebSocket section; this heading remains for existing links.
An outside poll of /v1/health does reset the idle timer, and the control half proves it
Section titled “An outside poll of /v1/health does reset the idle timer, and the control half proves it”Measured 2026-08-15 with two VMs running detached sleep 300, a 60-second
idle timeout, and a 900-second suspended timeout. The VM polled through its
endpoint every roughly 20 seconds remained RUNNING through 311 seconds.
The unpolled control was SUSPENDED at 66 seconds and remained so.
External polling keeps a busy VM alive; guest work alone does not. Poll health at an interval below the idle timeout when preserving an active exec is the caller’s intention. A local ledger watcher does not have this effect.
The 4096-byte runHookPayload ceiling is on the whole string, env map included
Section titled “The 4096-byte runHookPayload ceiling is on the whole string, env map included”Measured 2026-08-15 using a payload containing both agent_token and an
env map. Exactly 4096 serialized bytes passed length validation; 4097 failed
before image resolution, matching the token-only measurement. Validate the
whole serialized string after combining fields.
The shell endpoint is a real PTY over a WebSocket, and it is programmatically drivable
Section titled “The shell endpoint is a real PTY over a WebSocket, and it is programmatically drivable”Measured 2026-08-15. A programmable shell requires HTTP_INGRESS plus
SHELL_INGRESS. ALL_INGRESS alone cannot mint shell credentials. Combining
ALL_INGRESS with SHELL_INGRESS launched a VM but failed later at token
creation, so validate that combination before launch.
CreateMicrovmShellAuthToken returns a proxy header map and has no
allowedPorts parameter. Connect to the VM’s WebSocket endpoint with the
marker and authentication subprotocols; no port subprotocol is required.
Ordinary HTTPS with the shell token returned 502.
| Message | Meaning |
|---|---|
Initial text {"type":"session_init","session_id":"<uuid>"} |
Session identifier |
| Binary frames | Raw terminal input/output |
Text {"type":"resize","cols":120,"rows":40} |
Resize; stty size reported 40 120 |
Close 1000, shell exited |
Shell finished |
The session was a root PTY with job control; Ctrl-C produced status 130.
Unknown control messages became literal shell input rather than errors.
There is no structured per-command exit-status channel. This corrects the
original claim that the shell could not be driven programmatically; the
package now exposes it through microvm shell.
Tagging works on images and not on MicroVMs, and RunMicrovm takes no tags
Section titled “Tagging works on images and not on MicroVMs, and RunMicrovm takes no tags”Measured 2026-08-15. Image tags could be created, accumulated, listed,
and removed. GetMicrovmImage echoed them. Attempts to tag a running
MicroVM by ARN or bare ID failed. RunMicrovm has no tags field in the
2025-09-09 model, still true in the 2026-09-16 SDK refresh. Do not assume
image tags provide per-instance compute attribution.
Build introspection returns snapshot sizes and a chipset generation, not logs
Section titled “Build introspection returns snapshot sizes and a chipset generation, not logs”Measured 2026-08-15. ListMicrovmImageBuilds requires an image identifier
and version and returned two builds, for Graviton generations 3 and 4.
GetMicrovmImageBuild added snapshotBuild sizes: 579080192 memory bytes,
2357084160 code-install bytes, and 24297472 disk-snapshot bytes for the tested
image. It did not return logs or stateReason; use build summaries and
CloudWatch for failure details.
GetMicrovmImageVersion echoes build configuration, including resources,
hooks, connectors, and base version. state (build outcome) and status
(launch eligibility) are separate. Use snapshot dimensions for estimates and
preserve absent dimensions as unknown rather than zero.
The managed base image has two versions, and its versions are bare integers
Section titled “The managed base image has two versions, and its versions are bare integers”Measured 2026-08-15. The managed-image listing returned al2023-1, with
versions "0" and "1". The version readback of a derived image normalized
its base version to "1.0". This is a dated listing, not a claim that AWS
will always offer one image or two versions. Discover current versions and
retain their service-provided strings rather than treating every version as
an integer or a common semantic-version format.
Every field GetMicrovm returns for a running VM
Section titled “Every field GetMicrovm returns for a running VM”Measured 2026-08-15. A healthy response contained microvmId, state,
endpoint, imageArn, imageVersion, executionRoleArn, idlePolicy,
maximumDurationInSeconds, startedAt, ingressNetworkConnectors, and
egressNetworkConnectors.
runHookPayload was not echoed. stateReason was absent on the healthy VM.
Memory sizing belongs to the image version, not the instance response. The
idle policy echoed autoResumeEnabled; the package exposes it through
--auto-resume. See AWS GetMicrovm for
the complete current response shape, including optional fields.
A detached exec survives the 60-minute proxy-token ceiling
Section titled “A detached exec survives the 60-minute proxy-token ceiling”Measured 2026-08-15. A 75-minute detached exec produced all 450 expected ten-second ticks and exited zero without truncation. The tick gap across the 60-minute credential boundary was ten seconds. Fresh client processes minted new proxy credentials; the guest exec record survived independently.
The VM had a 30-minute idle window and external traffic every eight minutes. Without that traffic, idle suspension would remain possible. Polling a running exec returned its phase without partial stdout; use streaming or a file for progress. Recorded compute plus snapshot-read estimate: about $0.16.
INACTIVE is a real retire: RunMicrovm refuses the version, pinned or not
Section titled “INACTIVE is a real retire: RunMicrovm refuses the version, pinned or not”Measured 2026-08-16. After setting the sole image version to INACTIVE,
both pinned and unpinned launches returned 404 No active version found.
Readback still showed state: SUCCESSFUL, status: INACTIVE. Restoring
ACTIVE allowed launch. UpdateMicrovmImageVersion changes eligibility
without deleting the version. Effects on already-running VMs were not measured.
A launch with no executionRoleArn succeeds, so there is no free RunMicrovm probe
Section titled “A launch with no executionRoleArn succeeds, so there is no free RunMicrovm probe”Measured 2026-08-16. Omitting executionRoleArn created a real VM;
the field is optional in the service model. Missing a required-looking field
is not a safe dry run. Invalid image identifiers can bracket earlier
validation, but every probe must account for the possibility of creating a
billable resource.
GetMicrovmImageBuild’s snapshotBuild is absent on a container-build failure and partial on a hook timeout
Section titled “GetMicrovmImageBuild’s snapshotBuild is absent on a container-build failure and partial on a hook timeout”Measured 2026-08-16 across successful and deliberately failed images. Preserve this optional structure and its optional fields:
| Outcome | snapshotBuild |
|---|---|
| Successful | Memory, code-install, and disk-snapshot sizes present |
| Ready-hook timeout | Only codeInstallSizeInBytes present |
| Container build failure | Entire member absent |
Filling missing values with zero erases the distinction between an unbuilt image and installed code whose daemon never became ready. Both tested failures appeared in builds for Graviton 3 and 4.
baseImageVersion is accepted, validated, and normalised on the way back
Section titled “baseImageVersion is accepted, validated, and normalised on the way back”Measured 2026-08-16. baseImageVersion: "999" for al2023-1 failed with
HTTP 400 before creating an image. A build pinned to "1" read back as
"1.0". Discover valid request versions through the managed-version listing;
do not compare echoed strings literally with listing strings.
An unpinned build also reports a base version, so readback alone does not prove the caller pinned one. Retain the original request for reproducibility.
A guest listening on the wrong port fails the build with a clean build log
Section titled “A guest listening on the wrong port fails the build with a clean build log”Measured 2026-08-16, al2023-1, 8192 MiB baseline. A mismatch between
AGENTD_PORT and hooks.port produced CREATE_FAILED despite successful
Docker layers and clean startup logs: AWS called hooks on the wrong port.
The same happens when the daemon defaults to 9000 but the client selects
another port. Compare version readback with the Dockerfile; the client now
rejects known mismatches before building.
A baked environment layer removes the guest’s env init, measured with build --project
Section titled “A baked environment layer removes the guest’s env init, measured with build --project”Measured 2026-09-02, al2023-1, 1024 MiB baseline, a Python project with
one attrs dependency:
| Operation | Time |
|---|---|
First build --project --reuse |
126.6 s |
| Same dependency files, reused image | 0.48 s |
| Lockfile-only edit, new build | 125.1 s |
| Fresh VM importing from baked environment | 11.13–12.44 s running |
| Plain VM installing dependencies then importing | 31.85 s running |
The lockfile edit changed the image hash and installed dependency version.
This small sample saved roughly 20 seconds per launch; it is not a general
benchmark. The earlier description of a launch without --egress as having
no network was incorrect: the measurement showed dependency reuse, not
network isolation.
Exec starts with a minimal environment. In this run no PATH or HOME was
present; uv downloaded another interpreter despite one being installed. Use
the baked venv’s absolute executable path or supply the required environment.
A VM launched without the egress connector still has outbound network
Section titled “A VM launched without the egress connector still has outbound network”Measured 2026-09-11, 2026-09-12, and 2026-09-13, us-east-1, API
2025-09-09, al2023-1, baselines 512 and 1024 MiB. Launches omitting
egressNetworkConnectors reached public destinations:
| Destination | Result without managed egress connector |
|---|---|
example.com |
200 |
github.com |
200 |
sts.amazonaws.com |
302 |
pypi.org |
200 |
pypi.org with an invalid HTTPS proxy |
curl exit 7, HTTP status 000 |
The proxy-variable result is the basis for --deny-egress; it changes client
behavior without removing the network path.
Correction, 2026-09-16: the earlier model review incorrectly concluded
that no VPC control existed because it inspected only lambda-microvms.
Boto3/botocore 1.43.95 also exposes lambda-core API 2026-04-30, whose
CreateNetworkConnector accepts VPC subnets and security groups. AWS documents
attaching the active connector ARN through RunMicrovm.egressNetworkConnectors.
No internet egress requires a VPC without an IGW or NAT gateway, with no
alternative internet path. These are SDK/documentation findings; a VPC-isolated
launch was not live-measured in this refresh. See Networking.
The guest reaches the execution role’s credentials through MMDS, and no in-guest block works
Section titled “The guest reaches the execution role’s credentials through MMDS, and no in-guest block works”Measured 2026-09-11 and 2026-09-12, al2023-1, 512 MiB baseline.
169.254.169.254 served the execution role through Firecracker MMDS:
| Request | Response |
|---|---|
| Credential GET without token | 401 |
| IMDSv2 token PUT | 200 |
| Credential GET with token | 200, full temporary credential document |
| Token PUT as uid 1000 | 200 |
Both managed-egress and connector-less VMs behaved alike. Credential values were neither printed nor retained.
The daemon and child reported CapBnd 00000000a80425fb, lacking
CAP_NET_ADMIN and CAP_SYS_ADMIN, including with identity repair requested.
After installing iproute, route/rule/link changes still returned EPERM;
relevant /proc/sys writes were read-only. No tested in-guest metadata block
worked. This limits the earlier August capability observation.
Use least privilege on the execution role. VPC internet isolation does not
remove metadata credentials. The conformance role permits CloudWatch logging
and its policy is checked by drive_platform_posture.