こんにちは。
トライベック株式会社の岡山です。
「Next.js + Cognito + API Gateway v2 + RDS」というよくある AWS 構成を、Mac の手元でひととおり動かせるローカル環境としてとりあえず組んでみました。
LocalStack Community では Cognito と API Gateway v2 が動かず詰んでいたのですが、floci という GraalVM 製のエミュレータに乗せ換えたら無料のまま要件を満たせたので、その構成と詰まりどころをまとめます。
TL;DR
- Docker ランタイムは Colima。
brew install一発で入り、make setupから自動起動できるのでチーム導入の摩擦が少ない - AWS API エミュレータは floci(
hectorvent/floci)。Cognito User Pool / API Gateway v2 / Secrets Manager が無料で動くのが採用の決め手 - Terraform は
endpointsを差し替えるだけでlocal / dev / prodを同じモジュールで運用。ローカル専用 IaC は書かない - エミュレートできない CloudFront / WAF / Route53 / Bedrock は
enabled=falseで素通りさせ、ローカルでは作らない方針に倒した - 検証環境は MacBook Pro 14(M4 / 32GB)+ macOS Tahoe 26.2。Colima には
--cpu 4 --memory 8 --disk 60を割り当てています
はじめに
想定した構成は「Next.js 14(static export)を S3 + CloudFront で配信、Cognito + API Gateway HTTP API v2 + Lambda で API、データは RDS MySQL / S3 / SQS / Secrets Manager」というよくあるパターンです。
特定のプロジェクトに紐づけたわけではなく、この一式が手元で完結する箱を一度ちゃんと作っておきたいというのが今回のモチベーションでした。クラウドに当てずに動作確認だけしたい場面が地味に多いので、検証用の素振り環境として組んでおく価値はあるはず、という感覚です。
最初は LocalStack Community で組もうとしたのですが、構成の中核となる Cognito User Pool / API Gateway v2 / Secrets Manager がいずれも Pro 限定で詰みました。
そこで無料のまま同じ要件を満たせる floci に切り替え、Colima と Terraform を組み合わせて local / dev / prod を 1 セットのモジュールで動かす形に落ち着いたので、その記録です。
特に「Cognito からトークンを取って API Gateway v2 の JWT Authorizer を素通しさせる」までの一連の流れを手元で再現したい方の参考になれば、という想定で書いています。
検証環境
| 区分 | 内容 |
|---|---|
| マシン | MacBook Pro 14インチ(2024年11月モデル) |
| チップ | Apple M4 / メモリ 32 GB |
| OS | macOS Tahoe 26.2 |
| Colima | colima start --cpu 4 --memory 8 --disk 60 |
| Docker CLI / Compose | Homebrew 版 |
| Terraform | 1.6.0 以上 / AWS Provider ~> 5.50
|
| AWS CLI | v2 系(AWS_ENDPOINT_URL 対応版) |
| floci | hectorvent/floci:latest |
| MySQL |
mysql:8.0(RDS 代替として docker-compose で起動) |
| Next.js / Amplify | 14.2 / aws-amplify@^6.6.0
|
floci + MySQL + Next.js + Terraform を同時に動かしてもピーク 5〜6GB 程度で、Colima に 8GB 割り当てておけば普段の IDE と並行しても余裕がありました。
1. Colima を選んだ理由
Docker Desktop ではなく Colima にした理由は、技術というより導入のしやすさの面が大きいです。
✅ ライセンスを気にせず brew install colima docker docker-compose で入れられる
✅ make setup で colima status を見て未起動なら colima start を呼ぶだけで済む
✅ --cpu 4 --memory 8 --disk 60 を宣言的に揃えられるので、別マシンで再現しても挙動が揃いやすい
Colima 自体は「lima(macOS 用 Linux VM ランナ)に Docker / containerd / Kubernetes を載せるラッパ」ですが、今回は素の Docker ランタイムとしてだけ使っています。
brew install colima docker docker-compose
colima start --cpu 4 --memory 8 --disk 60
make setup の中身は、必要ツールの存在確認と Colima 自動起動だけです。
.PHONY: setup
setup: ## 必要ツールの存在確認と colima 起動
@command -v colima >/dev/null || (echo "✗ colima not found"; exit 1)
@command -v docker >/dev/null || (echo "✗ docker not found"; exit 1)
@command -v terraform >/dev/null || (echo "✗ terraform not found"; exit 1)
@command -v aws >/dev/null || (echo "✗ aws cli not found"; exit 1)
@if ! colima status >/dev/null 2>&1; then \
echo "colima is not running. Starting..."; \
colima start --cpu 4 --memory 8 --disk 60; \
else \
echo "✓ colima is running."; \
fi
@[ -f .env ] || cp .env.example .env
この一発で起動まで持っていけるようにしておくと、後で他のマシンに移したときも make setup だけで再現できて気が楽です。
2. floci を採用した理由 ― LocalStack Community との比較
floci は GraalVM ネイティブイメージで動く AWS API エミュレータで、LocalStack 互換のエンドポイント(既定で http://localhost:4566)を提供してくれます。
今回触りたかったサービスを LocalStack Community / Pro と並べてみると、無料で組みたい前提だと選択肢がほぼ floci 一択になりました。
| 観点 | floci(無料) | LocalStack Community | LocalStack Pro |
|---|---|---|---|
| Cognito User Pool | ○ | ✗ | ○ |
| API Gateway v2 (HTTP API) | ○ | ✗ | ○ |
| Secrets Manager | ○ | △ | ○ |
| KMS | ○ | △ | ○ |
| S3 / SQS | ○ | ○ | ○ |
| 起動時間 | 〜24ms | 数〜十数秒 | 同左 |
| 常駐メモリ | 〜13MiB | 数百MB〜 | 同左 |
| 商用利用 | 無料 | 無料 | 有償 |
GraalVM ネイティブイメージなのでとにかく軽く、make up から数秒でローカル AWS が立ち上がるのは試行錯誤の回転を速くしてくれます。
docker-compose.yml の中心はこれだけです。
services:
floci:
image: hectorvent/floci:${FLOCI_VERSION:-latest}
container_name: aws-local-floci
restart: unless-stopped
ports:
- "4566:4566"
- "4510-4559:4510-4559"
environment:
- FLOCI_STORAGE_MODE=hybrid
- FLOCI_DEFAULT_REGION=ap-northeast-1
- FLOCI_SERVICES=s3,sqs,apigateway,apigatewayv2,lambda,iam,kms,secretsmanager,acm,cloudformation,rds,dynamodb,sts,logs,cloudwatch,events
- FLOCI_DATA_DIR=/var/lib/floci
volumes:
- floci-data:/var/lib/floci
- /var/run/docker.sock:/var/run/docker.sock
- ./scripts/init-aws:/etc/floci/init/ready.d
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:4566/_floci/health"]
interval: 5s
timeout: 3s
retries: 30
押さえておきたい挙動は3つです。
✅ FLOCI_STORAGE_MODE=hybrid でホットデータはメモリ・コールドデータはディスク。make down してもデータが残る
✅ init/ready.d に shell スクリプトを置けば起動後に自動実行される。CI でも同じパスで初期データを投入できる
✅ /var/run/docker.sock をマウントしているのは、floci の RDS が内部で MySQL/Postgres コンテナを立てるため
ヘルスチェックは LocalStack 互換とは別の独自パス /_floci/health です。
ここを ready 判定に使わないと、STS が応答していても S3/SQS の初期化が終わっていないタイミングを掴んでしまうことがありました。
3. Terraform を local / dev / prod で同じモジュールに揃える
ここがこの構成のいちばんの肝で、ローカル専用の IaC は書かない方針に倒しました。
やっていることは、local 環境の providers.tf で endpoints を floci に向けるだけです。
provider "aws" {
region = var.region
access_key = "test"
secret_key = "test"
s3_use_path_style = true
skip_credentials_validation = true
skip_metadata_api_check = true
skip_requesting_account_id = true
endpoints {
s3 = var.endpoint_url
sqs = var.endpoint_url
apigateway = var.endpoint_url
apigatewayv2 = var.endpoint_url
lambda = var.endpoint_url
iam = var.endpoint_url
sts = var.endpoint_url
acm = var.endpoint_url
rds = var.endpoint_url
kms = var.endpoint_url
secretsmanager = var.endpoint_url
cloudwatch = var.endpoint_url
cloudformation = var.endpoint_url
logs = var.endpoint_url
dynamodb = var.endpoint_url
events = var.endpoint_url
cognitoidp = var.endpoint_url
}
}
dev / prod の providers.tf は、上記の endpoints ブロックを丸ごと外したシンプル版です。
モジュール側のコードは一切触りません。
エミュレートできないサービスは、ラッパーモジュールで count = var.enabled ? 1 : 0 パターンを使って local だけ素通りさせています。
module "cloudfront" {
source = "../../modules/cloudfront"
enabled = false # ← local では作らない
origin_bucket = module.s3.bucket_assets_id
}
module "cognito" {
source = "../../modules/cognito"
enabled = true # ← local でも本番でも作る
user_pool_name = "${var.project}-users"
}
| サービス | local(floci) | dev / prod(AWS) |
|---|---|---|
| S3 / SQS / DynamoDB | ✓ | ✓ |
| Cognito User Pool | ✓ | ✓ |
| API Gateway v2 + JWT Authorizer | ✓ | ✓ |
| RDS MySQL | △(compose の MySQL を直接) | ✓ |
| CloudFront / WAF / Route53 | ✗(enabled=false) |
✓ |
| CodePipeline / CodeBuild | ✗ | ✓ |
| Bedrock | ✗(mock を別コンテナで) | ✓ |
「ローカルでは作れないものは作らない、ただしモジュール構成は揃える」と割り切った瞬間に、IaC の運用がだいぶ楽になりました。
4. ハンズオン:0 から Cognito 越しに API を叩くまで
実際に手を動かす流れです。.env で AWS_ENDPOINT_URL=http://localhost:4566 を流しておくのがこの構成の前提になります。
brew install colima docker docker-compose terraform awscli jq
git clone <this-repo> && cd <this-repo>
cp .env.example .env
make setup # ツール存在チェック + colima 起動
make up # docker compose up -d → wait-for-floci → bootstrap-aws
make tf-init ENV=local
make tf-apply ENV=local
make cognito-login # テストユーザを作って IdToken / AccessToken を取得
ID_TOKEN="..."
make aws ARGS="apigatewayv2 get-apis"
curl -s -H "Authorization: Bearer $ID_TOKEN" \
http://localhost:4566/restapis/<api_id>/dev/_user_request_/protected
make aws は aws --endpoint-url=http://localhost:4566 を被せた薄いラッパなので、awscli の使い勝手は本番と同じです。
フロントエンド(Next.js + Amplify v6)と繋ぐ場合も、terraform output から Cognito の User Pool ID / Client ID を .env.local に流して npm run dev するだけで http://localhost:3000 で動きます。
Amplify 側の設定は標準のまま、何も特殊なことをしていないまま手元だけで動くのが、今回組んでみていちばん気持ちよかった点でした。
5. ハマりどころ
実際に組んでいて踏んだ落とし穴を、後続の自分のために残しておきます。
-
API Gateway v2 の JWT Authorizer は
accessTokenではなくidTokenを渡す。audience = Client IDで検証されるので、accessTokenを投げるとinvalid_tokenで 401 が返ります。1時間悩みました -
floci の Cognito は SRP が弱い。
USER_SRP_AUTHでNotAuthorizedExceptionが出ることがあるので、ローカル限定でALLOW_USER_PASSWORD_AUTHを許可するほうが安定します -
S3 は
s3_use_path_style = trueを必ず指定する。忘れるとbucket.localhost:4566を引きに行って DNS 失敗。これは2回やらかしました -
floci の RDS はエミュレートさせず compose の
mysqlを直接使うほうが速くて安定。アプリはlocalhost:3306を見ればよく、RDS のエンドポイントは Terraform output から差し込むのは dev/prod のときだけにしました -
ready 判定は
/_floci/healthを見る。sts get-caller-identityを ready 判定に使うと、S3/SQS の初期化が間に合わずに bootstrap が空振りすることがあります -
CloudFront / WAF / Route53 / Bedrock は割り切ってエミュレートしない。やろうとして時間を溶かすより、
enabled=falseで素通しにして本番でだけ確認するほうが結果的に速かったです
6. 補足:Bedrock の代替
Amazon Bedrock は floci でもエミュレートされないため、aws-samples/bedrock-access-gateway を OpenAI 互換 API として別プロファイルで起動する運用にしました。
bedrock-mock:
image: ghcr.io/aws-samples/bedrock-access-gateway:latest
profiles: ["bedrock"]
ports: ["8000:8000"]
environment:
- API_KEY=${BEDROCK_MOCK_API_KEY:-local-dev-key}
make up PROFILES=bedrock のときだけ起動するようにして、普段の起動には影響しないようにしています。
profile を分けるかどうかで「とりあえず立ち上げる」ときの軽さがけっこう変わるので、最初に決めておくと後悔が少ないです。
おわりに
特定のプロジェクト用に詰めたわけではなく、「Cognito + API Gateway v2 + S3/SQS/RDS + Next.js」を手元で一通り動かせる素振り環境として組んでみた、というのが今回の位置づけでした。
それでも、Cognito のサインインから API Gateway v2 の JWT Authorizer 通過までを何度でもタダで叩けるようになっただけで、想像以上に検証のハードルが下がったという印象です。
今後は具体的な開発ループに乗せてみてから足りない部分を足していきたいですし、Bedrock 周りも mock の精度をどこまで詰められるか引き続き試していきたいと思います。
参考リンク
- floci: https://github.com/floci-io/floci / https://floci.io/
- Colima: https://github.com/abiosoft/colima
- Terraform AWS Provider: https://registry.terraform.io/providers/hashicorp/aws/latest
- aws-samples/bedrock-access-gateway: https://github.com/aws-samples/bedrock-access-gateway