
The AWS Emulator Has Been Released, So I Tried It Out
This page has been translated by machine translation. View original
Introduction
On November 11, 2026, AWS announced the availability of AWS Emulator, an emulator for local use.
In this article, I launched AWS Emulator v1.0.322 with Docker, switched the destination of AWS SDK and AWS CLI calls to the local AWS Emulator endpoint, and ran basic operations on S3, SQS, DynamoDB, and Lambda.
I also compared startup time, memory usage, and image size with Floci 2.4.0, an OSS local emulator.
Startup and Connection
Building the Image
There is no prebuilt Docker image, so you build it from the Dockerfile in the User Guide. I tried this on an aarch64 host, so I changed only the RPM on the ADD line to the aarch64 one.
Save the following content with the name Dockerfile.
Dockerfile (for aarch64)
FROM public.ecr.aws/amazonlinux/amazonlinux:2023-minimal
ADD https://downloads.aws.com/aws-emulator/packages/aws-emulator/LTS/latest/aws-emulator-aarch64.rpm /tmp/aws-emulator.rpm
RUN rpm -i /tmp/aws-emulator.rpm && rm /tmp/aws-emulator.rpm \
&& mkdir -p /home/emulator && chown 1000 /home/emulator
USER 1000
ENV HOME=/home/emulator
EXPOSE 9297
ENTRYPOINT ["aws-emulator", "-b", "0.0.0.0"]
This Dockerfile is for aarch64. On an x86_64 host, please use the Dockerfile in the User Guide as is.
Specify the directory containing the Dockerfile and build.
docker build -t aws-emulator:verify <Dockerfile のあるディレクトリ>
Starting the Container
The mounted directory is a dedicated one. The ~/.aws directory on the host is not used.
SHARE="$PWD/aws-emulator-share"
mkdir -p "$SHARE"
docker run -d --rm --name aws-emulator-verify --memory=2g --memory-swap=2g \
-p 127.0.0.1:9297:9297 --user "$(id -u):$(id -g)" \
-e EMULATOR_TELEMETRY=off \
-v "$SHARE:/home/emulator/.aws/emulator" \
aws-emulator:verify -p 9297
You cannot connect immediately after startup, so wait with the following loop until /__admin/health returns HTTP 200.
for i in $(seq 30); do
curl -sf --cacert "$SHARE/9297/ca.pem" https://127.0.0.1:9297/__admin/health && break
sleep 1
done
Pointing the AWS CLI
The destination is switched with environment variables. Save the following content as env-emulator.sh. This script references the variable SHARE, so load it in the shell where you set SHARE in the previous step.
env-emulator.sh
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN AWS_PROFILE
_S="$SHARE/9297"
export AWS_ENDPOINT_URL=https://127.0.0.1:9297
export AWS_SHARED_CREDENTIALS_FILE="$_S/emulator_credentials"
export AWS_CONFIG_FILE="$_S/config"
export AWS_CA_BUNDLE="$_S/ca.pem"
export AWS_EC2_METADATA_DISABLED=true
export AWS_DISABLE_HOST_PREFIX_INJECTION=true
export AWS_REGION=us-east-1
export AWS_DEFAULT_REGION=us-east-1
After loading it, confirm that calls reach the emulator.
source env-emulator.sh
aws sts get-caller-identity
{
"Account": "000000000000",
"Arn": "arn:aws:iam::000000000000:user/emulator"
}
000000000000 is the emulator's fixed account ID.
Verifying Operation
The following commands were run in the shell where the environment variables were loaded.
S3
Creating a bucket, uploading from standard input, listing, and downloading were all possible with the regular aws s3 commands. The content of data0.json is {"hello":"world"}.
aws s3 mb s3://my-bucket
cat data0.json | aws s3 cp - s3://my-bucket/data.json
aws s3 ls s3://my-bucket
aws s3 cp s3://my-bucket/data.json ./downloaded.json && cat downloaded.json
make_bucket: my-bucket
2026-11-12 10:52:34 18 data.json
{"hello":"world"}
Next, I enabled versioning. Then I overwrote the same key with data2.json, whose content is {"hello":"world","version":2}, and retrieved the version list.
aws s3api put-bucket-versioning --bucket my-bucket --versioning-configuration Status=Enabled
aws s3 cp data2.json s3://my-bucket/data.json
aws s3api list-object-versions --bucket my-bucket
{
"Size": 30,
"Key": "data.json",
"VersionId": "<version-id>",
"IsLatest": true
},
{
"Size": 18,
"Key": "data.json",
"VersionId": "null",
"IsLatest": false
}
The VersionId of the object uploaded before versioning was enabled was the string "null".
The presigned URL was issued as a path-style https URL. The credentials and signature are masked.
aws s3 presign s3://my-bucket/data.json --expires-in 3600
https://127.0.0.1:9297/my-bucket/data.json?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=<masked>&X-Amz-Date=20261112T015236Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=<masked>
When I passed the emulator's ca.pem to curl's --cacert and accessed this URL, the content of the latest version was returned with HTTP 200.
URL=$(aws s3 presign s3://my-bucket/data.json --expires-in 3600)
curl -s -w "\nhttp_code=%{http_code}\n" --cacert "$SHARE/9297/ca.pem" "$URL"
{"hello":"world","version":2}
http_code=200
SQS
When I created a standard queue, the host of the QueueUrl was sqs.us-east-1.amazonaws.com. The 000000000000 included in the URL is also the emulator's fixed account ID.
aws sqs create-queue --queue-name orders
{
"QueueUrl": "https://sqs.us-east-1.amazonaws.com/000000000000/orders"
}
Passing this URL as is to --queue-url, I was able to send and receive messages. The receive response is excerpted to show only the body and the receive count.
aws sqs send-message \
--queue-url https://sqs.us-east-1.amazonaws.com/000000000000/orders \
--message-body '{"event":"order.placed","id":"abc123"}'
aws sqs receive-message \
--queue-url https://sqs.us-east-1.amazonaws.com/000000000000/orders \
--max-number-of-messages 10 \
--attribute-names All
{
"Messages": [
{
"Body": "{\"event\":\"order.placed\",\"id\":\"abc123\"}",
"Attributes": {
"ApproximateReceiveCount": "1"
}
}
]
}
A FIFO queue could also be created, and it was included in the list.
aws sqs create-queue --queue-name orders.fifo --attributes FifoQueue=true
aws sqs list-queues
{
"QueueUrls": [
"https://sqs.us-east-1.amazonaws.com/000000000000/orders",
"https://sqs.us-east-1.amazonaws.com/000000000000/orders.fifo"
]
}
DynamoDB
I created a table and extracted only the table status (TableStatus) from the response.
aws dynamodb create-table \
--table-name Users \
--attribute-definitions AttributeName=userId,AttributeType=S \
--key-schema AttributeName=userId,KeyType=HASH \
--billing-mode PAY_PER_REQUEST \
--query TableDescription.TableStatus \
--output text
CREATING
The status was CREATING, so I waited with aws dynamodb wait table-exists until it became ACTIVE. The wait time was measured by printing the time with date before and after.
date +%H:%M:%S.%N
aws dynamodb wait table-exists --table-name Users
echo "wait_exit=$?"
date +%H:%M:%S.%N
aws dynamodb describe-table --table-name Users --query Table.TableStatus --output text
11:06:37.290630503
wait_exit=0
11:06:40.244624834
ACTIVE
The waiter returned in about 3 seconds. Next, I ran item registration, retrieval, and scan.
aws dynamodb put-item --table-name Users --item '{"userId":{"S":"u1"},"name":{"S":"Alice"},"age":{"N":"30"}}'
aws dynamodb put-item --table-name Users --item '{"userId":{"S":"u2"},"name":{"S":"Bob"},"age":{"N":"20"}}'
aws dynamodb get-item --table-name Users --key '{"userId":{"S":"u1"}}'
{
"Item": {
"name": {
"S": "Alice"
},
"userId": {
"S": "u1"
},
"age": {
"N": "30"
}
}
}
When I scanned with the condition that age is greater than 25, ScannedCount was 2 and Count was 1.
aws dynamodb scan \
--table-name Users \
--filter-expression 'age > :min' \
--expression-attribute-values '{":min":{"N":"25"}}'
"Count": 1,
"ScannedCount": 2,
Lambda
Without creating an IAM role, I specified only the role ARN and created a function with the runtime nodejs22.x. The function code, index.mjs, is shown at the end of this section.
zip function.zip index.mjs
aws lambda create-function \
--function-name my-function \
--runtime nodejs22.x \
--role arn:aws:iam::000000000000:role/lambda-role \
--handler index.handler \
--zip-file fileb://function.zip \
--query '{FunctionName:FunctionName,Runtime:Runtime,Handler:Handler,State:State}'
{
"FunctionName": "my-function",
"Runtime": "nodejs22.x",
"Handler": "index.handler",
"State": "Pending"
}
The State was Pending, so I waited with aws lambda wait function-active-v2 and, as with DynamoDB, printed the times before and after.
date +%H:%M:%S.%N
aws lambda wait function-active-v2 --function-name my-function
echo "wait_exit=$?"
date +%H:%M:%S.%N
aws lambda get-function-configuration --function-name my-function --query '{State:State,LastUpdateStatus:LastUpdateStatus}'
11:06:42.621728900
wait_exit=0
11:06:43.992666608
{
"State": "Active",
"LastUpdateStatus": "Successful"
}
The waiter returned in about 1.4 seconds.
When I invoked it synchronously, StatusCode 200 was returned, and response.json contained the function's return value.
aws lambda invoke \
--function-name my-function \
--payload '{"key":"value"}' \
--cli-binary-format raw-in-base64-out \
response.json
cat response.json
{
"StatusCode": 200,
"ExecutedVersion": "$LATEST"
}
{"statusCode":200,"body":"{\"hello\":\"world\",\"received\":{\"key\":\"value\"}}"}
index.mjs
export const handler = async (event) => {
console.log("Event:", JSON.stringify(event));
return { statusCode: 200, body: JSON.stringify({ hello: "world", received: event }) };
};
Number of Supported Services
Of the 435 services visible in aws help of AWS CLI 2.40.0, only two lack a model in AWS Emulator v1.0.322: importexport (AWS Import/Export) and sdb (Amazon SimpleDB).
Resource Consumption
Measurement Conditions
I compared resource consumption and other aspects with Floci, an OSS implementation of an AWS emulator.
For measurement, I used an aarch64 Linux workstation on which other containers were also running. The Docker version is 29.9.3. I specified --memory=2g --memory-swap=2g for both AWS Emulator and Floci, and exposed ports only on 127.0.0.1. For AWS Emulator I used the image I built myself, and for Floci I used the floci/floci:2.4.0 image.
Startup time is the time from the start of docker run until HTTP 200 is returned. I queried /__admin/health for AWS Emulator and http://127.0.0.1:4566/ for Floci. Polling was at 0.02-second intervals, and image build time is not included. This value was measured separately; it is not the time taken by the 1-second-interval loop in "Starting the Container".
Idle memory was taken about 10 seconds after startup (AWS Emulator) and about 16 seconds after startup (Floci). I also took it the same way after creating an S3 bucket. Memory is the value in the MEM USAGE column of docker stats --no-stream, and image size is the value in the DISK USAGE column of docker image ls.
docker stats --no-stream --format 'table {{.Name}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.CPUPerc}}\t{{.PIDs}}' aws-emulator-verify
docker image ls aws-emulator:verify
docker stats --no-stream --format 'table {{.Name}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.CPUPerc}}\t{{.PIDs}}' floci-verify
docker image ls floci/floci:2.4.0
Floci startup command
docker run -d --rm --name floci-verify --memory=2g --memory-swap=2g \
-p 127.0.0.1:4566:4566 \
--tmpfs /app/data \
floci/floci:2.4.0
Measurement Results
Each value is the result of a single measurement.
| Metric | AWS Emulator v1.0.322 | Floci 2.4.0 |
|---|---|---|
| Startup time | 0.257 sec | 0.431 sec |
| Idle memory | 173.9MiB | 34.12MiB |
| Memory after S3 bucket creation | 177.6MiB | 37.62MiB |
| Image size (DISK USAGE) | 1.62GB | 367MB |
Summary
I ran the ARM version of AWS Emulator in a Docker environment and confirmed that basic operations on S3, SQS, DynamoDB, and Lambda can be executed from the AWS CLI.
Local emulators for AWS services include LocalStack and the open-source project Floci. With AWS Emulator released as an official AWS tool, we can expect high fidelity, keeping up with new features, and better support.
AWS Emulator v1.0.322 offers a rich set of 433 models, but please keep in mind differences from the real services, such as IAM policies and resource-based policies, and make use of it for local development of AWS services, CI, and more.
