AWS Emulatorを2つ同時に動かしてデータを分ける方法
AWS Emulator は、起動するときに -p で別のポートを指定すれば、1台のマシンで2つ同時に動かせます。ポートごとに鍵と証明書が作られ、作ったテーブルやバケットも、もう一方からは見えません。CI でテストを並列実行するときや、リージョンごとに別のエミュレーターを用意したいときに、どこが分かれ、どこで間違えやすいかを、検証環境で実行したコマンドと出力で示します。
検証環境は Ubuntu 24.04(x86_64)、AWS Emulator 1.0.353(2026年11月14日リリース、Debian パッケージ)、AWS CLI 2.40.3 です。AWS のアカウントは使わず、リクエストはすべて手元の2つのエミュレーターに送りました。本物の AWS での動きや料金は、この環境では確かめていません。記事で触れるところは公式ドキュメントを出どころにしています。Docker で動かす方法、3つ以上を同時に動かす場合、-p 0 で選ばれたポートに AWS CLI から送ることは試していません。

2つ目は -p で別のポートを指定して起動する
公式ガイドにある Linux での手順のとおり、Debian パッケージで入れました。1つ目は、ポートを指定せずに起動します。-t off は、利用状況のデータ(テレメトリー)を送らない指定です。
aws-emulator -t off
検証では、2つの端末の代わりに1つのスクリプトから、aws-emulator -t off > emu-9297.log 2>&1 & のようにエミュレーターをバックグラウンドで起動し、ログをファイルに書きました(ログのファイルのパスは短くしています)。最初の2つ(-p なしと -p 9300)の起動コマンドは、リダイレクトと & を外して載せています。
コマンドリファレンスによると、オプションなしで起動すると 127.0.0.1:9297 で待ち受けます。1つ目を動かしたまま、同じように aws-emulator -t off を実行すると、ポートが使われているというエラーで止まりました。終了コードは 1 です。
$ aws-emulator -t off; echo "終了コード: $?"
{"timestamp":"2026-11-14T02:37:03.735831Z","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
emulator serve failed: server I/O error: Address already in use (os error 98)
終了コード: 1
このとき、1つ目のフォルダー ~/.aws/emulator/9297/ はそのまま残り、このあとも1つ目のエミュレーターに繋がりました。リファレンスにも、ポートが使用中のときはファイルを書かず、すでに動いているエミュレーターのファイルはそのままだと書かれています。
2つ目は -p 9300 を付けて起動します。
aws-emulator -t off -p 9300
2つの起動ログから、emulator server information の行だけを抜き出しました(ホームディレクトリのパスは ~ に置き換えています)。接続先も、ファイルの置き場所も、ポートの番号で分かれています。
{"timestamp":"2026-11-14T02:37:02.908953Z","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":"~/.aws/emulator/9297/config","ca_file":"~/.aws/emulator/9297/ca.pem","java_truststore_file":"~/.aws/emulator/9297/truststore.p12"},"target":"aws_emulator_server"}
{"timestamp":"2026-11-14T02:37:04.016603Z","level":"INFO","fields":{"message":"emulator server information","endpoint_url":"https://127.0.0.1:9300","health_check_url":"https://127.0.0.1:9300/__admin/health","config_file":"~/.aws/emulator/9300/config","ca_file":"~/.aws/emulator/9300/ca.pem","java_truststore_file":"~/.aws/emulator/9300/truststore.p12"},"target":"aws_emulator_server"}
ここからは、ポート 9297 のほうをエミュレーター A、9300 のほうをエミュレーター B と呼びます。
鍵と証明書は、ポートごとのフォルダーに別々に作られる
~/.aws/emulator/ の下には、ポート番号を名前にしたフォルダーが2つできました。中のファイルの名前は同じです(所有者の名前は user に、ホームディレクトリのパスは ~ に置き換えています)。
$ ls ~/.aws/emulator/
9297
9300
$ ls -l ~/.aws/emulator/9297/ ~/.aws/emulator/9300/
~/.aws/emulator/9297/:
total 16
-rw------- 1 user user 566 Nov 14 02:37 ca.pem
-rw------- 1 user user 143 Nov 14 02:37 config
-rw------- 1 user user 116 Nov 14 02:37 emulator_credentials
-rw------- 1 user user 550 Nov 14 02:37 truststore.p12
~/.aws/emulator/9300/:
total 16
-rw------- 1 user user 570 Nov 14 02:37 ca.pem
-rw------- 1 user user 143 Nov 14 02:37 config
-rw------- 1 user user 116 Nov 14 02:37 emulator_credentials
-rw------- 1 user user 552 Nov 14 02:37 truststore.p12
中身は別々です。emulator_credentials のアクセスキーIDは2つで違いました(先頭の12文字だけを載せています)。シークレットアクセスキーは表示せずに sha256 のハッシュの先頭だけを比べ、証明書 ca.pem はフィンガープリントの先頭を比べました。どちらも一致しません。
$ grep -h aws_access_key_id ~/.aws/emulator/9297/emulator_credentials ~/.aws/emulator/9300/emulator_credentials
aws_access_key_id = EMULATOR9A2F…(以下略)
aws_access_key_id = EMULATOR5911…(以下略)
$ for p in 9297 9300; do echo -n "$p secret の sha256: "; grep aws_secret_access_key ~/.aws/emulator/$p/emulator_credentials | sha256sum | cut -c1-16; done
9297 secret の sha256: 9b6f022a8bc4ff0a
9300 secret の sha256: df518b335338355c
$ for p in 9297 9300; do echo -n "$p ca.pem: "; openssl x509 -in ~/.aws/emulator/$p/ca.pem -noout -fingerprint -sha256 | cut -c1-40; done
9297 ca.pem: sha256 Fingerprint=D9:04:5A:88:CF:71:DE:
9300 ca.pem: sha256 Fingerprint=1D:A7:B5:F1:E2:B1:2B:
config には、それぞれのポートの接続先と証明書の場所が書かれていました(ホームディレクトリのパスは ~ に置き換えています)。
$ cat ~/.aws/emulator/9297/config ~/.aws/emulator/9300/config
[default]
region = us-east-1
endpoint_url = https://127.0.0.1:9297
ca_bundle = ~/.aws/emulator/9297/ca.pem
s3 =
signature_version = s3v4
[default]
region = us-east-1
endpoint_url = https://127.0.0.1:9300
ca_bundle = ~/.aws/emulator/9300/ca.pem
s3 =
signature_version = s3v4
端末ごとに、そのポートの環境変数を設定する
公式ガイドにある、SDK の接続先をエミュレーターに向ける手順では、9297 以外のポートを使うときは、各値の 9297 を書き換えるよう案内しています。エミュレーター B に繋ぐ端末では、次のように 9300 にしました。エミュレーター A の端末では、同じ内容を 9297 で設定します。export AWS_PAGER= の行は、AWS CLI の出力をページャーで開かずに表示するために足したものです。
export AWS_ENDPOINT_URL=https://127.0.0.1:9300
export AWS_SHARED_CREDENTIALS_FILE=~/.aws/emulator/9300/emulator_credentials
export AWS_CONFIG_FILE=~/.aws/emulator/9300/config
export AWS_CA_BUNDLE=~/.aws/emulator/9300/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
export AWS_PAGER=
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN AWS_PROFILE
検証では、2つの端末の代わりに、この内容を env-9297.sh と env-9300.sh というファイルにして、コマンドごとに読み込ませました。以下の出力では、A の端末で実行したものに [端末A]、B の端末で実行したものに [端末B] を付けています。
aws sts get-caller-identity で確かめると、どちらも同じユーザーID、同じ ARN が返りました(12桁のアカウントIDは置き換えています)。リファレンスにも、どのエミュレーターも1つの同じアカウントとして振る舞うと書かれています。この応答だけでは、どちらのエミュレーターに繋がっているかは見分けられません。
[端末A] $ aws sts get-caller-identity --query "[UserId, Arn]" --output text
AIDAH5I7GEMO7ADXNFAZU arn:aws:iam::<12桁のアカウントID>:user/emulator
[端末B] $ aws sts get-caller-identity --query "[UserId, Arn]" --output text
AIDAH5I7GEMO7ADXNFAZU arn:aws:iam::<12桁のアカウントID>:user/emulator
片方で作ったテーブルは、もう片方からは見えない
エミュレーター A で DynamoDB のテーブル Cats を作り、項目を1件書き込みました(create-table の出力は --query でテーブル名だけにしています)。
[端末A] $ aws dynamodb create-table --table-name Cats --attribute-definitions AttributeName=id,AttributeType=S --key-schema AttributeName=id,KeyType=HASH --billing-mode PAY_PER_REQUEST --query TableDescription.TableName --output text
Cats
[端末A] $ aws dynamodb wait table-exists --table-name Cats
[端末A] $ aws dynamodb put-item --table-name Cats --item '{"id": {"S": "tama"}, "name": {"S": "タマ"}}'
[端末A] $ aws dynamodb list-tables
{
"TableNames": [
"Cats"
]
}
A を動かしたまま、エミュレーター B でテーブルの一覧を取ると、空でした。Cats を名前で指定しても、見つからないというエラーになります。
[端末B] $ aws dynamodb list-tables
{
"TableNames": []
}
[端末B] $ aws dynamodb describe-table --table-name Cats
aws: [ERROR]: An error occurred (ResourceNotFoundException) when calling the DescribeTable operation: Requested resource not found: Table: Cats not found
逆向きも同じです。エミュレーター B でテーブル Dogs を作ると、B の一覧には Dogs だけが、A の一覧には Cats だけが出ました。
[端末B] $ aws dynamodb create-table --table-name Dogs --attribute-definitions AttributeName=id,AttributeType=S --key-schema AttributeName=id,KeyType=HASH --billing-mode PAY_PER_REQUEST --query TableDescription.TableName --output text
Dogs
[端末B] $ aws dynamodb wait table-exists --table-name Dogs
[端末B] $ aws dynamodb list-tables
{
"TableNames": [
"Dogs"
]
}
[端末A] $ aws dynamodb list-tables
{
"TableNames": [
"Cats"
]
}
同じ名前の S3 バケットを、両方に作れる
本物の Amazon S3 では、バケットの名前のルールによると、共有のグローバルな名前空間に作る汎用バケットの名前は、同じパーティションの中のすべてのリージョン、すべての AWS アカウントで重複できません。2つのエミュレーターでは、同じ名前のバケット neko-note-demo を両方に作れました。
[端末A] $ aws s3api create-bucket --bucket neko-note-demo
{
"Location": "/neko-note-demo",
"BucketArn": "arn:aws:s3:::neko-note-demo"
}
[端末B] $ aws s3api create-bucket --bucket neko-note-demo
{
"Location": "/neko-note-demo",
"BucketArn": "arn:aws:s3:::neko-note-demo"
}
A のバケットにだけ hello.txt を置くと、B の同じ名前のバケットは空のままでした。aws s3 ls は何も出さず、行数を数えると 0 です。
[端末A] $ echo "from A" | aws s3 cp - s3://neko-note-demo/hello.txt
[端末A] $ aws s3 ls s3://neko-note-demo/
2026-11-14 02:37:18 7 hello.txt
[端末B] $ aws s3 ls s3://neko-note-demo/; echo "(行数: $(aws s3 ls s3://neko-note-demo/ | wc -l))"
(行数: 0)
B にも同じキーで別の中身を置いてから読み出すと、それぞれ自分の中身が返りました。
[端末B] $ echo "from B" | aws s3 cp - s3://neko-note-demo/hello.txt
[端末A] $ aws s3 cp s3://neko-note-demo/hello.txt -
from A
[端末B] $ aws s3 cp s3://neko-note-demo/hello.txt -
from B
鍵や証明書を取り違えると、繋がらない
A の端末の環境変数のまま、接続先と証明書だけを B に向けて送ってみました。鍵は A のものです。3つのサービスとも断られましたが、エラーの種類はサービスによって違いました。
[端末A] $ export AWS_ENDPOINT_URL=https://127.0.0.1:9300 AWS_CA_BUNDLE=~/.aws/emulator/9300/ca.pem; aws dynamodb list-tables
aws: [ERROR]: An error occurred (UnrecognizedClientException) when calling the ListTables operation: The security token included in the request is invalid.
[端末A] $ export AWS_ENDPOINT_URL=https://127.0.0.1:9300 AWS_CA_BUNDLE=~/.aws/emulator/9300/ca.pem; aws sts get-caller-identity
aws: [ERROR]: An error occurred (InvalidClientTokenId) when calling the GetCallerIdentity operation: The security token included in the request is invalid.
[端末A] $ export AWS_ENDPOINT_URL=https://127.0.0.1:9300 AWS_CA_BUNDLE=~/.aws/emulator/9300/ca.pem; aws s3api list-buckets
aws: [ERROR]: An error occurred (AccessDenied) when calling the ListBuckets operation: Sign the request with the emulator credentials
リファレンスには、鍵は生成したその起動でしか使えず、別のポートのエミュレーターでは使えないと書かれています。受け付けない鍵で署名したときのエラーとして表に載っているのは、AccessDenied と「Sign the request with the emulator credentials」というメッセージです。検証環境(1.0.353)でその形になったのは S3 だけで、DynamoDB と STS では、鍵が無効だという、それぞれのサービスのエラーが返りました。接続先の取り違えをエラーコードで見つけたいときは、AccessDenied だけでなく、サービスごとのエラーも返ることに気をつけてください。
証明書を取り違えた場合は、リクエストを送る前の TLS の検証で止まります。B の端末の環境変数のまま、AWS_CA_BUNDLE だけを A の ca.pem にすると、証明書を確かめられないというエラーになりました。終了コードは 255 です。
[端末B] $ export AWS_CA_BUNDLE=~/.aws/emulator/9297/ca.pem; aws dynamodb list-tables
aws: [ERROR]: SSL validation failed for https://127.0.0.1:9300/ [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate (_ssl.c:1082)
片方を止めても、もう片方のデータは残る
エミュレーター A に SIGTERM を送って止めました(終了コードは 0)。~/.aws/emulator/9297/ のフォルダーは残りましたが、中のファイルは0個になりました。B のフォルダーの4つのファイルはそのままです。
$ ls ~/.aws/emulator/; ls -A ~/.aws/emulator/9297/ | wc -l; ls ~/.aws/emulator/9300/
9297
9300
0
ca.pem
config
emulator_credentials
truststore.p12
A の端末から送ろうとすると、鍵のファイルがもうないので、AWS CLI は送る前に、認証情報が見つからないというエラーで止まります(--cli-connect-timeout 3 は、接続を待つ時間を3秒にする指定です)。B の端末では、Dogs のテーブルも、hello.txt の中身も、そのまま読めました。
[端末A] $ aws dynamodb list-tables --cli-connect-timeout 3
aws: [ERROR]: An error occurred (NoCredentials): Unable to locate credentials. You can configure credentials by running "aws login".
[端末B] $ aws dynamodb list-tables
{
"TableNames": [
"Dogs"
]
}
[端末B] $ aws s3 cp s3://neko-note-demo/hello.txt -
from B
A を aws-emulator -t off で同じポートにもう一度起動すると、テーブルもバケットもない、空の状態から始まりました。そのあいだも、B の Dogs は残っていました。
[端末A] $ aws dynamodb list-tables
{
"TableNames": []
}
[端末A] $ aws s3api list-buckets --query "Buckets[].Name"
[]
[端末B] $ aws dynamodb list-tables
{
"TableNames": [
"Dogs"
]
}
テストごとに片方だけを起動し直して、空の状態に戻すこともできます。リファレンスによると、エミュレーターには状態を消すための操作がなく、空にするには起動し直します。
ポートを決めずに、空いているポートで起動する
CI のように、どのポートが空いているか分からない場所では、-p 0 を付けるとオペレーティングシステムが空いているポートを選びます。Linux での手順のうち、ポートを変える節では、選ばれたポートを起動ログの endpoint_url から読むよう案内しています。リファレンスでも、このログの行は行の位置ではなく message の値で探すよう書かれています。検証環境では、起動ログをファイルに書き、Python でその行の endpoint_url を取り出しました(ログのファイルのパスは短くし、ログの行が出るまで待つ部分は省いています)。
$ aws-emulator -t off -p 0 > emu-auto.log 2>&1 &
$ grep 'emulator server information' emu-auto.log | python3 -c 'import sys, json; print(json.loads(sys.stdin.readline())["fields"]["endpoint_url"])'
https://127.0.0.1:43633
$ ls ~/.aws/emulator/
43633
9297
9300
フォルダーも、選ばれたポートの番号で作られました。公式ガイドでは、環境変数の各値に、そのエミュレーターのポートを使うよう案内しています。
公式ドキュメントにある制限と料金
この節は公式ドキュメントの記述をまとめたもので、検証環境では確かめていません。
- リージョンとアカウント:AWS Emulator の制限事項によると、1つのエミュレーターは、リクエストにあるリージョンやアカウントIDでリソースを分けません。複数のリージョンを試すときは、リージョンごとに別のポートでエミュレーターを動かすよう書かれています。
- 本物と比べている範囲:AWS がエミュレーターの動きを本物と比べているのは、米国西部(オレゴン、
us-west-2)リージョンです。ただし、グローバルなサービスは米国東部(バージニア北部、us-east-1)で比べています。 - 料金:エミュレーターに追加の料金はかかりません。料金がかかるのは、AWS にデプロイしたときの AWS のリソースです。
- 使いみち:開発とテストのためのもので、本番のワークロードには使わないよう書かれています。AWS では、長く動かし続ける使い方や負荷テストを試していないとしています。
Docker で2つ動かすときは、Docker での手順にあるとおり、Docker の -p のホスト側とコンテナー側、エミュレーターの -p の3か所すべてでポートを変えます。-p 127.0.0.1:9300:9297 のようにホスト側だけを変えると、2つのコンテナーが ~/.aws/emulator の同じフォルダーに書き込み、互いのファイルを置き換えたり消したりすると書かれています。また、コンテナーの中では -p 0 を使わないよう案内されています。




