This article is a machine translation of the contents of the following URL, which I wrote in Japanese:
Introduction
On November 11, 2026, AWS officially released AWS Emulator. It is a tool that lets you point AWS SDK and AWS CLI calls at an emulator running locally or in CI, without an AWS account. According to AWS, for implemented APIs it returns the same result as AWS for valid calls and the same error for invalid ones, and APIs that are not implemented fail with 501. There is no additional charge.
If you are hearing about it for the first time, questions like these come up.
- Does it really behave the same as AWS?
- What works and what doesn't?
- Will my own IaC (CDK or Terraform) go through?
The third one is especially important in practice. The official documentation says that support for IaC tools varies by service.
"Fidelity for data plane APIs, and support for infrastructure as code tools such as AWS CloudFormation, Terraform, and the AWS CDK, varies by service."
Source: https://docs.aws.amazon.com/emulator/latest/userguide/fidelity.html
So in this article, with IaC compatibility as the main focus, I summarize the results of actually trying it on Windows 11 + Docker. To help readers who are new to the emulator get the overall picture, I also checked how the emulator itself behaves.
- Deploy resources with CDK in stages and tabulate how far it goes and where it stops
- Build an API with API Gateway + Lambda + DynamoDB and confirm that it works by actually calling it
- Check the emulator's behavior: errors, unimplemented APIs, authentication, running multiple instances, and the lifetime of state
- Try Terraform in the same way
- Compare the results with the fidelity data and known issues published by AWS
This article assumes basic knowledge of the AWS SDK, the AWS CLI, and CDK (or Terraform), as well as basic Docker operations.
What is AWS Emulator?
| Item | Details | Source |
|---|---|---|
| Positioning | An official emulator for trying AWS APIs locally or in CI | Official (What's New, 2026-11-11) |
| State | In memory only; lost when stopped | Official |
| Unimplemented APIs | Return HTTP 501 NotImplemented | Official |
| Pricing | No additional charge | Official |
| Production use | Not allowed | Official |
| Security testing | Not supported (IAM policies, resource-based policies, KMS key policies, and cross-account access are out of scope) | Official |
| Windows/macOS | Runs in Docker. There is no prebuilt image; you build one yourself from a Dockerfile | Official |
| Regions/accounts | Use one Region and one account. To try multiple Regions, run separate emulators | Official |
How to read the diagram: your code and tools go to different destinations depending on whether the environment variable AWS_ENDPOINT_URL is set. If it is not set, they go to real AWS. If you set it to https://127.0.0.1:9297, they go to the local emulator.
What are the benefits of using AWS Emulator?
Before the verification, let me organize what AWS Emulator improves. I matched the use cases described in posts by AWS-affiliated authors (the AWS News Blog, a guest post by Swami Sivasubramanian, an AWS executive, on Werner Vogels' blog, and an article from the AWS organization on DEV.to) with what I could confirm in this verification. The source names used in the table and the URLs I referred to are as follows. All of them were published on 2026-11-11.
| Source name in the table | Content | URL |
|---|---|---|
| AWS News Blog | Test your applications against AWS locally with AWS Emulator (the author is a senior specialist solutions architect at AWS) | https://aws.amazon.com/blogs/aws/test-your-applications-against-aws-locally-with-aws-emulator/ |
| Swami's post | AWS Emulator and the engineer's job when agents write the code (a guest post by Swami Sivasubramanian on Werner Vogels' blog, All Things Distributed) | https://www.allthingsdistributed.com/2026/11/aws-emulator-and-the-engineers-job-when-agents-write-the-code.html |
| DEV.to | How emulators might catch what you might miss (an article under the AWS organization) | https://dev.to/aws/how-emulators-might-catch-what-you-might-miss-34hd |
| Benefit | What AWS says | What I confirmed in this article |
|---|---|---|
| You can try things without waiting for deployments | Unit tests can run locally without a deployment (AWS News Blog). It needs no deployment and no AWS account, so you can run tests on every change (DEV.to) | CDK deployments finished in 12 to 18 seconds. A single API call took from a few milliseconds to the low twenties of milliseconds (a single measurement, no comparison with real AWS) |
| You only change the destination, not your code or IaC | You point at the endpoint with environment variables, and no change to the application code is needed (AWS News Blog) | The boto3 code ran as is, in a form that would also work on real AWS once the environment variables are removed. CDK and Terraform also deployed with only the AWS_ENDPOINT_URL setting |
| It can find bugs that mocks miss | A bug that charges without deleting the SQS message passes with a mock, but with the emulator the message is redelivered and the test fails (DEV.to) | Errors in the AWS format (such as NoSuchKey and ConditionalCheckFailedException) were returned. Missing permissions of a Lambda role were also detected as AccessDeniedException. SQS redelivery itself is unverified |
| Unimplemented APIs are easy to spot | Unimplemented APIs return HTTP 501, and tests fail right away (AWS News Blog) | Of 27 APIs I tried, 4 returned 501. Because nothing silently succeeds, you notice |
| You can run multiple tests or agents in parallel | Each agent gets its own copy (separate port, keys, and state), with no shared test account (DEV.to) | I started a second emulator (9298) and confirmed that neither resources nor credentials are visible from the first |
| Coding agents can iterate on verification | They can create resources, check the result, and try again without waiting for a Regional deployment. The demo uses Kiro to operate DynamoDB (AWS News Blog, Swami's post) | In this article, I operated the CLI myself instead of an agent. Operation through an agent is unverified |
| You can avoid cloud costs and shared-resource conflicts | Testing on real cloud infrastructure costs money, and concurrently running tests overwrite shared resources and conflict (DEV.to) | No additional charge (official documentation). All the verification in this article was done on the emulator, and I created no resources on real AWS |
| You can start from a clean state every time | (Not mentioned in the posts I read) | A restart took 2 seconds, and the stacks and Lambda functions were all gone. It is easy to align the starting conditions of a test, but if you restart in the middle, everything is lost |
| You can check in advance how closely it matches | Fidelity scores are published per service as passed/total, and the tests are published on GitHub (AWS News Blog). Known behaviors that do not match AWS are also published in the repository (Swami's post) | In Step 5 and Step 6, I compared the scores and the known issues with my own results |
The posts from AWS are all explanations from the provider. The right column of the table shows the results of trying each item once on Windows 11 + Docker, and I did not compare them with real AWS.
At the same time, the posts from AWS do not say that the emulator can do everything.
- They suggest keeping mocks for "the logic that you control" and using the emulator for the parts that depend on the behavior of AWS services (DEV.to)
- It does not evaluate IAM policies or resource-based policies the way AWS does, so do not use it for security testing (AWS News Blog)
- One emulator acts as a single account, and there is no cross-account or cross-Region behavior (AWS News Blog)
These benefits also depend on the fidelity of the services you use. Whether each benefit holds for your own configuration is something I check in the following steps.
How to read fidelity
AWS measures how closely the emulator matches AWS with "fidelity tests", and publishes the number of passed tests out of the total for each service with every release.
| Term | Meaning |
|---|---|
| Fidelity test | A short program that runs a workflow with an SDK and compares the result, the state, and the errors with AWS |
| Fidelity score | The number of passed tests out of the total, per service (for example, 40 / 50) |
| Control plane | APIs for creating, reading, updating, deleting, and listing resources |
| Data plane | The main function of a service (for example, getting and putting objects in S3) |
Scores can only be compared between the same versions of the fidelity tests.
Verification environment
| Item | Details |
|---|---|
| OS | Windows 11 |
| How it was run | Docker 28.0.4 (the official Dockerfile) |
| Emulator | 1.0.322 (release date 2026.11.10, commit 7753983). It matched the latest GitHub release, v1.0.322 |
| Verification date | 2026-11-12 |
| IaC | AWS CDK (CLI 2.1148.1, aws-cdk-lib 2.275.0), Terraform 1.16.2 (AWS provider 6.73.0) |
| Other | Python 3.12.7, boto3 1.43.91, AWS CLI 2.39.3, Node.js v22.13.1 |
| Startup options |
-p 9297 --telemetry off. The port is published only on 127.0.0.1, with a 2 GiB memory limit |
The emulator is released several times a day, so the results depend on the verification date and the release.
Step 1: Start the emulator
I built the image from the official Dockerfile and started it with docker run. I used the Dockerfile as is (for x86_64; for Arm, change the rpm name to aarch64). A NOKEY warning about the rpm signature appeared during the build, but the build completed.
FROM public.ecr.aws/amazonlinux/amazonlinux:2023-minimal
ADD https://downloads.aws.com/aws-emulator/packages/aws-emulator/LTS/latest/aws-emulator-x86_64.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"]
docker build -t aws-emulator .
docker run -d --name emu --memory=2g --memory-swap=2g -p 127.0.0.1:9297:9297 `
-v "$env:USERPROFILE\.aws\emulator:/home/emulator/.aws/emulator" `
aws-emulator -p 9297 --telemetry off
This is the startup log (docker logs emu).
{"timestamp":"2026-11-11T22:33:58.891517Z","level":"INFO","fields":{"message":"aws-emulator emulator starting"},"target":"aws_emulator"}
This emulator collects usage and error telemetry: disabled (via the embedding configuration) — to turn it back on, remove every opt-out setting and restart
{"timestamp":"2026-11-11T22:33:58.944196Z","level":"INFO","fields":{"message":"request authentication enabled","credentials_file":"/home/emulator/.aws/emulator/9297/emulator_credentials"},"target":"aws_emulator_server"}
{"timestamp":"2026-11-11T22:33:58.963607Z","level":"INFO","fields":{"message":"emulator server information","endpoint_url":"https://127.0.0.1:9297","health_check_url":"https://127.0.0.1:9297/__admin/health","config_file":"/home/emulator/.aws/emulator/9297/config","ca_file":"/home/emulator/.aws/emulator/9297/ca.pem","java_truststore_file":"/home/emulator/.aws/emulator/9297/truststore.p12"},"target":"aws_emulator_server"}
{"timestamp":"2026-11-11T22:33:58.963668Z","level":"WARN","fields":{"message":"the emulator listens on 0.0.0.0, not loopback: other machines that can reach port 9297 can call it; bind 127.0.0.1 (-b 127.0.0.1) unless that is intended","bind_address":"0.0.0.0"},"target":"aws_emulator_server"}
The log shows that telemetry is disabled, that request authentication is enabled, and that the endpoint is https://127.0.0.1:9297. The last warning about 0.0.0.0 appears because -b 0.0.0.0 in the Dockerfile makes the emulator listen on all addresses inside the container. On the host side, the port is published only to my own PC with -p 127.0.0.1:9297:9297, so it is not reachable from outside.
Next, I pointed the SDK and CLI at the emulator with environment variables, following the official procedure. The variables that can hold real credentials are removed.
$env:AWS_ENDPOINT_URL = "https://127.0.0.1:9297"
$env:AWS_SHARED_CREDENTIALS_FILE = "$env:USERPROFILE\.aws\emulator\9297\emulator_credentials"
$env:AWS_CONFIG_FILE = "$env:USERPROFILE\.aws\emulator\9297\config"
$env:AWS_CA_BUNDLE = "$env:USERPROFILE\.aws\emulator\9297\ca.pem"
$env:AWS_EC2_METADATA_DISABLED = "true"
$env:AWS_DISABLE_HOST_PREFIX_INJECTION = "true"
$env:AWS_REGION = "us-east-1"
These are the results of the connectivity check.
| Check | Result |
|---|---|
/__admin/health |
ok |
aws sts get-caller-identity |
Account 000000000000, user/emulator
|
| S3: create a bucket with a regular name, put/get, delete | Succeeded |
DynamoDB table name ab
|
A ValidationException in the same format as AWS |
S3 amzn-s3-demo-bucket (the name used in the official documentation examples) |
Failed with InvalidBucketName
|
The last row was different from what I expected. You cannot create a bucket with the name used in the official documentation examples. This is not a defect of the emulator: the S3 bucket naming rules reserve names that start with amzn-s3-demo- (source: https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html ). It also means that the emulator validates even this rule in the same way as AWS.
The official documentation gives the following points to watch out for.
- If you run tests in a shell where
AWS_ENDPOINT_URLis not set, they call real AWS with whatever credentials are found - If environment variables such as
AWS_ACCESS_KEY_IDremain set, the emulator rejects the requests - Publish the port in the form
-p 127.0.0.1:9297:9297(the short form exposes it to the whole network)
Step 2: Deploy with CDK in stages
I add resources to a single stack in stages with -c stage=N.
cdk bootstrap
cdk bootstrap aws://000000000000/us-east-1 succeeded in 17 seconds. The CDKToolkit stack that was created contains an S3 bucket, an ECR repository, IAM roles (five of them), and an SSM parameter, and I could also read the SSM parameter /cdk-bootstrap/hnb659fds/version.
Note: the combination of CDK CLI 2.1148.1 and aws-cdk-lib 2.276.0 failed, because the library requires CLI 2.1148.2, which had not been published yet. This is a CDK-side issue and unrelated to the emulator. In this article, I pinned the library to 2.275.0.
Results by stage
| Stage | Resources | cdk deploy | Does it actually work? |
|---|---|---|---|
| 1 | S3 bucket | Succeeded (17 s) | head-bucket succeeded |
| 2 | DynamoDB (PAY_PER_REQUEST) | Succeeded (17 s) |
ACTIVE; put-item/get-item succeeded |
| 3 | SQS + SNS + subscription | Succeeded (17 s) | An SNS publish reached SQS |
| 4 | IAM role + Lambda (inline, Node 22) | Succeeded (12 s) | State=Active, but invoke failed with exports is not defined
|
| 5 | EventBridge (rate 5 minutes) → SQS | Succeeded (17 s) | The scheduled run fired and an event reached SQS |
| 6 | Step Functions (Pass) | Succeeded (17 s) | start-execution → SUCCEEDED
|
| 7 | CloudWatch Logs + Alarm | Succeeded (17 s) | The log group was created. The alarm does not appear in describe-alarms
|
| 8 | Lambda (Code.fromAsset) |
Succeeded (13 s) | invoke succeeded. The asset was published to the staging bucket |
| 9 | Lambda + API Gateway (REST, GET) | Failed three times, succeeded on the fourth (18 s) |
HelloEmulator is returned (see "Checking that it works" below) |
| 10 | API Gateway → Lambda → DynamoDB (item registration API) | Failed twice, succeeded on the third (then redeployed after adding permissions) | Saved with POST and retrieved with GET (see "Step 3: Check how the emulator behaves" below) |
| destroy | Delete the stack | Succeeded (12 s) | Only the log groups for Lambda remain (because CDK creates them with DeletionPolicy: Retain; this is not an emulator issue) |
This is the output when I redeployed the stage 8 stack with no changes.
CdkSampleStack: deploying... [1/1]
✅ CdkSampleStack (no changes)
✨ Deployment time: 0s
Outputs:
CdkSampleStack.BucketName = cdksamplestack-bucket83908e77-db9d1636caba
CdkSampleStack.FnAssetName = CdkSampleStack-FnAssetDC7A43AF-BEA29F2C2421
CdkSampleStack.FnName = CdkSampleStack-Fn9270CBC0-3ADA671FD9E8
CdkSampleStack.LogGroupName = CdkSampleStack-Lg419948D3-9E819B94EA09
CdkSampleStack.QueueUrl = https://sqs.us-east-1.amazonaws.com/000000000000/CdkSampleStack-Queue4A7E3555-148DCCAAE08B
CdkSampleStack.SmArn = arn:aws:states:us-east-1:000000000000:stateMachine:CdkSampleStack-SM934E715A-EFE2C378FC2A
CdkSampleStack.TableName = CdkSampleStack-TableCD117FA1-A02DF716D6D7
CdkSampleStack.TopicArn = arn:aws:sns:us-east-1:000000000000:CdkSampleStack-TopicBFC7AF6E-7C0061677BC0
You can also check the contents of the deployed stack with list-stack-resources. At stage 8, all 18 resources in the template were CREATE_COMPLETE. However, as described above, the alarm does not appear in describe-alarms even though it is CREATE_COMPLETE.
$ aws cloudformation list-stack-resources --stack-name CdkSampleStack --query "StackResourceSummaries[].[ResourceType,ResourceStatus]" --output table
---------------------------------------------------------
| ListStackResources |
+-----------------------------------+-------------------+
| AWS::CloudWatch::Alarm | CREATE_COMPLETE |
| AWS::S3::Bucket | CREATE_COMPLETE |
| AWS::CDK::Metadata | CREATE_COMPLETE |
| AWS::Lambda::Function | CREATE_COMPLETE |
| AWS::Lambda::Function | CREATE_COMPLETE |
| AWS::Logs::LogGroup | CREATE_COMPLETE |
| AWS::IAM::Role | CREATE_COMPLETE |
| AWS::Logs::LogGroup | CREATE_COMPLETE |
| AWS::IAM::Role | CREATE_COMPLETE |
| AWS::Logs::LogGroup | CREATE_COMPLETE |
| AWS::SQS::Queue | CREATE_COMPLETE |
| AWS::SNS::Subscription | CREATE_COMPLETE |
| AWS::SQS::QueuePolicy | CREATE_COMPLETE |
| AWS::Events::Rule | CREATE_COMPLETE |
| AWS::StepFunctions::StateMachine | CREATE_COMPLETE |
| AWS::IAM::Role | CREATE_COMPLETE |
| AWS::DynamoDB::Table | CREATE_COMPLETE |
| AWS::SNS::Topic | CREATE_COMPLETE |
+-----------------------------------+-------------------+
$ aws cloudwatch describe-alarms
{
"MetricAlarms": [],
"CompositeAlarms": [],
"LogAlarms": []
}
Where it stopped, or what was noteworthy
| Category | Details | Applies to |
|---|---|---|
| Not implemented (501) | Not encountered in this IaC. Separately, when I tried 27 APIs, 4 returned 501 (see below) | None |
| State transitions | The DynamoDB table became ACTIVE
|
None |
| Succeeded in CloudFormation but not usable | The alarm is CREATE_COMPLETE but does not appear in describe-alarms. An alarm created directly with put-metric-alarm does appear in the list |
Stage 7 |
| Lambda inline code | The inline exports.handler is treated as index.mjs and fails with exports is not defined. It works as index.js with Code.fromAsset
|
Stage 4 |
| Format of output values | The SQS QueueUrl and the API Gateway URL use real AWS host names (sqs.us-east-1.amazonaws.com, *.execute-api.us-east-1.amazonaws.com). Operations through the CLI/SDK work because the endpoint is overridden, but opening these URLs with curl or a browser sends the request to real AWS | Stage 3, Stage 9 |
| Lambda Function URL | The permission resource created by addFunctionUrl went to CREATE_FAILED with the message "does not apply the InvokedViaFunctionUrl property", and was rolled back |
Stage 9 (first attempt) |
| StatementId too long | The AWS::Lambda::Permission created by the default of LambdaRestApi (the proxy+ proxy) failed with the validation error "statementId must be 100 characters or less", because the statement ID contains the stack name and a long logical ID. I worked around it by defining only a single GET or by using shorter IDs. The same issue is recorded as a known issue in the official repository (Step 6) |
Stage 9, Stage 10 |
| Duplicate stage name | Creating a second REST API with the prod stage in the same stack went to CREATE_FAILED with prod already exists in stack. I worked around it by changing the stage name. In AWS, a stage name is scoped to each API, but the emulator does not allow duplicates within a stack. I have not confirmed the behavior on real AWS |
Stage 10 |
| Redeploy after rollback | The log groups that CDK creates are Retain, so they remain even after a failed rollback, and a redeploy failed with "The specified log group already exists" (AlreadyExists). I deleted them manually and redeployed. This is a CDK/CloudFormation behavior and not specific to the emulator |
Stage 9, Stage 10 |
| Lambda missing permissions | In stage 10, I forgot grantReadWriteData, and Lambda was denied by DynamoDB with AccessDeniedException. The emulator detected the missing permissions of the Lambda role. This is useful for finding mistakes in code |
Stage 10 |
I did not check how the same code behaves on real AWS, so I cannot say whether the Lambda inline code behavior is a difference in the emulator.
Checking that it works: does HelloEmulator come back?
Even if a deployment succeeds, that does not mean it works. So I confirmed that Lambda returns HelloEmulator and that it can be called through API Gateway.
This is the Lambda code (lambda/hello.js).
exports.handler = async () => ({
statusCode: 200,
headers: { 'Content-Type': 'text/plain' },
body: 'HelloEmulator',
});
In CDK, this Lambda is connected to a LambdaRestApi, with only a single GET defined on the root (/).
| How I called it | Result |
|---|---|
aws lambda invoke |
StatusCode 200; the body in the response is HelloEmulator
|
aws apigateway test-invoke-method |
status 200, body HelloEmulator
|
curl (the Host header set to the API's host name; the connection goes to the emulator) |
HTTP/1.1 200 OK, content-type: text/plain, body HelloEmulator
|
curl (path form /restapis/<id>/prod/_user_request_/) |
404 UnknownService (not reachable with this path) |
This is an example curl command. To call API Gateway, you send the request to the emulator's single endpoint and specify the target API with the Host header.
curl.exe -i --cacert $env:AWS_CA_BUNDLE `
-H "Host: <restApiId>.execute-api.us-east-1.amazonaws.com" `
https://127.0.0.1:9297/prod/
HTTP/1.1 200 OK
content-type: text/plain
content-length: 13
x-amzn-requestid: 863869a5-ede1-4e56-bf04-151a017052da
date: Wed, 11 Nov 2026 22:58:41 GMT
HelloEmulator
If you open the URL that CDK outputs (https://<id>.execute-api.us-east-1.amazonaws.com/prod/) as is, the request goes to real AWS. To send it to the emulator, connect to 127.0.0.1 and specify the API with the Host header, as shown above.
Step 3: Check how the emulator behaves
So far, the question was whether the IaC can be deployed. For readers who are new to AWS Emulator, I also checked the behavior of the emulator itself from several angles. All the examples below were run in a shell with the environment variables from Step 1 set.
1. Point your code at the emulator without changing it
You do not write an endpoint or credentials in the application code; the destination is decided by environment variables alone. The following Python (boto3) is code that would work as is on real AWS if you removed the environment variables.
import boto3, uuid
s3 = boto3.client("s3"); ddb = boto3.client("dynamodb"); sqs = boto3.client("sqs")
sfx = uuid.uuid4().hex[:8]
s3.create_bucket(Bucket=f"demo-{sfx}")
s3.put_object(Bucket=f"demo-{sfx}", Key="a.txt", Body=b"hello")
print(s3.get_object(Bucket=f"demo-{sfx}", Key="a.txt")["Body"].read())
ddb.create_table(TableName=f"demo-{sfx}", BillingMode="PAY_PER_REQUEST",
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}])
ddb.put_item(TableName=f"demo-{sfx}", Item={"id": {"S": "1"}})
These are the results (a single measurement, on local Docker, in milliseconds).
| Operation | Time |
|---|---|
| S3 create_bucket | 18.5 |
| S3 put_object | 3.9 |
| S3 get_object | 2.9 |
| DynamoDB create_table (ACTIVE immediately afterward) | 4.5 |
| DynamoDB put_item | 2.6 |
| SQS create_queue | 22.0 |
| SQS send_message | 1.9 |
| SQS receive_message | 1.8 |
AWS says that calls come back in milliseconds. In this measurement, too, a single call took from a few milliseconds to the low twenties of milliseconds. I did not compare this with real AWS.
2. Invalid calls return errors in the AWS format
| Call | Error returned |
|---|---|
| S3: get a key that does not exist | NoSuchKey: The specified key does not exist. |
| S3: head-bucket on a bucket that does not exist | 404 Not Found |
| SQS: get the URL of a queue that does not exist | AWS.SimpleQueueService.NonExistentQueue |
| DynamoDB: describe a table that does not exist | ResourceNotFoundException: Requested resource not found: Table: nope not found |
| DynamoDB: a conditional write whose condition is not met | ConditionalCheckFailedException |
| Lambda: get a function that does not exist | ResourceNotFoundException: Function not found: arn:aws:lambda:...:function:nope |
Unlike a mock that simply succeeds when you call it, the implemented APIs run validation and produce errors. I have not confirmed that they match AWS exactly, but the error codes are in the same format as AWS.
3. Unimplemented APIs fail clearly
AWS says that unimplemented APIs return HTTP 501. When I tried 27 APIs, 4 of them returned 501.
| API | Result |
|---|---|
athena list-work-groups |
501 NotImplemented (operation ListWorkGroups is scaffolded but not implemented in this emulator) |
translate list-text-translation-jobs |
501 |
comprehend list-entity-recognizers |
501 |
quicksight list-dashboards |
501 |
| The other 23 | Responded without an error (whether the behavior matches AWS is unconfirmed) |
Responding without an error does not mean the behavior is correct. Still, it is an advantage of 501 that an unimplemented API does not silently succeed and a test can notice immediately as a failure.
4. Authentication is required and changes with every startup
| Call | Result |
|---|---|
| A request without a signature (curl) | 403 UnrecognizedClientException: The security token included in the request is invalid.
|
| A request signed with arbitrary keys | AccessDenied: Sign the request with the emulator credentials |
| After restarting the emulator | The credentials file is rewritten (new keys are generated at every startup) |
According to the official documentation, this design prevents other web pages or local programs from calling the emulator without your knowledge.
5. Multiple emulators are independent
I started a second emulator (9298) on a different port and checked.
| Operation | Result |
|---|---|
| Create a bucket on the first one (9297) | It exists only on 9297 |
| List buckets on the second one (9298) | Empty (the resources on 9297 are not visible) |
| Create a bucket on the second one | It exists only on 9298 and is not visible on 9297 |
| Call the second one with the credentials of the first |
AccessDenied (the keys are different) |
If you start one emulator per agent, per CI job, or per branch, their resources are not visible to each other.
6. State lives in memory only and is lost on restart
| Before restart | After restart |
|---|---|
| 2 stacks (CDKToolkit, CdkSampleStack), 3 buckets, 3 tables | All zero (the stacks and Lambda functions are gone) |
The restart completed in 2 seconds. You can start from an initial state every time, but if you restart in the middle of a test, everything is lost. There is no persistence (as stated in the official documentation).
7. An application that spans services works (API Gateway → Lambda → DynamoDB)
I built a small API that registers and retrieves items. Lambda saves the value received by POST into DynamoDB, and GET retrieves it.
const { DynamoDBClient, PutItemCommand, GetItemCommand } = require('@aws-sdk/client-dynamodb');
const ddb = new DynamoDBClient({});
exports.handler = async (event) => {
if (event.httpMethod === 'POST') {
const { id, text } = JSON.parse(event.body || '{}');
await ddb.send(new PutItemCommand({ TableName: process.env.TABLE_NAME, Item: { id: { S: id }, text: { S: text } } }));
return { statusCode: 201, body: JSON.stringify({ saved: id }) };
}
const id = event.pathParameters.k;
const r = await ddb.send(new GetItemCommand({ TableName: process.env.TABLE_NAME, Key: { id: { S: id } } }));
return r.Item ? { statusCode: 200, body: JSON.stringify({ id, text: r.Item.text.S }) } : { statusCode: 404, body: '{"message":"not found"}' };
};
To call the emulator's DynamoDB from inside Lambda, I had to specify environment variables explicitly. The emulator does not automatically pass an endpoint or credentials to the Lambda execution environment. These are the values I confirmed.
| Item | Details |
|---|---|
| Lambda execution environment | Node.js (process.version is v22.0.0). I could load the DynamoDB client (@aws-sdk/client-dynamodb) |
| Default environment variables | No AWS_ENDPOINT_URL, no credentials |
| Environment variables I added |
AWS_ENDPOINT_URL=https://127.0.0.1:9297, AWS_SHARED_CREDENTIALS_FILE, AWS_CONFIG_FILE, NODE_EXTRA_CA_CERTS (all paths inside the container) |
In the first deployment, I forgot to grant permissions to DynamoDB (grantReadWriteData). The emulator then detected the missing permissions of the Lambda role and returned 500.
AccessDeniedException: User: arn:aws:sts::000000000000:assumed-role/CdkSampleStack-AppServiceRole.../CdkSampleStack-App... is not authorized to perform: dynamodb:PutItem on resource: arn:aws:dynamodb:us-east-1:000000000000:table/... because no identity-based policy allows the dynamodb:PutItem action
These are the results after adding the permissions and redeploying.
| Request | Result |
|---|---|
POST /v1/i ({"id":"item-1","text":"HelloEmulator from DynamoDB"}) |
201 Created, {"saved":"item-1"}
|
GET /v1/i/item-1
|
200 OK, {"id":"item-1","text":"HelloEmulator from DynamoDB"}
|
GET /v1/i/nope
|
404 Not Found, {"message":"not found"}
|
aws dynamodb scan (directly from the CLI) |
1 item; item-1 is saved |
With this, I confirmed that the whole flow of request → API Gateway → Lambda → DynamoDB → retrieval works. Here is the flow as a diagram.
However, AWS asks you not to use the emulator for security tests such as IAM policies. The emulator did detect the permission error this time, but its decision is not necessarily the same as AWS's (AWS states that policies are evaluated differently from AWS).
Step 4: Try Terraform
I pointed two resources, an S3 bucket and a DynamoDB table, at the emulator using only the AWS_ENDPOINT_URL environment variable. On the provider side, I set skip_credentials_validation, skip_requesting_account_id, skip_metadata_api_check, and s3_use_path_style.
| Operation | Result |
|---|---|
| init | Succeeded |
| apply | Succeeded (13 s). Creating the DynamoDB table took 6 s |
| destroy | Succeeded (6 s) |
| plan right after apply | A diff appears on DynamoDB (max_read_request_units/max_write_request_units of on_demand_throughput go from -1 to null). 1 to change |
| apply again |
UpdateTable fails with a ValidationException |
This diff is recorded as a known issue in the official repository (Step 6). To isolate the cause, I ran describe-table directly, and OnDemandThroughput came back with MaxReadRequestUnits: -1 and MaxWriteRequestUnits: -1. The provider treats the fact that an unset item comes back with this value as a diff and tries to update it, but since the update is empty, UpdateTable fails; that is my interpretation. I have not confirmed how this item is returned for an unset table on real AWS.
This is the output when I ran terraform plan right after terraform apply.
$ terraform plan
aws_dynamodb_table.t: Refreshing state... [id=tf-emu-sample-table]
aws_s3_bucket.b: Refreshing state... [id=tf-emu-sample-bucket-20261112]
Terraform will perform the following actions:
# aws_dynamodb_table.t will be updated in-place
~ resource "aws_dynamodb_table" "t" {
id = "tf-emu-sample-table"
name = "tf-emu-sample-table"
tags = {}
# (13 unchanged attributes hidden)
- on_demand_throughput {
- max_read_request_units = -1 -> null
- max_write_request_units = -1 -> null
}
# (3 unchanged blocks hidden)
}
Plan: 0 to add, 1 to change, 0 to destroy.
If you run terraform apply once more in this state, it fails with the following error.
Error: updating AWS DynamoDB Table (tf-emu-sample-table): operation error DynamoDB: UpdateTable, https response error StatusCode: 400, RequestID: (omitted), api error ValidationException: At least one of ProvisionedThroughput, BillingMode, UpdateStreamEnabled, GlobalSecondaryIndexUpdates, SSESpecification, ReplicaUpdates, MultiAccountReplicaReady, ReplicaTransitRoleArn, MultiRegionConsistency, DeletionProtectionEnabled, OnDemandThroughput, WarmThroughput or TableClass is required
Step 5: Compare with the fidelity scores
Release v1.0.322 comes with the score data scores.json.gz (commit 7753983, aggregated at 2026-11-11 00:09 UTC). It contains the result (pass/fail/skipped) of each fidelity test and the list of services. It is not a table compiled by AWS, so in this article I recounted the results for each service.
- Overall: 24,243 of 42,857 tests (excluding skipped) passed (56.6%)
- How I counted: for tests that target only one service, I counted pass/total per service (tests that span multiple services are not included)
- Note: scores can only be compared between the same versions of the tests (as stated in the official documentation). My aggregation is not an official score table
| Service | pass / total | Ratio | Result in this CDK/Terraform run |
|---|---|---|---|
| SQS | 299 / 333 | 90% | Worked, up to delivery via SNS |
| ECR | 233 / 287 | 81% | Created by bootstrap |
| S3 | 965 / 1,220 | 79% | Creation, put/get, and asset publishing worked |
| DynamoDB | 807 / 1,044 | 77% | CDK worked. With Terraform, the second apply failed |
| SNS | 210 / 284 | 74% | Worked |
| Step Functions | 432 / 589 | 73% | The execution succeeded |
| CloudWatch | 621 / 848 | 73% | The alarm is not visible through CFN |
| CloudWatch Logs | 466 / 642 | 73% | Log group creation worked |
| IAM | 488 / 691 | 71% | Role creation worked |
| STS | 94 / 169 | 56% | get-caller-identity worked |
| Lambda | 365 / 787 | 46% | Assets worked; inline failed |
| SSM | 92 / 452 | 20% | Reading the bootstrap parameter worked |
| CloudFormation | 414 / 2,612 | 16% | All the stacks in this run worked |
Even where the score is low, the simple stacks in this run worked. I think the 16% for CloudFormation is because many of its tests target complex configurations (spanning services, or involving rollbacks and updates), but I have not checked the breakdown. A low ratio does not necessarily mean your configuration will not work, so the reliable approach is to try the range of APIs you actually use.
Step 6: Compare with known issues in the official repository
In the issues/ directory of aws/aws-emulator, each known difference is a separate file. They are divided into two folders: things-that-do-not-work/ (unresolved) and things-that-now-work/ (resolved). I checked whether the differences I found in this verification were already recorded.
This is how I searched.
- I sparse-checked-out only
issues/(HEAD is commit 7753983, the same as release v1.0.322 that I used) - There are 6,544 unresolved and 12,708 resolved files
- I ran a full-text search over all files with keywords related to the differences I found, and read the matching files
These are the results.
| My observation | Known issue | Verdict |
|---|---|---|
DynamoDB on_demand_throughput comes back as -1, and Terraform keeps showing a diff |
20261108T011128Z-dynamodb-2-net-new-causes-describetable-invents-an.md (unresolved). It states that "DescribeTable returns an OnDemandThroughput of -1/-1 even for tables that did not request a maximum, and drift detection in CloudFormation, Terraform, and similar tools reports a difference for every such table" |
Match |
The statementId of AWS::Lambda::Permission exceeds 100 characters |
20261105T132157Z-cloudformation-8-net-new-root-causes-in-the-684719.md (unresolved, cause 1) and 20261109T152222Z-apigateway-cloudformation-lambda-cfn-generated-lambda-permission-statementid.md (unresolved). They state that "CloudFormation does not truncate the generated statement ID to 100 characters, and every stack of CDK RestApi/LambdaRestApi fails" |
Match |
A CloudWatch alarm created through CloudFormation does not appear in describe-alarms
|
20261023T113357Z-cloudformation-reports-create-complete-for-logs-cloudwatch-ecs.md (unresolved). Its traces include a check where describe_alarms is empty for a Lambda + alarm deployed through CFN |
Match |
Lambda inline code (Code.fromInline) fails with exports is not defined
|
No matching issue. There are several related issues on the execution fidelity of the Node.js runtime. One of them says that index.handler should work both as CommonJS (exports.handler) and as an ES module (human-lambda-invoke-must-run-nodejs20-x-and-nodejs22-x-handlers.md), but I found nothing about the file name used for inline code |
None (possibly unreported) |
The Lambda Function URL (addFunctionUrl) permission fails with InvokedViaFunctionUrl
|
No matching issue. There are other issues about Function URLs (for example, 20261029T233239Z-lambda-function-url-configs-return-a-default-invokemode.md), but none deals with InvokedViaFunctionUrl
|
None (possibly unreported) |
A second REST API in the same stack fails with the prod stage (prod already exists in stack) |
No matching issue. already exists in stack appears in several issues for another purpose (the message wording when a physical name collides), but none deals with duplicate stage names |
None (possibly unreported) |
What I can read from this:
- Three of the differences I found were already recorded in the official repository. For two of them (
on_demand_throughputandstatementId), the issues describe the AWS-side behavior ("CloudFormation keeps the ID within 100 characters" and "OnDemandThroughputis omitted"). I have not confirmed this on real AWS; this is what the issues state. - For the remaining three (inline code, Function URL, and duplicate stage names), the full-text search found no match. They may be unreported. AWS accepts reports of differences through GitHub Issues.
Notes:
- The issue files are records of differences raised from test runs, and they were written against a commit at a certain point in time (each file contains a note showing that commit). The same problem does not necessarily occur in the current release.
- In fact, there was an example that showed how outdated they can be. The file
20261106T052537Z-apigatewayv2-lambda-logs-the-nodejs22-x-lambda-runtime.mdin the unresolved folder says that "the nodejs22.x runtime does not bundle the AWS SDK v3", but in this release I could load@aws-sdk/client-dynamodbfrom Lambda and it worked. Even if an issue is in the unresolved folder, it may already be fixed. - A full-text search depends on keywords. Issues written with wording that does not match my search terms may have been missed.
How to use it in practice
| Aspect | Details |
|---|---|
| Good for | Syntax errors in IaC, references between resources, basic create and delete, smoke tests in CI, integration tests of applications that use the SDK (I confirmed the whole path of API → Lambda → DynamoDB) |
| Not good for | Validating IAM or resource-based policies (AWS lists them as out of scope. The emulator did detect the missing Lambda permissions this time, but its decision is not necessarily the same as AWS's) |
| Caution: status | Do not rely on "the stack succeeded" alone; check the created resources separately with describe or by actually calling them (the alarm is an example) |
| Caution: destination | If you run commands in a shell where AWS_ENDPOINT_URL is empty, they go to real AWS. The URLs that CDK outputs use real AWS host names, so do not open them as they are |
| Caution: Lambda | If you call other services from inside Lambda, specify the endpoint and credentials explicitly with environment variables. Inline code did not work, so deploy it as an asset |
| Caution: state | In memory only. Everything is lost on restart. Use it on the assumption that you recreate it for each test |
| Operations | Record the emulator release, the verification date, and the fidelity scores of the services you use. It is released several times a day, so the results can change |
Conclusion
Even right after its release, AWS Emulator could run a wider range than I had expected, from CDK deployments to processing that spans API Gateway, Lambda, and DynamoDB. On the other hand, even when the CloudFormation status was successful, there were resources that were not actually usable, like the alarm. Instead of judging by the success of the deployment alone, checking the behavior of the created resources is a prerequisite for operating it. It is well suited to integration tests in CI and to environments where AI agents can safely experiment, while permission checks such as IAM still need to be done on real AWS. The emulator is updated several times a day, so the differences I confirmed in this article may change in the future. If you try it with your own configuration and find a difference that is not in the official records, reporting it through GitHub Issues leads to improvements. Note that all the verification was done on the emulator, and I did not compare it with real AWS.
References
- AWS Emulator is now available for building and testing AWS applications locally (2026-11-11) https://aws.amazon.com/about-aws/whats-new/2026/11/aws-emulator-now-available/
- AWS Emulator documentation https://docs.aws.amazon.com/emulator
- Fidelity in AWS Emulator https://docs.aws.amazon.com/emulator/latest/userguide/fidelity.html
- Pointing your AWS SDK at AWS Emulator https://docs.aws.amazon.com/emulator/latest/userguide/point-sdk-to-emulator.html
- Security in AWS Emulator https://docs.aws.amazon.com/emulator/latest/userguide/security.html
- Getting started with Docker https://docs.aws.amazon.com/emulator/latest/userguide/getting-started-docker.html
- aws/aws-emulator (GitHub) https://github.com/aws/aws-emulator
- aws/aws-emulator release v1.0.322 (with the fidelity data scores.json.gz attached) https://github.com/aws/aws-emulator/releases/tag/v1.0.322
- Test your applications against AWS locally with AWS Emulator (AWS News Blog, 2026-11-11) https://aws.amazon.com/blogs/aws/test-your-applications-against-aws-locally-with-aws-emulator/
- AWS Emulator and the engineer's job when agents write the code (All Things Distributed, 2026-11-11) https://www.allthingsdistributed.com/2026/11/aws-emulator-and-the-engineers-job-when-agents-write-the-code.html
- How emulators might catch what you might miss (DEV.to, AWS organization, 2026-11-11) https://dev.to/aws/how-emulators-might-catch-what-you-might-miss-34hd
- General purpose bucket naming rules (Amazon S3) https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html





Top comments (0)