Claude Codeに新規AWS構築を任せたら意外とローリスク
はじめに
アプリにLLMによる分析機能を追加するため、既存WebアプリからAWS上のAPIを呼び出す構成を新規に立てる必要がありました。
都合上、AWSアカウントも新規発行する前提だったので、思い切ってVPC設計からCloudFront / WAF構築まで、全部Claude Codeに任せて完成させたという体験談です。
結論を先に言うと、設計指示書 (INFRA.md) を最初に書いてからClaude Codeに渡せば、新規アカウントならIAMスコープとSSM経由運用でローリスク、残るリスクはコストだけでした。
「新規アカウント × Claude Code」はローリスクと言えるのか
以下4点によりリスクを下げたと考えています。
1. 既存リソース破壊リスクがゼロ
新規アカウントなのでVPC削除すら気軽です。
本番アカウントなら delete-vpc 1行も躊躇するところですが、新規ならそれが「正しい初手」になります。
2. IAMをプロジェクトprefixでスコープ限定
最初の数コマンドが AccessDenied で落ちたのを契機に、myapp-ai-* のロール / インスタンスプロファイルだけ触れるカスタム管理ポリシー (myapp-ai-iam-management) をConsoleから作成し、Claude Codeが叩くIAMユーザにアタッチしました。
{
"Sid": "PassMyAppAiRoleToEc2Only",
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::111111111111:role/myapp-ai-*",
"Condition": {"StringEquals": {"iam:PassedToService": "ec2.amazonaws.com"}}
}
これにより、Claudeがprefix外のリソースを触ろうとしても自動で弾かれる「安全装置」となります。
3. SSHポート全閉 + SSM Session Manager経由
22/443を一切開放しない方針を INFRA.md 冒頭に明記し、EC2上の作業は全部 aws ssm send-command で AWS-RunShellScript documentに投入する形にしました。
副次効果として秘密鍵をtranscriptに晒さない運用が前提から自然に出てくるのが大きいです。
4. AWS_PROFILE 事前export、--profile 引数禁止
Claude Codeのサブシェルにユーザーシェルの環境変数は継承されないので、AWS_PROFILE=my-aws-profile_poweruser を各コマンドに前置する運用にしました。
--profile 引数は禁止です。
AWS_PROFILE=my-aws-profile_poweruser aws sts get-caller-identity
これで別アカウントに誤爆する経路を塞げます。
INFRA.md駆動の進め方
本構築の核は、AWSリソースを作る前に INFRA.md という設計指示書を書いたことです。
中身はざっくり以下:
- 目的: どのサーバから何へHTTPSで繋ぐか(別クラウドWeb → AWS API)
- 採用構成: 別クラウドWeb → CloudFront → VPC Origin → private EC2 → API process
-
セキュリティ方針:
- CloudFrontを唯一の公開入口とする
- WAFをCloudFrontに紐付ける
- WAF IP setで許可送信元のみAllow
- WAFのdefault actionはBlock
- CloudFrontからoriginへCustom Headerを付与
- EC2側のNginxでそのCustom Headerを検証
- Custom Header不一致 / 未指定なら403
- CloudFront cacheは無効化
- CloudFront / WAF / APIのログを有効化
- WAF / CloudFront / SG / Nginxそれぞれの個別設定要件
- 実装・確認タスクリスト
- 明示すべきリスク(CloudFront自体はpublic endpointである、Header漏洩リスク、Headerのローテーション設計が必要 …)
これをClaude Codeの作業ディレクトリ直下に置いて「これに沿って構築して」と渡すだけです。
効果は思った以上に大きく、たとえばWAFのルール順、X-Forwarded-For を信用しない設計、cache無効化、Header値をログに出さない、といった要件について指示書と矛盾する選択肢を勝手に却下してくれるようになります。
「ALB立てましょうか」とは言ってこなくなります。
レビュー側も「方針への逸脱の有無」だけ見れば良いので、コード差分レビューと同じ感覚でIaCオペレーションを承認できました。
構築フェーズ1: 新規VPC 〜 EC2 〜 Bedrock疎通まで
まず「EC2をprivate subnetに立ててBedrockを叩けるところまで」を作ります。
やったことは以下で、すべてClaudeに任せました。
-
sts get-caller-identityでprofile確認 - デフォルトVPCを削除(残存リソース確認込み)
- VPC + private subnet + NAT GW + VPC endpoint(
bedrock-runtime+ S3 gateway)を作成 - IAM Role + Instance Profile + Bedrock InvokeModel inline policy
- SG作成(inbound初期全空 / outboundデフォルト)
- EC2起動(t3.small / AL2023 / Public IPなし / IMDSv2強制 / gp3 30GB encrypted)
- SSM Online確認 →
bedrock-runtime invoke-modelで疎通 - アプリを
/opt/myapp_aiにclone →uv sync→ systemd起動 →/healthz200(GitHub Deploy key登録のみ人手)
ハマりどころ: Bedrock inference profile IDパターン不一致
最初のinvokeが AccessDenied で落ちたのですが、
IAM Resourceを inference-profile/jp.anthropic.claude-sonnet-4-6-* と末尾ハイフン + * で書いていたところ、
AWS側の短縮ID jp.anthropic.claude-sonnet-4-6 と一致しなかったのが原因でした。
...-6* に直して解決。Claudeが list-inference-profiles で実IDを確認 → policy修正 → IMDS credential cache反映を待ってリトライ、までを自走しました。
構築フェーズ2: CloudFront / WAF / Nginxを載せる
EC2が /healthz を返せるようになったら、INFRA.md の「CloudFront唯一の公開入口 / WAF default Block / Custom Header検証 / origin直接到達不可」を成立させるためにCloudFront周りを載せます。
- CloudFront VPC Origin (2024-11 GA)をEC2に直接当てる。ALB不要
- ACM (us-east-1) をDNS検証で発行 → CloudFrontに紐付け
- WAF: default Block / IP setに別クラウドWebサーバの固定IPのみAllow
-
Nginx: CloudFront Custom Origin Headerを
mapディレクティブで検証、不一致は403。Header名と値はSecrets Managerから取得しシェル変数経由で注入、transcriptとnginx access logの両方に出さない
コスト面での留意点(残リスク)
ローリスクと言いつつ、コストのリスクは新規アカウントでも残ります。
本構築で実際に判断した部分です。
| リソース | 月額目安(東京) | 対策 |
|---|---|---|
| NAT Gateway | $32 + $4.5/100GB | 必須なので受け入れ。BedrockはVPC endpoint経由にしてNATを回避 |
| VPC Interface Endpoint | 1個 $10.2 + $0.01/GB | bedrock-runtimeのみ常設、SSMトリオは削除しNAT経由にフォールバック |
| EIP | 未紐付け $3.6 | 構成組み直しでrelease忘れに注意 |
| EC2 t3.small | $19 (24/7) | 検証フェーズ完了時はstopで時間課金を止める |
| CloudFront VPC Origin機能料 | $0 | 従量課金プランなら追加料金なし($200/月Businessはflat-rate選択時のみ) |
| CloudFront / WAF | 従量課金 | STG規模では無視できるレベル |
実際にやったコスト最適化
当初はVPC endpointをフルセット(bedrock-runtime / ssm / ssmmessages / ec2messages)で立てて月 ~$94。STGではここまで必要ないと判断し、SSMトリオを削除してNAT経由にフォールバックしました(SDK / コード変更ゼロ)。
結果月 ~$64、約 -$30/月です。bedrock-runtimeのホットパスだけprivateを維持しました。ロールバックも同SG / 同subnetで再作成するだけで無停止です。
TIPS
検証で使わない時間帯は「止めていい資源は明示的に止めて」とClaudeに依頼する運用にしました。
EC2を stop → 必要時に start-instances で再開、という流れです。
/HANDOVER等と組み合わせると相性が良いでしょう。
Claudeに任せない(任せられない)ライン
外部システムが絡む4つは、結局人間が手を動かす必要がありました。これは新規アカウント前提でも変わりません。
- GitHubのDeploy key登録: 鍵生成まではClaude、登録だけ人間(2FAや組織ポリシーの壁)
-
レジストラのDNSレコード追加: ACM検証CNAMEと、サービス用CNAMEの2種。Claudeが値を吐く → 人間が貼る →
acm wait certificate-validatedで確認 -
Bedrock Anthropic use case form提出: Console GUIフォーム。Claudeは「これが理由で
ResourceNotFoundException: Model use case details have not been submittedを返してる」と特定するところまではやってくれましたが、提出ボタンは人間が押す必要があります
「Claudeは完璧」ではなく、Claudeが判断 / 試行錯誤を担当して、人間はGUIフォームと外部システムへの最終投入だけ担当するという分業を達成できました。
まとめ
-
INFRA.md(設計指示書)→ Claude Code → AWS CLIの流れは、新規アカウントなら比較的ローリスクで回る - 残るリスクはコスト。NAT / VPC endpoint / EIP / EC2稼働時間 などには意識が必要。
- Claudeは
AccessDenied/ 404 / 403 を切り分けて「IAMの問題か、アカウント側の問題か、AWSの時間問題か」を区別できます。これがエージェントに任せる最大のメリット。 - もしAIエージェントにAWS構築を試させたいなら、プロジェクト用にAWSアカウントを1個切ることを強くお勧めします。何も壊れるものないからね。
Discussion