# AWS Emulator: a local AWS for you and your coding agents

> AWS Emulator runs the AWS APIs on your machine, with no account. How to run it on a Mac, and why it's a good test loop for coding agents.

Author: [Flavio Copes](https://flaviocopes.com/about/) | Published: 2026-11-12 | Topics: [AI](https://flaviocopes.com/tags/ai/) | Canonical: https://flaviocopes.com/aws-emulator/

**AWS Emulator** is a program that serves the AWS APIs on your own machine. You point the AWS CLI or an AWS SDK at it, and your code creates S3 buckets, DynamoDB tables and SQS queues that only exist in the emulator's memory.

You don't need an AWS account, it's free, and the source is [on GitHub](https://github.com/aws/aws-emulator). It's made by AWS, and the [product page](https://aws.amazon.com/products/developer-tools/emulator/) puts coding agents right in the first sentence.

![The AWS Emulator product page: local AWS for development, coding agents, test runs and CI jobs](https://flaviocopes.com/images/aws-emulator/landing-page.webp)

I asked Claude Code to set it up on my Mac and test a small script against it. Everything in this post comes from that session, including the two times the agent's requests tried to leave for the real AWS.

## How it works

The emulator is one process that listens on `https://127.0.0.1:9297` and answers for every AWS service on that port. There's no dashboard.

Every time it starts it generates a new access key, a secret key and a TLS certificate, and writes them to `~/.aws/emulator/9297/`. Requests must be signed with those keys, which stop working when the emulator stops.

```mermaid
flowchart LR
  accTitle: A coding agent testing AWS code against AWS Emulator
  accDescr: The agent runs the code, the AWS SDK sends the request to AWS Emulator on 127.0.0.1 port 9297, the emulator answers with the result or the AWS error, and the agent fixes the code. The real AWS account is never reached.
  Agent[Coding agent] --> Code[Your code + AWS SDK]
  Code --> Emu[AWS Emulator<br>127.0.0.1:9297]
  Emu -->|result or AWS error| Agent
  Code -.-x AWS[Your AWS account]
```

All the state is in memory. Stop the emulator and every resource is gone.

## Run it on a Mac

The emulator runs natively only on Linux. On macOS and Windows you run it in Docker, and AWS doesn't publish an image, so you build one from a short `Dockerfile`. You need [Docker installed](https://flaviocopes.com/docker-installation-macos/) and running.

This is the one for Apple silicon. On an Intel machine, replace `aarch64` with `x86_64`:

```dockerfile
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"]
```

Build it:

```bash
docker build -t aws-emulator .
```

The build took 26 seconds here, and the image is 1.16GB.

Now create the folder the container shares with your Mac, and start the emulator:

```bash
mkdir -p ~/.aws/emulator
docker run --rm --memory=2g --memory-swap=2g -p 127.0.0.1:9297:9297 \
  -e HOME="$HOME" -v "$HOME/.aws/emulator:$HOME/.aws/emulator" \
  aws-emulator -p 9297 --telemetry off
```

Telemetry is on by default, and `--telemetry off` disables it. Notice the `127.0.0.1:` in front of the port. Without it, Docker opens the emulator to your whole network.

It was answering its health check half a second after `docker run`, and it uses about 174MB of memory when idle.

## Point the AWS CLI at it

In another terminal, set these environment variables. They send every request to the emulator and make the AWS CLI use the emulator's keys and certificate:

```bash
export AWS_ENDPOINT_URL=https://127.0.0.1:9297
export AWS_SHARED_CREDENTIALS_FILE=~/.aws/emulator/9297/emulator_credentials
export AWS_CONFIG_FILE=~/.aws/emulator/9297/config
export AWS_CA_BUNDLE=~/.aws/emulator/9297/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
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN AWS_PROFILE
```

The last line removes any real credentials from the shell.

Check that you're talking to the emulator. Its account ID is always `000000000000`:

```bash
aws sts get-caller-identity
```

```json
{
    "UserId": "AIDAH5I7GEMO7ADXNFAZU",
    "Account": "000000000000",
    "Arn": "arn:aws:iam::000000000000:user/emulator"
}
```

Now create a bucket and list it:

```bash
aws s3api create-bucket --bucket flaviocopes-uploads
aws s3api list-buckets --query 'Buckets[].Name'
```

```json
[
    "flaviocopes-uploads"
]
```

Use the AWS CLI version 2. With version 1 (1.46.3), `aws sqs` ignored `AWS_ENDPOINT_URL` and tried to call `queue.amazonaws.com`.

## It returns the errors AWS returns

A hand-written mock accepts whatever you send it. The emulator validates requests like AWS does.

DynamoDB table names must be at least 3 characters long. Let's try with 2:

```bash
aws dynamodb create-table --table-name ab \
  --attribute-definitions AttributeName=id,AttributeType=S \
  --key-schema AttributeName=id,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST
```

```
An error occurred (ValidationException) when calling the CreateTable operation: 1 validation error detected: Value 'ab' at 'tableName' failed to satisfy constraint: Member must have length greater than or equal to 3
```

Same with an SQS queue whose name ends in `.fifo` but isn't declared as a FIFO queue:

```bash
aws sqs create-queue --queue-name orders.fifo
```

```
An error occurred (InvalidParameterValue) when calling the CreateQueue operation: Can only include alphanumeric characters, hyphens, or underscores. 1 to 80 in length
```

## Use it from Node.js

The SDK reads the same environment variables, so the code has no endpoint and no credentials in it. Node.js needs one more variable to trust the emulator's certificate:

```bash
export NODE_EXTRA_CA_CERTS=~/.aws/emulator/9297/ca.pem
```

This script creates a table and a queue, saves an order, sends its ID through the queue and reads the order back:

```js
import { DynamoDBClient, CreateTableCommand, PutItemCommand, GetItemCommand, waitUntilTableExists } from '@aws-sdk/client-dynamodb'
import { SQSClient, CreateQueueCommand, SendMessageCommand, ReceiveMessageCommand } from '@aws-sdk/client-sqs'

const db = new DynamoDBClient()
const sqs = new SQSClient({ useQueueUrlAsEndpoint: false })

await db.send(new CreateTableCommand({
  TableName: 'orders',
  AttributeDefinitions: [{ AttributeName: 'id', AttributeType: 'S' }],
  KeySchema: [{ AttributeName: 'id', KeyType: 'HASH' }],
  BillingMode: 'PAY_PER_REQUEST'
}))
await waitUntilTableExists({ client: db, maxWaitTime: 30 }, { TableName: 'orders' })
const { QueueUrl } = await sqs.send(new CreateQueueCommand({ QueueName: 'new-orders' }))

const start = performance.now()
await db.send(new PutItemCommand({
  TableName: 'orders',
  Item: { id: { S: 'order-1' }, total: { N: '49' } }
}))
await sqs.send(new SendMessageCommand({ QueueUrl, MessageBody: 'order-1' }))

const { Messages } = await sqs.send(new ReceiveMessageCommand({ QueueUrl }))
const { Item } = await db.send(new GetItemCommand({
  TableName: 'orders',
  Key: { id: { S: Messages[0].Body } }
}))
console.log(Item)
console.log(`${Math.round(performance.now() - start)}ms`)
```

Run it:

```bash
node orders.js
```

```
{ id: { S: 'order-1' }, total: { N: '49' } }
15ms
```

That's 15 milliseconds for the 4 calls between the two timestamps.

The script didn't look like this at the start. It took the agent two failed runs to get there, and those two runs are the reason I think this tool matters for agents.

## A test loop for coding agents

An agent writes AWS code from memory, and the only way to know if it works is to run it. Until now that meant giving the agent credentials for a real account, where it can create things that cost money and delete things you care about. The other option was mocks, and a mock only checks what the agent already believes.

With the emulator the agent runs the code and reads the error AWS would return.

The first version of the script wrote the order right after creating the table. It failed:

```
ResourceNotFoundException: Requested resource not found
```

On AWS a DynamoDB table takes a few seconds to become active, and the emulator reproduces that. Running the script again right away failed with `ResourceInUseException`, because the table was still being created. The agent added `waitUntilTableExists`, which is the fix you need on AWS too. A mock would have accepted the write.

The second run failed in a more interesting way:

```
InvalidClientTokenId: The security token included in the request is invalid.
```

That error came from the real AWS. The emulator returns queue URLs with the real hostname, like `https://sqs.us-east-1.amazonaws.com/000000000000/new-orders`, and the JavaScript SDK sends SQS requests to the host in the queue URL. So `SendMessage` left my Mac and reached AWS, which rejected it because it was signed with the emulator's keys. `useQueueUrlAsEndpoint: false` keeps the request on the configured endpoint.

Nothing happened, and that's because the agent had no real credentials. The emulator's keys start with `EMULATOR` and are worth nothing outside that one run. The AWS CLI version 1 request I mentioned before never left at all: Claude Code's sandbox blocked the connection to `queue.amazonaws.com`.

My advice is to run the agent with both protections: a shell with no real AWS credentials, and a sandbox that only lets it reach `127.0.0.1`. The emulator needs no network access to work.

### One emulator per agent

Each emulator has its own port, keys and state. If you run several agents in parallel, for example one per Git worktree, start one emulator for each on a different port. The port goes in three places:

```bash
docker run --rm --memory=2g --memory-swap=2g -p 127.0.0.1:9298:9298 \
  -e HOME="$HOME" -v "$HOME/.aws/emulator:$HOME/.aws/emulator" \
  aws-emulator -p 9298 --telemetry off
```

I had the `orders` table on port 9297. Listing the tables on both:

```bash
aws dynamodb list-tables --query TableNames
```

```json
[
    "orders"
]
```

On 9298 the same command prints `[]`. A request signed with the keys of 9298 and sent to 9297 is rejected:

```
AccessDenied: Sign the request with the emulator credentials
```

So an agent can't touch another agent's resources by mistake.

There's no route to reset the state. To start clean you restart the emulator, which takes half a second. Add `--idle-timeout 600` and an emulator the agent forgot stops by itself after 10 minutes without calls.

### Tell the agent about it

There's an official skill that teaches the agent to install, start and connect the emulator. It needs the AWS CLI 2.35.0 or later:

```bash
aws agent-toolkit add-skill --skill-name aws-emulator --region us-east-1
```

It installs for the agents the AWS CLI finds on your machine, like Claude Code, Cursor and Kiro. With it you can also ask "Will my app in this repo work on AWS Emulator?" and the agent checks the AWS operations your project calls against the published test results.

I didn't install it. Claude Code got the emulator running from the Docker page of the docs. What I'd add in any case is a few lines in the project's [AGENTS.md](https://flaviocopes.com/agents-md/), so the agent doesn't have to rediscover the setup:

```markdown
## Testing AWS code

- Never use real AWS credentials. Test against AWS Emulator.
- Start it with `./scripts/emulator.sh` and load `./scripts/emulator-env.sh` in the shell that runs the tests.
- `aws sts get-caller-identity` must print account `000000000000` before you run anything.
- SQS clients need `useQueueUrlAsEndpoint: false`.
- To reset the state, restart the emulator.
```

`emulator.sh` holds the `docker run` command and `emulator-env.sh` the `export` lines from above.

## Emulator, mocks or a real account

| | AWS Emulator | Hand-written mocks | A real AWS test account |
| --- | --- | --- | --- |
| AWS account | Not needed | Not needed | Needed |
| Credentials the agent holds | Keys that only work for one run | None | Real ones |
| Cost | Free | Free | You pay for what the tests create |
| Rejects invalid requests | Yes, with the AWS error | Only if you wrote that case | Yes |
| State across calls | In memory, gone on restart | Whatever you coded | Stays until you delete it |
| IAM and bucket policies | Not evaluated like AWS | No | Yes |
| Speed | Milliseconds per call | Instant | Network round trips, slow deploys |
| Cleanup | Restart | Nothing to clean | Your job |

If you've used LocalStack, it's the same idea, built by AWS from the API models the SDKs are generated from.

## What it doesn't do

Don't use it to test security. The emulator doesn't evaluate IAM policies, S3 bucket policies or KMS key policies the way AWS does, so a request AWS would deny can succeed.

How closely it matches AWS changes from service to service. It's best at the control plane, the calls that create, list and delete resources. Operations that aren't implemented yet answer with HTTP 501, so a test fails immediately:

```bash
aws iotfleetwise list-fleets
```

```
An error occurred (NotImplemented) when calling the ListFleets operation: operation ListFleets is scaffolded but not implemented in this emulator
```

Each [release](https://github.com/aws/aws-emulator/releases) publishes a score per service, and the [tests behind the scores](https://github.com/aws/aws-emulator-evaluation-suite) are public.

A few more limits:

- Use one region per emulator. It doesn't keep resources separate by region or account.
- The container is capped at 2GB of memory. Over that it stops with exit code 137 and the resources are lost.
- It's made for test runs. AWS hasn't tested it for load or for running a long time.
- Code you pass to it, like a Lambda function, runs without isolation. In Docker it stays inside the container.
- With `localhost` in the endpoint, the JavaScript SDK puts the S3 bucket name in the hostname and the request fails. Keep `127.0.0.1`.

## It was built by agents

AWS says the emulator was [written entirely by autonomous agents](https://aboutamazon.com/news/aws/how-autonomous-ai-agents-built-aws-emulator), and that the team didn't write or review any of the code.

One team of agents writes tests from the public AWS API models and verifies each one against the real AWS. A second team replays those tests against the emulator and files an issue for every difference. A third fixes one issue at a time, and a change is merged only if it passes a gate that checks, among other things, that no test regressed.

The issues are files in the repository. The folder with the fixed ones holds more than 12,700 of them. The release I ran is 1.0.322.

![The things-that-now-work folder in the aws-emulator repository on GitHub, truncated at 1,000 files with 11,709 more entries omitted, last commit by IssueFixerAgent](https://flaviocopes.com/images/aws-emulator/fixed-issues.webp)

The repository doesn't take pull requests. If you find a difference from AWS, you open an issue and the agents pick it up.

It worked because the agents had a spec, the API models, and a reference to compare against, AWS itself. The emulator gives your agent the second half of that for AWS code: something to run the code against that answers like AWS does.
