I tried out Step Functions SDK integration for Lambda MicroVMs
This page has been translated by machine translation. View original
Introduction
On September 15, 2026, AWS announced that AWS Step Functions would automatically add AWS SDK integrations for new AWS services and features within weeks of their release.
This announcement introduces new AWS services such as AWS Lambda MicroVMs and AWS Lambda Core.
I tried working with Lambda MicroVMs, released in June, through Step Functions SDK integrations.
What We're Building in This Article
I created a mechanism that starts a website requiring token authentication, accepts access for only 3 minutes, and then automatically stops.
Overall Flow
What you need to prepare in advance: one MicroVM Image, two IAM roles (an execution role for MicroVM and an execution role for the state machine), and one S3 bucket to store image materials. In addition to these, you create one state machine.
Each time the state machine runs, it starts the MicroVM, issues an authentication token, waits 3 minutes, and stops the MicroVM. These four processes are configured with SDK integration Tasks and Wait states. Step Functions controls the MicroVM's lifetime, retrieval of the access URL, and token issuance, but does not receive the MicroVM's processing results.
Preparing the App and MicroVM Image
Verification Environment
- Region:
ap-northeast-1 - AWS CLI:
aws-cli/2.36.46 - MicroVM Image base image:
al2023-1(version1)
For the site, I use an HTTP server that returns the current time for each request and outputs access logs to standard output. The changing current time confirms that responses are actually being generated on the MicroVM.
app.py
import datetime
import os
import socket
from http.server import BaseHTTPRequestHandler, HTTPServer
LISTEN_PORT = 8080
BOOT_AT = datetime.datetime.now(datetime.timezone.utc)
def log(line):
now = datetime.datetime.now(datetime.timezone.utc).isoformat()
print(f"[{now}] {line}", flush=True)
class TimeHandler(BaseHTTPRequestHandler):
server_version = "microvm-clock/1.0"
def do_GET(self):
now = datetime.datetime.now(datetime.timezone.utc)
body = (
"MicroVM clock\n"
f"now_utc: {now.isoformat()}\n"
f"epoch: {now.timestamp():.3f}\n"
f"booted_utc: {BOOT_AT.isoformat()}\n"
f"uptime_seconds: {(now - BOOT_AT).total_seconds():.1f}\n"
f"hostname: {socket.gethostname()}\n"
).encode()
self.send_response(200)
self.send_header("Content-Type", "text/plain; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
log(
f"ACCESS port={self.server.server_address[1]} "
f"client={self.client_address[0]} "
f'"{self.requestline}" 200 {len(body)} '
f'ua="{self.headers.get("User-Agent", "-")}" '
f'host="{self.headers.get("Host", "-")}" '
f'xff="{self.headers.get("X-Forwarded-For", "-")}"'
)
def log_message(self, fmt, *args):
pass # Access logs are structured and output on the do_GET side
def serve(port):
try:
httpd = HTTPServer(("0.0.0.0", port), TimeHandler)
except Exception as e:
log(f"LISTEN_FAILED port={port} error={e}")
return
log(f"LISTENING port={port}")
httpd.serve_forever()
if __name__ == "__main__":
log(f"BOOT env_port={os.environ.get('PORT', '-')}")
serve(LISTEN_PORT)
Since the MicroVM's HTTPS endpoint routes to port 8080 inside the MicroVM by default, the app also listens on 8080.
Dockerfile
FROM public.ecr.aws/amazonlinux/amazonlinux:2023
RUN dnf install -y python3 && dnf clean all
COPY app.py /app/app.py
WORKDIR /app
EXPOSE 8080
CMD ["python3", "-u", "/app/app.py"]
Place Dockerfile and app.py in the same directory, bundle them into a zip file, and upload to S3.
zip -j image.zip Dockerfile app.py && aws s3 cp image.zip s3://<BUCKET>/
Create the image by specifying that zip file.
aws lambda-microvms create-microvm-image \
--name microvm-3min-site \
--base-image-arn arn:aws:lambda:ap-northeast-1:aws:microvm-image:al2023-1 \
--base-image-version 1 \
--build-role-arn arn:aws:iam::<ACCOUNT_ID>:role/microvm-3min-site-execution \
--code-artifact uri=s3://<BUCKET>/image.zip \
--egress-network-connectors arn:aws:lambda:ap-northeast-1:aws:network-connector:aws-network-connector:INTERNET_EGRESS \
--logging '{"cloudWatch":{"logGroup":"/aws/lambda-microvms/microvm-3min-site","logStream":"build"}}' \
--resources '[{"minimumMemoryInMiB":2048}]'
IAM Roles
Prepare one execution role for MicroVM and one execution role for Step Functions. The trust policies and permissions are as follows.
Full IAM Roles and Policies
Role 1: microvm-3min-site-execution (MicroVM execution role)
Trust Policy
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "lambda.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}
Inline Policy
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
"Resource": "arn:aws:logs:ap-northeast-1:<ACCOUNT_ID>:log-group:/aws/lambda-microvms/*"
},
{
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::<BUCKET>/*"
}
]
}
Role 2: microvm-3min-site-sfn (State machine execution role)
Trust Policy
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "states.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}
Inline Policy
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"lambda:RunMicrovm",
"lambda:TerminateMicrovm",
"lambda:CreateMicrovmAuthToken"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::<ACCOUNT_ID>:role/microvm-3min-site-execution"
}
]
}
State Machine
JSONata is adopted as the query language for the state machine. RunMicrovm, CreateMicrovmAuthToken, and TerminateMicrovm are called via SDK integrations, and JSONata is used to extract values such as MicroVM ID and endpoint from each response. The definition is saved as sfn-3min-site.asl.json.
Key Points for JSONata and SDK Integration
The resource ARN for SDK integration takes the form arn:aws:states:::aws-sdk:<service-name-lowercase>:<apiAction>. In JSONata mode, Arguments / Assign / Output are used.
The MicroVM ID and endpoint are extracted from the RunMicrovm response and reused in subsequent states.
"Assign": {
"microvmId": "{% $states.result.MicrovmId %}",
"endpoint": "{% $states.result.Endpoint %}"
}
At the time of the RunMicrovm response, JSONata calculates the stopAt timestamp 3 minutes later, which is used for both Wait and the authentication token expiration.
"Assign": {
"stopAt": "{% $fromMillis($toMillis($now()) + 180000) %}"
}
"Timestamp": "{% $stopAt %}"
Since the authentication token key X-aws-proxy-auth contains hyphens, it is referenced by enclosing it in backticks in JSONata.
"Assign": {
"authToken": "{% $states.result.AuthToken.`X-aws-proxy-auth` %}"
}
By including the execution name in the log stream name, MicroVM logs are separated per execution.
"LogStream": "{% 'sfn-' & $states.context.Execution.Name %}"
sfn-3min-site.asl.json (full content)
{
"Comment": "Start MicroVM, publish for 3 minutes, and stop after 3 minutes (JSONata / SDK integration only)",
"QueryLanguage": "JSONata",
"StartAt": "RunMicrovm",
"States": {
"RunMicrovm": {
"Type": "Task",
"Resource": "arn:aws:states:::aws-sdk:lambdamicrovms:runMicrovm",
"Arguments": {
"ImageIdentifier": "{% $states.input.imageArn %}",
"ImageVersion": "{% $states.input.imageVersion %}",
"ExecutionRoleArn": "{% $states.input.executionRoleArn %}",
"MaximumDurationInSeconds": 900,
"Logging": {
"CloudWatch": {
"LogGroup": "/aws/lambda-microvms/microvm-3min-site",
"LogStream": "{% 'sfn-' & $states.context.Execution.Name %}"
}
}
},
"Assign": {
"microvmId": "{% $states.result.MicrovmId %}",
"endpoint": "{% $states.result.Endpoint %}",
"stopAt": "{% $fromMillis($toMillis($now()) + 180000) %}"
},
"Next": "CreateAuthToken"
},
"CreateAuthToken": {
"Type": "Task",
"Resource": "arn:aws:states:::aws-sdk:lambdamicrovms:createMicrovmAuthToken",
"Arguments": {
"MicrovmIdentifier": "{% $microvmId %}",
"ExpirationInMinutes": 3,
"AllowedPorts": [
{
"Port": 8080
}
]
},
"Assign": {
"siteUrl": "{% 'https://' & $endpoint %}",
"authToken": "{% $states.result.AuthToken.`X-aws-proxy-auth` %}"
},
"Next": "ServeFor3Minutes"
},
"ServeFor3Minutes": {
"Type": "Wait",
"Timestamp": "{% $stopAt %}",
"Next": "TerminateMicrovm"
},
"TerminateMicrovm": {
"Type": "Task",
"Resource": "arn:aws:states:::aws-sdk:lambdamicrovms:terminateMicrovm",
"Arguments": {
"MicrovmIdentifier": "{% $microvmId %}"
},
"Next": "Done"
},
"Done": {
"Type": "Pass",
"Output": {
"microvmId": "{% $microvmId %}",
"url": "{% $siteUrl %}",
"servedUntil": "{% $stopAt %}"
},
"End": true
}
}
}
aws stepfunctions create-state-machine --name microvm-3min-site --definition file://sfn-3min-site.asl.json --role-arn <SFN_ROLE_ARN> --type STANDARD
--type STANDARD is specified. This is to check the authentication token from the execution history using get-execution-history described later.
Running and Accessing for 3 Minutes
Execute using the image ARN, version, and MicroVM execution role as input.
aws stepfunctions start-execution \
--state-machine-arn arn:aws:states:ap-northeast-1:<ACCOUNT_ID>:stateMachine:microvm-3min-site \
--input '{"imageArn":"arn:aws:lambda:ap-northeast-1:<ACCOUNT_ID>:microvm-image:microvm-3min-site","imageVersion":"1.0","executionRoleArn":"arn:aws:iam::<ACCOUNT_ID>:role/microvm-3min-site-execution"}'
The authentication token is included in the output of the success event (TaskSucceeded) of the CreateAuthToken state that calls CreateMicrovmAuthToken. Retrieve this event from the execution history.
aws stepfunctions get-execution-history --execution-arn <EXECUTION_ARN> \
--query 'events[?type==`TaskSucceeded`].taskSucceededEventDetails.output'
Here are the results after execution completed. Only the key fields are excerpted.
aws stepfunctions describe-execution --execution-arn <EXECUTION_ARN>
{
"status": "SUCCEEDED",
"startDate": "2026-09-17T20:22:33.344000+09:00",
"stopDate": "2026-09-17T20:25:34.323000+09:00",
"output": "{\"microvmId\":\"microvm-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX\",\"url\":\"https://xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.lambda-microvm.ap-northeast-1.on.aws\",\"servedUntil\":\"2026-09-17T11:25:33.728Z\"}"
}
Instructed to stop 180 seconds after the RunMicrovm response, the execution completed in 181 seconds including the completion of the stop.
Let's also check the final state on the MicroVM side. Here too, only the key fields are shown.
aws lambda-microvms get-microvm --microvm-identifier <MICROVM_ID>
{
"state": "TERMINATED",
"startedAt": "2026-09-17T20:22:33.655000+09:00",
"terminatedAt": "2026-09-17T20:25:34.751000+09:00",
"stateReason": "Success.",
"maximumDurationInSeconds": 900,
"ingressNetworkConnectors": [
"arn:aws:lambda:ap-northeast-1:aws:network-connector:aws-network-connector:HTTP_INGRESS"
]
}
The State in the RunMicrovm response was PENDING. It took about 4 seconds from the start of execution until the first curl returned 200.
Here are the curl results sent from my local machine.
| # | Time (UTC) | Condition | HTTP | From Response |
|---|---|---|---|---|
| 1 | 11:22:37 | No token | 403 | Request missing authentication |
| 2 | 11:22:37 | With token | 200 | now_utc: 2026-09-17T11:22:37.482160+00:00 |
| 3 | 11:22:47 | With token | 200 | now_utc: 2026-09-17T11:22:47.651081+00:00 |
| 4 | 11:22:57 | With token | 200 | now_utc: 2026-09-17T11:22:57.708069+00:00 |
| 5 | 11:25:35 (response at 11:25:38) | With token | 502 | No body |
Teardown
First terminate any MicroVMs remaining from failed executions, then delete the created resources.
Teardown Commands
aws lambda-microvms list-microvms
aws lambda-microvms terminate-microvm --microvm-identifier <MICROVM_ID>
aws stepfunctions delete-state-machine --state-machine-arn <STATE_MACHINE_ARN>
aws lambda-microvms delete-microvm-image --image-identifier <IMAGE_ARN>
aws iam delete-role-policy --role-name <ROLE> --policy-name <NAME>
aws iam delete-role --role-name <ROLE>
aws s3 rm s3://<BUCKET>/image.zip
aws s3api delete-bucket --bucket <BUCKET>
aws logs delete-log-group --log-group-name /aws/lambda-microvms/microvm-3min-site
Summary
Using only Step Functions SDK integrations and JSONata, I was able to write the MicroVM startup, retrieval of the access URL and token issuance, and shutdown after a set period of time — all without Lambda functions.
Standard Workflow execution time can be up to 1 year, and Lambda MicroVMs can run for up to 8 hours. In a configuration like this one that issues an authentication token only once, the maximum accessible time is 60 minutes, but it seems applicable for use cases that clearly divide usage time, such as short-term demo sites.
It took about 3 months for Lambda MicroVMs SDK integration, but going forward, new services are expected to be integrated within weeks of release. I look forward to actively leveraging Step Functions SDK integrations when working with new services.



