Claude CodeでAWSデプロイを自動化する実践ガイド:CDK・GitHub Actions・安全な運用
Claude Codeで小さなWeb APIをAWSへ安全にデプロイするCDK実装、CI/CD、IAM、ログ、費用対策を解説。
AWSへのデプロイをClaude Codeに任せるとき、いちばん危ないのは「それっぽい構成図」と「動かないIaC」で満足してしまうことです。実務では、IAM、シークレット、ロールバック、ログ、費用上限までそろって初めて運用できます。
この記事では、小さなWebアプリやAPIをAWSに出す最初の一歩として、API Gateway + Lambda + AWS CDK + GitHub Actionsを実装します。CDKは「Infrastructure as Code」、つまりAWS画面で手作業する内容をコード化する仕組みです。IAMは「誰が何をできるかを決める権限表」、OIDCは「GitHub Actionsが長期アクセスキーなしでAWSに一時ログインする仕組み」と考えると理解しやすいです。
Claude Codeの役割は、公式ドキュメントを前提に、コード生成、差分確認、失敗ログの読み取り、運用手順の文書化を高速化することです。Claude Code自体の概要はClaude Code公式ドキュメントで確認できます。AWS側の根拠はAWS CDKのNodejsFunction、API GatewayのLambdaプロキシ統合、Lambda環境変数、IAMベストプラクティスを参照してください。
どのAWS構成を選ぶべきか
最初にサービス選定を決めます。ここを曖昧にすると、Claude Codeも過剰な構成を作りがちです。
| 構成 | 向いている用途 | 良い点 | 注意点 |
|---|---|---|---|
| Lambda + API Gateway | 小さなAPI、問い合わせフォーム、Webhook、管理画面の軽いバックエンド | サーバー管理が少なく、低トラフィックで始めやすい | 長時間処理、常時接続、大きなコンテナには不向き |
| ECS/Fargate + ALB | Docker化済みAPI、常時動くバックエンド、バッチとAPIの混在 | コンテナの自由度が高く、既存アプリを移しやすい | VPC、ALB、タスク定義、スケール設計が増える |
| Amplify または S3 + CloudFront | 静的サイト、SPA、フロントエンド中心のアプリ | 配信が速く、CDNと相性が良い | API、認証、バックエンド処理は別設計が必要 |
この記事では、相談やMVPで最も多い「小さなAPIを安全に公開する」ケースに寄せて、Lambda + API Gatewayを選びます。ECS/Fargateの深掘りはClaude Code × AWS ECS Fargate、Lambda単体の基礎はClaude Code × AWS Lambda完全ガイド、IAM設計はClaude Code × AWS IAM権限設計ガイドも参考にしてください。
flowchart LR
User[利用者] --> Api[API Gateway]
Api --> Fn[Lambda Node.js API]
Fn --> Secret[Secrets Manager]
Fn --> Logs[CloudWatch Logs]
GitHub[GitHub Actions OIDC] --> CDK[AWS CDK deploy]
CDK --> Api
CDK --> Fn
実務で使う4つのユースケース
1つ目は、問い合わせフォームや資料請求フォームです。フロントエンドは既存のWebサイトに置き、送信先だけをAPI Gateway + Lambdaにします。APIキー、使用量プラン、CloudWatch Logsを付ければ、フォームスパムや障害の調査がしやすくなります。
2つ目は、SaaSのMVP APIです。最初からEKSや複雑なVPCを作らず、/v1/health、/v1/contact、/v1/webhookのような小さい境界で始めます。Claude Codeには「今はLambdaで十分。RDSやVPCは必要になるまで追加しない」と明示します。
3つ目は、社内ツールや運用Webhookです。Slack通知、CMS更新通知、軽い承認APIなどは、Lambdaの短い処理に合います。ただし、外部APIの待ち時間が長い処理や、キューで再実行したい処理はSQSやStep Functionsを足すべきです。
4つ目は、AWS/Claude Code導入相談でよく出る「既存の手動デプロイをCI/CD化したい」ケースです。ここでは、手作業のAWSコンソール変更をCDKに寄せ、GitHub ActionsからOIDCでデプロイします。長期アクセスキーをGitHub Secretsに置く運用は避けます。
Claude Codeに渡すプロジェクト指示
Claude Codeは強いですが、AWSでは自由に作らせるほど危険です。まずCLAUDE.mdに制約を書きます。
# AWS deployment rules
- Use AWS CDK v2 and TypeScript.
- Target region: ap-northeast-1 unless explicitly changed.
- Do not create VPC resources for this small API unless required.
- Prefer API Gateway + Lambda for the first release.
- Runtime secrets must come from AWS Secrets Manager, not plaintext env vars.
- Use IAM grants such as secret.grantRead(handler) instead of wildcard policies.
- Add CloudWatch log retention and reserved concurrency.
- Before deploy, run npm test, npx cdk synth, and npx cdk diff.
- Never paste AWS access keys into files or GitHub Secrets.
Claude Codeへの依頼は、成果物と禁止事項を同時に書きます。
このリポジトリにCDK v2のSmallApiStackを追加してください。
API Gateway REST API + Lambda Node.js 20で /v1/health と /v1/contact を作ります。
Secrets Managerから prod/claude-code-demo/api を読みます。
Lambda実行ロールは必要最小限にし、CloudWatch Logsの保持期間、予約同時実行、API Gatewayのスロットリングも入れてください。
デプロイ用GitHub ActionsはOIDCだけを使い、長期AWSキーは使わないでください。
編集後に npm test, npx cdk synth, npx cdk diff の結果を説明してください。
CDKプロジェクトを作る
ローカルにはNode.js、AWS CLI v2、AWS CDK CLI、認証済みのAWSプロファイルが必要です。Windowsの場合はGit BashかWSLで以下のBashコマンドを使うと、そのまま試しやすいです。
mkdir claude-code-aws-api
cd claude-code-aws-api
npx cdk init app --language typescript
npm install @aws-sdk/client-secrets-manager
npm install --save-dev esbuild @types/aws-lambda
mkdir -p lambda
aws sts get-caller-identity
初回だけ、Lambdaが読む設定をSecrets Managerに作ります。実サービスのAPIキーやDBパスワードは平文の環境変数ではなく、Secrets Managerに寄せます。AWSのLambda環境変数ドキュメントでも、認証トークンやAPIキーのような機密情報にはSecrets Managerの利用が推奨されています。
aws secretsmanager create-secret \
--name prod/claude-code-demo/api \
--secret-string '{"supportQueue":"aws-consulting"}' \
--region ap-northeast-1
Lambdaハンドラーを実装する
lambda/handler.tsを作ります。問い合わせ内容はログに残しますが、本文やシークレットを丸ごと出さない点が重要です。ログは便利ですが、個人情報の置き場ではありません。
import type {
APIGatewayProxyEvent,
APIGatewayProxyResult,
} from "aws-lambda";
import {
GetSecretValueCommand,
SecretsManagerClient,
} from "@aws-sdk/client-secrets-manager";
const secrets = new SecretsManagerClient({});
let cachedConfig: Record<string, string> | undefined;
async function loadConfig(): Promise<Record<string, string>> {
if (cachedConfig) return cachedConfig;
const secretArn = process.env.SECRET_ARN;
if (!secretArn) throw new Error("SECRET_ARN is not configured");
const result = await secrets.send(
new GetSecretValueCommand({ SecretId: secretArn }),
);
cachedConfig = JSON.parse(result.SecretString ?? "{}");
return cachedConfig;
}
function json(statusCode: number, body: unknown): APIGatewayProxyResult {
return {
statusCode,
headers: {
"content-type": "application/json",
"cache-control": "no-store",
},
body: JSON.stringify(body),
};
}
export async function handler(
event: APIGatewayProxyEvent,
): Promise<APIGatewayProxyResult> {
try {
if (event.httpMethod === "GET" && event.path.endsWith("/v1/health")) {
return json(200, {
ok: true,
stage: process.env.STAGE ?? "dev",
});
}
if (event.httpMethod === "POST" && event.path.endsWith("/v1/contact")) {
const body = JSON.parse(event.body ?? "{}") as {
email?: string;
message?: string;
};
if (!body.email || !body.message) {
return json(400, { ok: false, message: "email and message required" });
}
const config = await loadConfig();
const emailDomain = body.email.split("@")[1] ?? "unknown";
console.log(
JSON.stringify({
event: "contact_received",
emailDomain,
messageLength: body.message.length,
}),
);
return json(202, {
ok: true,
routedTo: config.supportQueue ?? "manual",
});
}
return json(404, { ok: false, message: "not found" });
} catch (error) {
console.error("handler_error", error);
return json(500, { ok: false, message: "internal error" });
}
}
CDKスタックを実装する
lib/small-api-stack.tsを置き換えます。NodejsFunctionはTypeScript/JavaScriptのLambdaをesbuildでバンドルできるCDK constructです。appSecret.grantRead(handler)を使うと、対象シークレットの読み取り権限だけがLambda実行ロールに付与されます。
import * as cdk from "aws-cdk-lib";
import * as apigateway from "aws-cdk-lib/aws-apigateway";
import * as iam from "aws-cdk-lib/aws-iam";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as nodejs from "aws-cdk-lib/aws-lambda-nodejs";
import * as logs from "aws-cdk-lib/aws-logs";
import * as secretsmanager from "aws-cdk-lib/aws-secretsmanager";
import { Construct } from "constructs";
import * as path from "path";
export class SmallApiStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const appSecret = secretsmanager.Secret.fromSecretNameV2(
this,
"AppSecret",
"prod/claude-code-demo/api",
);
const handler = new nodejs.NodejsFunction(this, "ApiHandler", {
functionName: "claude-code-small-api-prod",
entry: path.join(__dirname, "../lambda/handler.ts"),
handler: "handler",
runtime: lambda.Runtime.NODEJS_20_X,
architecture: lambda.Architecture.ARM_64,
memorySize: 256,
timeout: cdk.Duration.seconds(10),
logRetention: logs.RetentionDays.ONE_MONTH,
reservedConcurrentExecutions: 20,
environment: {
STAGE: "prod",
SECRET_ARN: appSecret.secretArn,
},
bundling: {
minify: true,
sourceMap: true,
externalModules: ["@aws-sdk/*"],
},
});
appSecret.grantRead(handler);
handler.addToRolePolicy(
new iam.PolicyStatement({
actions: ["cloudwatch:PutMetricData"],
resources: ["*"],
conditions: {
StringEquals: {
"cloudwatch:namespace": "ClaudeCodeLab/SmallApi",
},
},
}),
);
const api = new apigateway.RestApi(this, "SmallApi", {
restApiName: "claude-code-small-api",
cloudWatchRole: true,
deployOptions: {
stageName: "prod",
metricsEnabled: true,
loggingLevel: apigateway.MethodLoggingLevel.INFO,
dataTraceEnabled: false,
throttlingRateLimit: 20,
throttlingBurstLimit: 40,
},
});
const v1 = api.root.addResource("v1");
v1.addResource("health").addMethod(
"GET",
new apigateway.LambdaIntegration(handler),
);
v1.addResource("contact").addMethod(
"POST",
new apigateway.LambdaIntegration(handler),
{ apiKeyRequired: true },
);
const apiKey = api.addApiKey("ClientApiKey", {
apiKeyName: "claude-code-small-api-prod-client",
});
const usagePlan = api.addUsagePlan("BasicUsagePlan", {
throttle: { rateLimit: 10, burstLimit: 20 },
quota: { limit: 1000, period: apigateway.Period.MONTH },
});
usagePlan.addApiKey(apiKey);
usagePlan.addApiStage({ stage: api.deploymentStage });
new cdk.CfnOutput(this, "ApiUrl", { value: api.url });
}
}
bin/claude-code-aws-api.tsもスタック名を合わせます。
#!/usr/bin/env node
import * as cdk from "aws-cdk-lib";
import { SmallApiStack } from "../lib/small-api-stack";
const app = new cdk.App();
new SmallApiStack(app, "SmallApiProdStack", {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION ?? "ap-northeast-1",
},
});
デプロイ前に必ず差分を見る
CDKの初回利用ではbootstrapが必要です。bootstrapはCDKがデプロイ用のS3バケットやロールを作る準備作業です。
export AWS_REGION=ap-northeast-1
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
npx cdk bootstrap "aws://${AWS_ACCOUNT_ID}/${AWS_REGION}"
npm test
npx cdk synth
npx cdk diff SmallApiProdStack
npx cdk deploy SmallApiProdStack --require-approval never
cdk diffでIAMポリシー、API Gateway、Lambda名、ログ保持期間、予約同時実行を確認します。ここで「なぜこの権限が必要か」をClaude Codeに説明させると、不要なワイルドカードに気づきやすくなります。
動作確認とログ確認
デプロイ後はCloudFormation出力からAPI URLを取り、GETとPOSTを確認します。APIキーは本番認証の代わりではありませんが、使用量制限と簡易的なクライアント識別に役立ちます。
API_URL=$(aws cloudformation describe-stacks \
--stack-name SmallApiProdStack \
--query "Stacks[0].Outputs[?OutputKey=='ApiUrl'].OutputValue" \
--output text)
curl "${API_URL}v1/health"
KEY_ID=$(aws apigateway get-api-keys \
--name-query claude-code-small-api-prod-client \
--query "items[0].id" \
--output text)
API_KEY=$(aws apigateway get-api-key \
--api-key "$KEY_ID" \
--include-value \
--query "value" \
--output text)
curl -X POST "${API_URL}v1/contact" \
-H "content-type: application/json" \
-H "x-api-key: ${API_KEY}" \
-d '{"email":"masa@example.com","message":"AWS deployment consultation"}'
LambdaログはCloudWatch Logsに送られます。AWS公式ドキュメントにもある通り、ログ表示には数分かかることがあります。すぐ見えないだけで失敗と判断しないでください。
aws logs tail "/aws/lambda/claude-code-small-api-prod" \
--since 1h \
--follow
GitHub ActionsでOIDCデプロイする
GitHub ActionsにはAWSアクセスキーを保存しません。AWS側にGitHub OIDCプロバイダーとデプロイ用ロールを作り、そのロールの信頼ポリシーを特定リポジトリ、ブランチ、環境に絞ります。AWS IAMドキュメントでも、一時認証情報と最小権限が推奨されています。
信頼ポリシーの例です。your-org、your-repo、アカウントIDは置き換えてください。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:your-org/your-repo:ref:refs/heads/main"
}
}
}
]
}
.github/workflows/deploy-aws.ymlは次の形にします。
name: deploy-aws-cdk
on:
push:
branches: ["main"]
workflow_dispatch:
permissions:
id-token: write
contents: read
concurrency:
group: prod-aws-cdk
cancel-in-progress: false
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-cdk-deploy-prod
aws-region: ap-northeast-1
- run: npm ci
- run: npm test
- run: npx cdk synth
- run: npx cdk diff SmallApiProdStack
- run: npx cdk deploy SmallApiProdStack --require-approval never
デプロイロールの権限は、CDK bootstrapロール、CloudFormation実行ロール、権限境界のどれを採用するかで変わります。ここは会社のAWS管理方針に依存するため、「とりあえずAdministratorAccess」は本番では避け、AWS IAM Access Analyzerや権限境界で削っていくのが現実的です。
費用ガードレールを入れる
Lambdaは小さく始めやすい一方、無限ループやスパムで呼び出しが増えると費用が膨らみます。この記事のCDKでは、API Gatewayのスロットリング、使用量プラン、Lambdaの予約同時実行を入れています。さらにAWS Budgetsで月額通知を作ります。
{
"BudgetName": "small-api-monthly-guardrail",
"BudgetLimit": {
"Amount": "20",
"Unit": "USD"
},
"TimeUnit": "MONTHLY",
"BudgetType": "COST"
}
[
{
"Notification": {
"NotificationType": "ACTUAL",
"ComparisonOperator": "GREATER_THAN",
"Threshold": 80,
"ThresholdType": "PERCENTAGE"
},
"Subscribers": [
{
"SubscriptionType": "EMAIL",
"Address": "owner@example.com"
}
]
}
]
aws budgets create-budget \
--account-id "$AWS_ACCOUNT_ID" \
--budget file://budget.json \
--notifications-with-subscribers file://notifications-with-subscribers.json
AWS Budgetsはコストと使用量の追跡に使えますが、作成直後にグラフや通知がすぐ反映されないことがあります。余裕を持って本番前に設定してください。
ロールバックの現実的な手順
Lambda + API Gatewayの最初の運用では、複雑なBlue/Greenより「最後に動いたコミットへ戻す」手順を明文化する方が効果的です。
git revert <bad-commit-sha>
git push origin main
GitHub Actionsが再デプロイしたら、APIとログを確認します。
curl "${API_URL}v1/health"
aws cloudformation describe-stack-events \
--stack-name SmallApiProdStack \
--max-items 20
aws logs tail "/aws/lambda/claude-code-small-api-prod" --since 30m
失敗時の落とし穴は3つあります。1つ目は、AWSコンソールで手修正してCDKとの差分が読めなくなることです。2つ目は、Lambdaの環境変数に秘密情報を入れ、ログや差分に出してしまうことです。3つ目は、API GatewayのdataTraceEnabledを本番で有効にして、リクエスト本文をログに出してしまうことです。この記事のCDKではfalseにしています。
Claude Codeにレビューさせるチェックリスト
デプロイ前にClaude Codeへ次の確認を依頼します。ここで「問題なさそう」ではなく、ファイル名と根拠を出させます。
AWSデプロイ前レビューをしてください。
確認対象:
- CDK diffで追加されるIAM権限に過剰なワイルドカードがないか
- LambdaがSecrets Manager以外から秘密情報を読んでいないか
- API Gatewayのスロットリング、使用量プラン、ログ設定があるか
- CloudWatch Logsに個人情報や本文を出していないか
- rollback手順がREADMEか運用メモにあるか
- npm test, cdk synth, cdk diff が通っているか
問題があれば severity, file, line, fix を表で出してください。
Claude Codeのレビューは最終判断ではありません。AWSアカウント境界、会社のセキュリティルール、料金影響は人間が決めます。ただ、レビューの入口を自動化すると、同じミスを何度も見落とす確率は下げられます。
まとめ:Claude Codeに「安全な足場」を作らせる
AWSデプロイでClaude Codeを使う価値は、単にCDKコードを速く書くことではありません。サービス選定、IaC、CI/CD、IAM、ログ、費用、ロールバックを一つの運用手順にまとめるところにあります。
小さなWeb APIなら、最初はLambda + API Gateway + CDKで十分なことが多いです。Dockerの常時稼働が必要になったらECS/Fargateへ、静的配信中心ならS3 + CloudFrontやAmplifyへ広げます。重要なのは、最初から過剰な構成にせず、後から移れる境界を作ることです。
AWSデプロイ、Claude Code運用ルール、GitHub Actions OIDC、IAM最小権限をチームに合わせて設計したい場合は、Claude Code導入・開発自動化相談から相談してください。Masaが既存リポジトリ、AWSアカウント構成、デプロイ失敗履歴を見ながら、実装可能な改善案に落とし込みます。
この記事で紹介した内容を実際に試した結果、MVPの問い合わせAPIならCDKスタック、GitHub Actions、ログ確認、費用通知までを半日から1日で形にできます。一方で、IAMロールとシークレット設計を雑にすると後で必ず手戻りします。Claude Codeには実装を速くさせ、人間は権限、費用、障害時の判断を握る。この分担が、AWSデプロイ自動化を安全に進めるコツです。
無料PDF: Claude Code はじめてのチートシート
まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。
スパムは送りません。登録情報は厳重に管理します。
Claude Codeを仕事で使える形にしませんか?
無料PDFで基礎を固めたあと、すぐ使えるテンプレート集で試し、必要なら業務自動化や導入相談まで進められます。
この記事を書いた人
Masa
Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。
関連書籍・参考図書
この記事のテーマに関連する書籍を楽天ブックスで探せます。
※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。
関連記事
ObsidianメモをCLAUDE.mdに変えるClaude Code運用: 文脈を毎回説明しない仕組み
Obsidianの作業メモからCLAUDE.md用の運用ノートを作り、Claude Codeに安定した文脈を渡す方法。
Claude Code Revenue CTA Routing: 記事からPDF、Gumroad、相談へ送る設計
PVだけで終わらせず、読者の状態に合わせて無料PDF、Gumroad教材、導入相談へ分岐するCTA設計です。
Claude Codeチーム引き継ぎルール: レビュー、権限、収益導線まで渡す実務手順
Claude Codeの作業をチームで渡すための証拠、権限、ロールバック、無料PDF/Gumroad/相談導線の実務ルール。