Claude CodeでAWS Lambdaを安全に作る実践ガイド
Node.js LambdaをClaude Codeで作り、ローカルテスト、IAM、環境変数、API Gateway、ログ確認まで安全に進める手順。
AWS LambdaをClaude Codeで書くと、ハンドラー、テストイベント、IAMポリシー、デプロイコマンドまで一気に形になります。便利な一方で、AWSは権限と課金が本番に直結します。Claude Codeに任せてよいのは下書きと確認の自動化までで、実際の権限付与、公開API、削除できないリソース、月額コストは必ず人間がレビューします。
この記事では、チームで使いやすい最小構成として、Node.jsのLambdaを作り、ローカルで動作確認し、IAMを最小権限に近づけ、環境変数、CloudWatch Logs、API Gateway、zipパッケージでの更新まで進めます。Lambdaは「イベントを受け取ったときだけ起動する小さな実行環境」、IAMは「AWSで何をしてよいかを決める権限表」、API Gatewayは「HTTPリクエストをLambdaへ渡す入口」です。
公式仕様は変わるので、実装前にAWS Lambdaの入門ドキュメント、Node.js Lambdaの公式ガイド、環境変数の公式ガイド、CloudWatch Logsでの監視、そしてClaude Codeのcommon workflowsを横に置いてください。この記事は2026年6月1日時点で、Node.js 24ランタイムを前提にしています。
どんな場面で使うか
Lambdaは「ずっとサーバーを動かすほどではないが、確実に処理したい」作業に向いています。Claude Codeと相性がよいユースケースは次の3つです。
| ユースケース | Lambdaでやる理由 | Claude Codeに任せる範囲 | 人間が見る範囲 |
|---|---|---|---|
| Webhook受信 | 決済、フォーム、SaaS通知を短時間で受けられる | 署名検証、イベントfixture、失敗時レスポンスの下書き | 秘密鍵の扱い、リトライ、重複処理 |
| 社内用の軽いAPI | 小さなJSON APIなら常時稼働サーバーが不要 | Node.js handler、API Gateway接続、ログ設計 | 認証、CORS、公開範囲、コスト |
| バッチの入口 | CSV投入、画像処理、通知キューなどを小さく始められる | 入力チェック、CloudWatchログ、エラー分類 | タイムアウト、再実行、安全な削除 |
比較すると、最初の検証はzipデプロイが速く、チーム運用はSAMやCDKに移すのが現実的です。
| 方法 | 向いている場面 | 注意点 |
|---|---|---|
| AWSコンソール | 1回だけ挙動を見る | 手順が残らず、レビューしにくい |
| AWS CLI + zip | この記事のような小さい検証 | IAMと環境変数を手で確認する必要がある |
| SAM / CDK | チームの継続運用 | IaCのレビューとデプロイ承認が必要 |
概念図にすると、Claude Codeはコードを書く人ではなく、レビュー材料をそろえる補助役です。
flowchart LR
A[Claude Codeで下書き] --> B[ローカルNode.jsテスト]
B --> C[IAMと環境変数を人間が確認]
C --> D[zipパッケージをdevへデプロイ]
D --> E[API Gateway経由で確認]
E --> F[CloudWatch Logsで失敗を読む]
F --> A
1. Node.js Lambdaハンドラー
まず依存パッケージなしで動くindex.mjsを作ります。API Gateway HTTP APIのイベントと、古いREST API形式のどちらでも動くように、rawPathとpath、requestContext.http.methodとhttpMethodの両方を見ます。
// index.mjs
import crypto from "node:crypto";
const allowedStages = new Set(["dev", "staging", "prod"]);
function readConfig() {
const stage = process.env.APP_STAGE ?? "dev";
if (!allowedStages.has(stage)) {
throw new Error(`Invalid APP_STAGE: ${stage}`);
}
return {
stage,
tableName: process.env.TABLE_NAME ?? "local-orders",
logLevel: process.env.LOG_LEVEL ?? "info",
};
}
function log(level, message, details = {}) {
console.log(
JSON.stringify({
level,
message,
service: "orders-api",
...details,
})
);
}
function json(statusCode, body) {
return {
statusCode,
headers: {
"content-type": "application/json",
},
body: JSON.stringify(body),
};
}
function parseBody(event) {
if (!event.body) return {};
const raw = event.isBase64Encoded
? Buffer.from(event.body, "base64").toString("utf8")
: event.body;
return JSON.parse(raw);
}
export async function handler(event = {}, context = {}) {
const config = readConfig();
const method = event.requestContext?.http?.method ?? event.httpMethod ?? "GET";
const path = event.rawPath ?? event.path ?? "/";
const requestId = context.awsRequestId ?? "local";
log("info", "request.start", {
requestId,
method,
path,
stage: config.stage,
});
try {
if (method === "GET" && path === "/health") {
return json(200, { ok: true, stage: config.stage });
}
if (method === "POST" && path === "/orders") {
const payload = parseBody(event);
const orderId = payload.orderId ?? crypto.randomUUID();
log("info", "order.accepted", {
requestId,
orderId,
tableName: config.tableName,
});
return json(202, {
orderId,
status: "accepted",
storedIn: config.tableName,
});
}
return json(404, { error: "not_found", method, path });
} catch (error) {
log("error", "request.failed", {
requestId,
errorName: error.name,
errorMessage: error.message,
});
return json(500, { error: "internal_error", requestId });
}
}
この段階ではDynamoDBに書き込んでいません。初心者が最初にやるべきことは「HTTPイベントを正しく受け取る」「JSONを返す」「ログで追える」状態を作ることです。データベース接続を後から足すほうが、権限と失敗原因を切り分けやすくなります。
2. イベントfixtureでローカルテストする
次に、API Gatewayから来るイベントをJSONで保存します。fixtureは「テスト用の固定入力」です。Claude Codeに生成させるときも、実リクエストの形に近いfixtureを残すとレビューしやすくなります。
{
"version": "2.0",
"routeKey": "POST /orders",
"rawPath": "/orders",
"requestContext": {
"http": {
"method": "POST",
"path": "/orders"
}
},
"headers": {
"content-type": "application/json"
},
"body": "{\"orderId\":\"demo-1001\",\"amount\":3200,\"currency\":\"JPY\"}",
"isBase64Encoded": false
}
events/create-order.jsonとして保存したら、ローカル実行用の小さなスクリプトを作ります。
// local-test.mjs
import { readFile } from "node:fs/promises";
import { handler } from "./index.mjs";
process.env.APP_STAGE = "dev";
process.env.TABLE_NAME = "orders-dev";
process.env.LOG_LEVEL = "debug";
const eventPath = process.argv[2] ?? "events/create-order.json";
const event = JSON.parse(await readFile(eventPath, "utf8"));
const result = await handler(event, { awsRequestId: "local-001" });
console.log(JSON.stringify(result, null, 2));
実行します。
node local-test.mjs events/create-order.json
期待する出力はstatusCodeが202で、orderIdが返ることです。ここで動かないものはAWSに上げても直りません。Claude Codeに「このログと出力を見て、原因候補を3つに絞って」と頼む前に、まずローカルで再現できる入力を作るのが近道です。
3. IAMは最小権限から始める
Lambda実行ロールには、最初からAdministratorAccessを付けません。CloudWatch Logsへの出力だけなら、次のような権限から始めます。DynamoDBを使う場合だけ、対象テーブルのARNに絞って追加します。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "CreateOwnLogGroupIfMissing",
"Effect": "Allow",
"Action": "logs:CreateLogGroup",
"Resource": "arn:aws:logs:ap-northeast-1:123456789012:*"
},
{
"Sid": "WriteOwnLambdaLogs",
"Effect": "Allow",
"Action": ["logs:CreateLogStream", "logs:PutLogEvents"],
"Resource": "arn:aws:logs:ap-northeast-1:123456789012:log-group:/aws/lambda/claude-orders-dev:*"
},
{
"Sid": "ReadWriteOnlyOrdersTable",
"Effect": "Allow",
"Action": ["dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:UpdateItem", "dynamodb:Query"],
"Resource": "arn:aws:dynamodb:ap-northeast-1:123456789012:table/orders-dev"
}
]
}
このJSONはそのまま本番へ貼るものではなく、レビューのたたき台です。アカウントID、リージョン、関数名、テーブル名を実環境に合わせ、使っていないDynamoDB権限は削ってください。IAMの変更は「通るか」ではなく「広すぎないか」を人間が確認します。AWS権限の考え方は、サイト内のAWS IAMガイドも参考になります。
4. zipパッケージでデプロイする
ここからはAWS CLIを使います。実行するとAWSリソースが作られ、少額でも課金対象になる可能性があります。作業前にAWSアカウント、リージョン、削除手順を確認してください。
export AWS_REGION=ap-northeast-1
export FUNCTION_NAME=claude-orders-dev
export ROLE_NAME=claude-orders-dev-lambda-role
export ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
zip function.zip index.mjs
信頼ポリシーを作ります。これは「Lambdaサービスがこのロールを引き受けてよい」という設定です。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
trust-policy.jsonとして保存したら、ロールを作成します。さきほどのIAMポリシーはlambda-policy.jsonとして保存しておきます。
aws iam create-role \
--role-name "$ROLE_NAME" \
--assume-role-policy-document file://trust-policy.json
aws iam put-role-policy \
--role-name "$ROLE_NAME" \
--policy-name claude-orders-dev-inline \
--policy-document file://lambda-policy.json
ROLE_ARN=$(aws iam get-role \
--role-name "$ROLE_NAME" \
--query "Role.Arn" \
--output text)
aws lambda create-function \
--function-name "$FUNCTION_NAME" \
--runtime nodejs24.x \
--handler index.handler \
--zip-file fileb://function.zip \
--role "$ROLE_ARN" \
--architectures arm64 \
--timeout 10 \
--memory-size 128 \
--environment "Variables={APP_STAGE=dev,TABLE_NAME=orders-dev,LOG_LEVEL=info}" \
--region "$AWS_REGION"
コードだけ更新するときは、関数を作り直さずに次のコマンドを使います。
zip function.zip index.mjs
aws lambda update-function-code \
--function-name "$FUNCTION_NAME" \
--zip-file fileb://function.zip \
--region "$AWS_REGION"
環境変数を更新するときは注意が必要です。AWS CLIのupdate-function-configurationで環境変数を指定すると、既存のVariables全体を置き換える挙動になります。差分だけを追加するつもりで既存値を消さないよう、現在値を取得してから変更してください。パスワードやAPIキーのような秘密情報は環境変数に直接入れず、Secrets ManagerやParameter Storeを検討します。
aws lambda update-function-configuration \
--function-name "$FUNCTION_NAME" \
--environment "Variables={APP_STAGE=dev,TABLE_NAME=orders-dev,LOG_LEVEL=debug}" \
--region "$AWS_REGION"
5. API GatewayとCloudWatch Logsで確認する
まずLambdaを直接呼び出します。
aws lambda invoke \
--function-name "$FUNCTION_NAME" \
--payload fileb://events/create-order.json \
--region "$AWS_REGION" \
response.json
cat response.json
次にHTTP APIを作り、Lambdaへつなぎます。公開URLができます。社内検証でも、認証、CORS、レート制限、削除予定日を確認してから実行してください。
API_ID=$(aws apigatewayv2 create-api \
--name claude-orders-dev-api \
--protocol-type HTTP \
--target "arn:aws:lambda:${AWS_REGION}:${ACCOUNT_ID}:function:${FUNCTION_NAME}" \
--query "ApiId" \
--output text)
aws lambda add-permission \
--function-name "$FUNCTION_NAME" \
--statement-id AllowHttpApiInvoke \
--action lambda:InvokeFunction \
--principal apigateway.amazonaws.com \
--source-arn "arn:aws:execute-api:${AWS_REGION}:${ACCOUNT_ID}:${API_ID}/*/*" \
--region "$AWS_REGION"
API_ENDPOINT=$(aws apigatewayv2 get-api \
--api-id "$API_ID" \
--query "ApiEndpoint" \
--output text)
curl -s "$API_ENDPOINT/health"
curl -s -X POST "$API_ENDPOINT/orders" \
-H "content-type: application/json" \
-d '{"amount":3200,"currency":"JPY"}'
CloudWatch Logsは、障害対応の最初に見ます。console.logをJSONにしておくと、後から検索しやすくなります。
aws logs tail "/aws/lambda/${FUNCTION_NAME}" \
--follow \
--region "$AWS_REGION"
ログが出ない場合は、IAMのlogs:CreateLogStreamとlogs:PutLogEvents、関数名、リージョンを確認します。API Gatewayで500になる場合は、Lambdaの戻り値がstatusCode、headers、bodyの形になっているかを見ます。より詳しい監視設計はAWS CloudWatchの記事とAWS API Gatewayの記事も併読してください。
Claude Codeにレビューさせるプロンプト
Claude Codeには、自由にAWSへデプロイさせるのではなく、差分とリスクを説明させます。次のプロンプトをリポジトリで使うと、チームレビューに必要な観点がそろいます。
You are reviewing a Node.js AWS Lambda change for a team repository.
Scope:
- Files: index.mjs, events/*.json, IAM policy JSON, deployment commands in docs
- Runtime: nodejs24.x
- Entry point: index.handler
Review for:
1. API Gateway event compatibility and response shape
2. Input validation and JSON parsing failures
3. Environment variables that overwrite existing values
4. IAM actions that are wider than needed
5. CloudWatch logs that expose secrets or personal data
6. AWS resources that can create unexpected cost
7. Local test commands that a reviewer can copy and run
Return:
- blocking issues
- non-blocking improvements
- exact commands to verify locally
- questions a human must answer before deploying
ここで大事なのは、Claude Codeに「修正して」だけで頼まないことです。まず調査、計画、編集、検証の順に進めます。公式のcommon workflowsにあるように、探索してから実装させると、勝手な前提でIAMやデプロイ先を広げる失敗が減ります。
よくある落とし穴
1つ目は、ローカルで動かさずにAWSへ上げることです。LambdaのエラーはCloudWatch Logsに出ますが、イベントの形が違うだけならローカルfixtureで先に見つけられます。
2つ目は、環境変数に秘密情報を入れることです。Lambdaの環境変数は設定としては便利ですが、値の管理、暗号化、閲覧権限を考える必要があります。秘密情報はSecrets ManagerやParameter Storeを検討してください。
3つ目は、IAMを広げすぎることです。dynamodb:*やResource: "*"は検証を速くしますが、事故の範囲も広げます。Claude Codeが提案したポリシーは、使うアクションと対象ARNを1行ずつ削るレビューをします。
4つ目は、API Gatewayの公開範囲です。create-api --targetは素早い反面、公開URLがすぐできます。社内APIなら認証、CORS、ステージ名、ログ、レート制限、削除予定を先に決めます。
5つ目は、コストの見落としです。Lambda自体は小さく見えても、CloudWatch Logs、API Gateway、DynamoDB、NAT Gateway、外部API呼び出しで費用が増えます。Claude Codeの提案に「この構成で課金される箇所を列挙して」と必ず聞きます。
チーム導入時の安全なレビュー順
最初はdev環境だけで動かします。PRにはindex.mjs、fixture、IAMポリシー、デプロイ手順、削除手順を含めます。レビュー担当者はローカルでnode local-test.mjsを実行し、次にIAMの差分を読み、最後にAWS CLIのコマンドを確認します。
本番化するなら、zip手順をそのまま残すのではなく、AWSデプロイガイドのようにCI/CDやCDKへ移します。手作業のCLIは検証には向きますが、チーム運用では「誰が、いつ、何を変えたか」が残る仕組みが必要です。
ClaudeCodeLabでは、こうしたAWS権限、CLAUDE.md、レビュー用プロンプト、デプロイ承認の型を教材・テンプレートとして整理しています。自社のAWSアカウント構成、IAM境界、Lambda/API Gatewayのレビュー運用まで一緒に設計したい場合は、Claude Code導入相談・研修で実リポジトリ前提の相談ができます。
最後に削除手順も用意しておきます。検証後に不要なら、API、Lambda、IAMロールを消します。
aws apigatewayv2 delete-api --api-id "$API_ID" --region "$AWS_REGION"
aws lambda delete-function --function-name "$FUNCTION_NAME" --region "$AWS_REGION"
aws iam delete-role-policy --role-name "$ROLE_NAME" --policy-name claude-orders-dev-inline
aws iam delete-role --role-name "$ROLE_NAME"
この記事で紹介した内容を実際に試した結果、Claude Codeは「handler、fixture、IAM、CLI手順を同じ文脈でそろえる」作業にかなり強い一方、AWS権限と公開URLの判断は人間が見ないと危険だと分かりました。ローカルfixtureで先に失敗を潰し、dev環境でログを読み、IAMとコストを人間が確認する順番にすると、Lambdaの小さな自動化でもチームで安心してレビューできます。
無料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/相談導線の実務ルール。