AWS CDK(TypeScript)で Lambda・API Gateway・DynamoDB の構成を作るなら、コードは Claude Code に書かせ、cdk synth とテストと cdk diff まで Claude Code に回させ、cdk deploy だけは人が行う、という分担にするのが安全で速い方法です。この記事では、Go の Lambda(provided.al2023・arm64)と API Gateway HTTP API と DynamoDB を 1 つのスタックで作り、CLAUDE.md の書き方、検証の回し方、IAM の最小権限をどこで見ればよいかを、実際に npx cdk synth と cdk diff を通した出力つきで説明します(2026 年 10 月時点、aws-cdk-lib 2.272.0 / CDK CLI 2.1144.0 で確認)。
AWS CDK(Cloud Development Kit)は、AWS の構成を TypeScript などのコードで書き、CloudFormation のテンプレートに変換してデプロイする道具です。Claude Code に任せると楽になるのは、型のある CDK のコードを書き、cdk synth でエラーを見て直す、という繰り返し です。CDK はコンパイルとテンプレート生成で多くの間違いが分かるので、AI エージェントが自分で検証しながら進めやすい題材です。
一方で、AI に任せてはいけない部分もはっきりしています。本番の AWS アカウントへの cdk deploy と、IAM の権限が広がっていないかの最終判断です。そこで、この記事では次の分担にします。
開発者が指示し、Claude Code が CDK を書いて go test・cdk synth・jest・cdk diff まで回し、人が diff を読んで承認してから cdk deploy する流れ 図: 筆者作成
作る構成は次のとおりです。API Gateway の HTTP API が 2 つのルートを受け、Go の Lambda が DynamoDB に読み書きします。
API Gateway HTTP API から Go の Lambda、DynamoDB へとつながる 1 つの CDK スタックの構成図 図: 筆者作成
部品 使う CDK の API ポイント DynamoDB aws-cdk-lib/ aws-dynamodb の TableV2公式の README が「すべての用途で推奨」としている新しい L2 コンストラクト1 Lambda(Go) @aws-cdk/ aws-lambda-go-alpha の GoFunctionGo のビルドを CDK が行う。実験的(experimental)なモジュール2 API Gateway aws-cdk-lib/ aws-apigatewayv2 の HttpApiHTTP API の L2 は aws-cdk-lib に入っている安定版 権限 table.grant(fn, ...actions)使う操作だけを、このテーブルだけに許可する
Go の Lambda そのものの書き方は AWS Lambda で Go の API サーバーを作る で詳しく扱っています。この記事は「CDK で包む」「Claude Code に任せる」部分に絞ります。
理由は 3 つあります。1 つ目は、AWS Lambda への関心が続いていることです。Google トレンド(日本・過去 90 日、2026-10-05 取得)の相対値で、「AWS Lambda」は直近 4 週の平均が 2.3、その前の 8 週の平均が 1.9 でした。値は小さいものの、やや上向きです3 。2 つ目は、Lambda まわりの新機能が 2026 年も続いていることです。たとえば Lambda Managed Instances は 2026 年 9 月に 90 分のタイムアウトに対応し、CDK にも CapacityProvider が入っています(詳しくは Lambda Managed Instances とは )。
3 つ目は、AI エージェントに IaC(Infrastructure as Code)を書かせるための道具がそろってきたことです。AWS は CDK と CloudFormation のドキュメント検索・テンプレート検証を行う MCP サーバー「AWS IaC MCP Server」を公開しています4 。Claude Code が古い API を書いてしまう問題を、公式ドキュメントを引かせることで減らせます。
Claude Code に CDK を書かせる前に、CLAUDE.md(プロジェクトのルールを書いておくファイル)と .claude/ settings.json(権限の設定)を用意します。ここが一番大事です。CLAUDE.md は「お願い」、settings.json は「強制」と考えてください。
CDK のプロジェクトで書いておくとよいのは、使うコマンド、検証の順番、やってはいけないこと、権限の方針 の 4 つです。長い説明より、Claude Code がそのまま実行できる形で書きます。
# todo-api(AWS CDK / TypeScript + Go の Lambda)
## 構成
- lib/todo-api-stack.ts: スタック本体(DynamoDB TableV2 / GoFunction / HttpApi)
- lambda/api/: Go のハンドラ(provided.al2023・arm64)。go.mod は lambda/ にある
## 変更したら必ずこの順で確かめる
1. (cd lambda && go vet ./... && go test ./...)
2. npx cdk synth --quiet # テンプレート生成。Go のビルドもここで走る
3. npx jest # テンプレートのテスト(IAM の権限もここで見る)
4. npx cdk diff --template base.template.json # 前回のテンプレートとの差分を出して見せる
## やってはいけないこと
- cdk deploy / cdk destroy / cdk bootstrap は実行しない(人が実行する)
- AWS の認証情報・アカウント ID をコードに書かない
- cdk.json の feature flags(context)を勝手に消さない
## 権限の方針
- grantReadWriteData のような広い grant は使わず、table.grant(fn, '使う操作') で必要な操作だけを許可する
- IAM ポリシーに Resource: '*' が出たら、理由を説明してから使う
- 権限を変えたら cdk diff の「IAM Statement Changes」の表を必ず貼る
CLAUDE.md の書き方の全体は CLAUDE.md の書き方(AGENTS.md との違い) にまとめています。
CLAUDE.md に「deploy しない」と書いても、それはモデルへの指示にすぎません。実行そのものを止めるには、Claude Code の権限ルールで deny にします。Claude Code は deny → ask → allow の順にルールを評価し、最初に一致したもので決まります5 。
{
"permissions" : {
"allow" : [
"Bash(npx cdk synth *)" ,
"Bash(npx cdk diff *)" ,
"Bash(npx jest *)" ,
"Bash(go test *)" ,
"Bash(go vet *)"
] ,
"deny" : [
"Bash(npx cdk deploy *)" ,
"Bash(cdk deploy *)" ,
"Bash(npx cdk destroy *)" ,
"Bash(cdk destroy *)" ,
"Bash(npx cdk bootstrap *)" ,
"Bash(aws *)"
]
}
}
Bash(aws *) を deny にしておくと、Claude Code が AWS CLI で直接リソースを触ることも防げます。ルールの末尾の * は、そのコマンドで始まる呼び出しすべてに一致します5 。より強く止めたい場合は、hooks でコマンドを検査する方法もあります(Claude Code の hooks 入門 )。
CDK の API は版ごとに増えたり非推奨になったりします。Claude Code が古い書き方をしないように、AWS の IaC MCP Server をつないでおくと、CDK のドキュメント検索、サンプル検索、ベストプラクティス、cfn-lint による検証、cfn-guard によるコンプライアンス確認をツールとして使えます4 。ドキュメント検索とテンプレートの検証は AWS の認証情報なしで動きます4 。
claude mcp add aws-iac -- uvx awslabs.aws-iac-mcp-server@latest
ここからは実際の手順です。前提は Node.js 24、Go 1.26、AWS CDK CLI 2.1144.0 です。どの手順も、Claude Code には「何を作るか」と「どう確かめるか」をセットで頼みます。
最初の雛形はコマンドで作ります。ここは AI に任せるより、決まったコマンドを打つ方が確実です。
mkdir todo-api && cd todo-api
npx aws-cdk@2.1144.0 init app --language typescript
npm i @aws-cdk/aws-lambda-go-alpha@2.272.0-alpha.0
2026 年 10 月時点の cdk init は、TypeScript 7 系と tsx を使う雛形を作り、import * as cdk from 'aws-cdk-lib/ core' の形で書かれています。alpha モジュールは aws-cdk-lib と同じ版番号(末尾に -alpha.0)をそろえて入れます。
Claude Code への頼み方の例です。
lambda/api/main.go に、API Gateway HTTP API(ペイロード 2.0)から呼ばれる Go の Lambda を書いて。
POST /todos で {id, title} を DynamoDB に PutItem、GET /todos/{id} で GetItem する。
テーブル名は環境変数 TABLE_NAME。DynamoDB の操作は interface に切り出して、
テーブル駆動テストでモックを使って確かめて。go vet と go test が通るまで直して。
できあがったハンドラの中心は次のとおりです。DynamoDB クライアントを Store という interface で受けているので、テストではメモリ上の map に差し替えられます。
type Store interface {
PutItem(ctx context.Context, in *dynamodb.PutItemInput, opts ...func (*dynamodb.Options)) (*dynamodb.PutItemOutput, error )
GetItem(ctx context.Context, in *dynamodb.GetItemInput, opts ...func (*dynamodb.Options)) (*dynamodb.GetItemOutput, error )
}
type handler struct {
db Store
table string
}
func (h *handler) handle(ctx context.Context, req events.APIGatewayV2HTTPRequest) (events.APIGatewayV2HTTPResponse, error ) {
switch req.RequestContext.HTTP.Method {
case http.MethodPost:
var t Todo
if err := json.Unmarshal([]byte (req.Body), &t); err != nil || t.ID == "" || t.Title == "" {
return reply(http.StatusBadRequest, map [string ]string {"error" : "id と title は必須です" })
}
item, err := attributevalue.MarshalMap(t)
if err != nil {
return reply(http.StatusInternalServerError, map [string ]string {"error" : err.Error()})
}
if _, err := h.db.PutItem(ctx, &dynamodb.PutItemInput{TableName: aws.String(h.table), Item: item}); err != nil {
return reply(http.StatusInternalServerError, map [string ]string {"error" : "保存に失敗しました" })
}
return reply(http.StatusCreated, t)
case http.MethodGet:
}
return reply(http.StatusMethodNotAllowed, map [string ]string {"error" : "許可されていないメソッドです" })
}
func main () {
cfg, err := config.LoadDefaultConfig(context.Background())
if err != nil {
panic (err)
}
h := &handler{db: dynamodb.NewFromConfig(cfg), table: os.Getenv("TABLE_NAME" )}
lambda.Start(h.handle)
}
依存は github.com/ aws/ aws-lambda-go v1.55.1 と AWS SDK for Go v2(service/ dynamodb v1.70.0)です。テストは「作成できる」「title が無いと 400」「作成したものを取得できる」「無い id は 404」「DELETE は 405」の 5 ケースで、go vet と go test が通ることを確かめました。
次に、スタック本体を頼みます。頼むときに、ランタイムとアーキテクチャと権限を言葉で指定する のがコツです。指定しないと、AI は古い例に引きずられて広い権限や古いランタイムを書きがちです。
lib/todo-api-stack.ts に、DynamoDB(TableV2・オンデマンド・PITR 有効・RETAIN)、
lambda/api を GoFunction でビルドする Lambda(provided.al2023・arm64・256MB・10 秒)、
HttpApi の POST /todos と GET /todos/{id} を書いて。
権限は table.grant で PutItem と GetItem だけ。終わったら npx cdk synth --quiet を通して。
できあがったスタックです。npx cdk synth が通ることを確かめています。
import * as cdk from 'aws-cdk-lib/core' ;
import { Construct } from 'constructs' ;
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb' ;
import * as lambda from 'aws-cdk-lib/aws-lambda' ;
import * as logs from 'aws-cdk-lib/aws-logs' ;
import * as apigwv2 from 'aws-cdk-lib/aws-apigatewayv2' ;
import { HttpLambdaIntegration } from 'aws-cdk-lib/aws-apigatewayv2-integrations' ;
import { GoFunction } from '@aws-cdk/aws-lambda-go-alpha' ;
export class TodoApiStack extends cdk.Stack {
constructor (scope : Construct , id : string , props ?: cdk.StackProps ) {
super (scope, id, props);
const table = new dynamodb.TableV2 (this , 'TodoTable' , {
partitionKey : { name : 'id' , type : dynamodb.AttributeType .STRING },
billing : dynamodb.Billing .onDemand (),
pointInTimeRecoverySpecification : { pointInTimeRecoveryEnabled : true },
removalPolicy : cdk.RemovalPolicy .RETAIN ,
});
const fn = new GoFunction (this , 'TodoFunction' , {
entry : 'lambda/api' ,
runtime : lambda.Runtime .PROVIDED_AL2023 ,
architecture : lambda.Architecture .ARM_64 ,
memorySize : 256 ,
timeout : cdk.Duration .seconds (10 ),
environment : { TABLE_NAME : table.tableName },
logGroup : new logs.LogGroup (this , 'TodoFunctionLogs' , {
retention : logs.RetentionDays .ONE_MONTH ,
}),
});
table.grant (fn, 'dynamodb:PutItem' , 'dynamodb:GetItem' );
const api = new apigwv2.HttpApi (this , 'TodoHttpApi' );
const integration = new HttpLambdaIntegration ('TodoIntegration' , fn);
api.addRoutes ({ path : '/todos' , methods : [apigwv2.HttpMethod .POST ], integration });
api.addRoutes ({ path : '/todos/{id}' , methods : [apigwv2.HttpMethod .GET ], integration });
new cdk.CfnOutput (this , 'ApiUrl' , { value : api.apiEndpoint });
}
}
ここで runtime を明示しているのには理由があります。GoFunction の既定のランタイムは PROVIDED_AL2 です2 。一方、Lambda の公式ドキュメントは Go の関数に provided.al2023 を使う手順を案内しています6 。指定を省くと古い方のランタイムになるので、CLAUDE.md かプロンプトで必ず指定させます。
コードができたら、CLAUDE.md に書いた順番で検証を回させます。次の GIF は、実際に実行したコマンドと出力です。
go test、npx cdk synth、npx jest が通り、cdk diff で IAM の権限が 2 件から 12 件に広がる差分が表で出る様子 実際に実行した出力(cdk diff の表は途中の行を省略)
npx cdk synth --quiet は、テンプレートを cdk.out/ に書き出すだけで画面には出しません。このとき Go のビルドも走ります。GoFunction は、ローカルに Go 1.11 以上があればそれでビルドし、無ければ Docker のコンテナでビルドします2 。手元に Go がある環境では、synth に Docker は要りません。
テンプレートのテストは、aws-cdk-lib/ assertions で書きます。権限のテストを入れておくと、あとで誰か(AI を含む)が権限を広げたときにテストが落ちて気づけます。
import * as cdk from 'aws-cdk-lib/core' ;
import { Template } from 'aws-cdk-lib/assertions' ;
import { TodoApiStack } from '../lib/todo-api-stack' ;
test ('Lambda は provided.al2023 / arm64 で動く' , () => {
const template = Template .fromStack (new TodoApiStack (new cdk.App (), 'TestStack' ));
template.hasResourceProperties ('AWS::Lambda::Function' , {
Runtime : 'provided.al2023' ,
Architectures : ['arm64' ],
});
});
test ('DynamoDB への権限は PutItem と GetItem だけ' , () => {
const template = Template .fromStack (new TodoApiStack (new cdk.App (), 'TestStack' ));
const policies = Object .values (template.findResources ('AWS::IAM::Policy' ));
const actions = policies
.flatMap ((p ) => p.Properties .PolicyDocument .Statement )
.flatMap ((s ) => [s.Action ].flat ())
.filter ((a : string ) => a.startsWith ('dynamodb:' ))
.sort ();
expect (actions).toEqual (['dynamodb:GetItem' , 'dynamodb:PutItem' ]);
});
最初は Match.objectLike で Action の配列をそのまま比べるテストを書いていましたが、cdk synth の出力とテスト内で作るテンプレートとで操作の並び順が変わり、順番を変えただけで落ちることがありました。テストでは並べ替えてから比べると安定します。
cdk diff は通常、デプロイ済みのスタックと比べるので AWS の認証情報が要ります。デプロイ前のレビューでは、前回の synth 結果を保存しておき、--template でそのファイルと比べると認証情報なしで差分を出せます。
cp cdk.out/TodoApiStack.template.json base.template.json
npx cdk diff --template base.template.json
Go の Lambda を CDK で扱う方法は 2 つあります。結論は、個人や小さなチームなら GoFunction、alpha を避けたい組織なら lambda.Function と Code.fromAsset で自分でビルド です。
観点 GoFunction(aws-lambda-go-alpha)lambda.Function + Code.fromAsset安定性 experimental。破壊的変更があり得て、セマンティックバージョニングの対象外2 aws-cdk-lib の安定版 API だけで済む ビルド CDK が go build する。Go が無ければ Docker で2 GOOS=linux GOARCH=arm64 go build -tags lambda.norpc -o bootstrap を自分で行い、そのディレクトリを渡す6 既定のランタイム PROVIDED_AL2(PROVIDED_AL2023 を明示する)2 自分で Runtime.PROVIDED_AL2023 を指定 ビルドフラグ bundling.goBuildFlags。synth 時に「任意のコマンドを実行できる」という警告の注記が出るMakefile や npm scripts で自由に書ける 向くケース 速く始めたい、関数が少ない CI で Go のビルドとテストを先に済ませたい、alpha を入れたくない
実際に goBuildFlags に -ldflags "-s -w" を入れて synth したところ、「goBuildFlags can execute arbitrary commands during bundling」という警告の注記が出ました。AI にビルドフラグを足させるときは、この警告を消すために注記を握りつぶさせず、フラグの中身を人が確かめてください。
Code.fromAsset の場合、Lambda が探す実行ファイルの名前は bootstrap で、zip の一番上に置く必要があります6 。-tags lambda.norpc は古い Go 1.x ランタイム用の RPC 部分を外してパッケージを小さくするための任意のタグです6 。
AI に CDK を書かせたとき、人が一番時間をかけて見るべきなのは IAM です。CDK の公式ガイドも、L2 コンストラクトの grant メソッドを使えば最小権限の IAM ロールが作られる、と案内しています7 。ただし「どの grant を選ぶか」で権限の広さは大きく変わります。
実際に、table.grant(fn, 'dynamodb:PutItem', 'dynamodb:GetItem') を table.grantReadWriteData(fn) に置き換えて cdk diff を取ると、許可される DynamoDB の操作が 2 個から 12 個に増えました。増えたのは BatchGetItem、BatchWriteItem、ConditionCheckItem、DeleteItem、DescribeTable、GetRecords、GetShardIterator、Query、Scan、UpdateItem です。
Lambda に許可される DynamoDB の操作の数(grant の書き方別) データを表で見る Lambda に許可される DynamoDB の操作の数(grant の書き方別) 書き方 操作の数(個) table.grant(PutItem, GetItem) 2 table.grantReadWriteData() 12
出典: 筆者集計(元データ: aws-cdk-lib 2.272.0 で npx cdk diff --template を実行した IAM Statement Changes の出力、2026-10-05)
grantReadWriteData は「読み書き全部」なので、この API に要らない DeleteItem や Scan まで入ります。もしハンドラに入力の検証漏れがあれば、テーブル全体を読み出したり消したりする道が開きます。AI は「動くこと」を優先して広い grant を選びがちなので、CLAUDE.md で禁止し、テストで固定しておくのが効きます。
cdk diff の出力のうち、次の点を毎回確かめます。
見る場所 確かめること 危ないサイン IAM Statement Changes の Action 実際にコードで呼ぶ操作だけか *、dynamodb:*、使っていない Delete* や ScanIAM Statement Changes の Resource 対象のテーブル・キューの ARN に絞れているか *(理由の説明が無い)Principal 想定したロール・サービスか 別のサービスや * Resources の [-] と [~] の置き換え テーブルなど状態を持つものが作り直されないか DynamoDB テーブルの replace Security Group Changes 0.0.0.0/0 からの受け付けが増えていないか 意図しない開放
CDK CLI は、IAM の表の下に「この一覧に出ないセキュリティ関連の変更もあり得る」という注意を出します。表だけでなく Resources の差分も読む必要があります。テーブルには RemovalPolicy.RETAIN を付けておくと、スタックを消してもデータは残ります。
ここまでは「Lambda に渡す権限」の話でした。もう 1 つ、cdk deploy を実行する側の権限があります。CDK の公式ガイドでは、bootstrap で作られる CloudFormation の実行ロールは既定で AdministratorAccess を持ち、権限境界(permissions boundary)も付いていないと説明しています7 。そのうえで、細かすぎる権限設計よりも、権限境界や SCP(サービスコントロールポリシー)でガードレールを敷く方法を推奨しています7 。
Claude Code に cdk deploy を許可しない理由はここにもあります。デプロイの権限は強いので、実行するのは人か、承認を挟んだ CI/CD に限ります。
cdk synth が確かめるのはテンプレートの形までです。サービスの上限、名前の重複、リージョンで使えない機能などは、デプロイして初めて分かります。AWS IaC MCP Server の CloudFormation 検証(cfn-lint)とコンプライアンス確認(cfn-guard)を Claude Code に使わせると、デプロイ前に見つかるものが増えます4 。
@aws-cdk/ aws-apigatewayv2-alpha のように、昔は alpha だったが今は aws-cdk-lib に入っているモジュールを入れようとすることがあります。HTTP API の L2 は aws-cdk-lib/ aws-apigatewayv2 を使います。CLAUDE.md に「使うモジュール」を書いておくか、MCP でドキュメントを引かせます。
cdk init の .gitignore には cdk.out が入っています。Claude Code に git add -A をさせる運用だと、ビルドしたものや base.template.json が混ざりやすいので、何をコミットするかを CLAUDE.md に書いておきます。
雛形の bin/ todo-api.ts は env を指定しない「環境に依存しないスタック」です。VPC の検索(Vpc.fromLookup)のようにアカウントとリージョンが要る機能は、この状態では使えません。使うときは env を指定しますが、アカウント ID はコードに直書きせず環境変数から読みます。
AWS CDK のコードと Go のハンドラは Claude Code に書かせ、go test → cdk synth → jest → cdk diff まで自分で回させる
cdk deploy / destroy / bootstrap と aws コマンドは .claude/ settings.json の deny で止める。CLAUDE.md の「しない」は強制にならない
GoFunction は runtime: PROVIDED_AL2023 を明示する。alpha を避けたいなら Code.fromAsset で自分でビルドする
権限は table.grant(fn, '使う操作') で絞り、テストで固定する。grantReadWriteData は 12 個の操作を許可する
レビューでは cdk diff の IAM の表と Resources の置き換えを人が読む
次にやることとして、まずは手元の CDK プロジェクトに CLAUDE.md と deny ルールを入れ、cdk diff --template で差分を見る流れを 1 回試してみてください。
おすすめしません。CDK のデプロイは bootstrap で作られた強いロールで行われ、既定では AdministratorAccess が付いています7 。Claude Code には synth と diff までを任せ、deploy は人か、承認を挟む CI/CD で行ってください。止めるには .claude/ settings.json の deny に Bash(npx cdk deploy *) などを書きます。
すぐ始めたいなら GoFunction、alpha のモジュールを入れたくないなら Code.fromAsset です。GoFunction は experimental で、既定のランタイムが PROVIDED_AL2 なので PROVIDED_AL2023 を明示します2 。Code.fromAsset の場合は bootstrap という名前の実行ファイルを自分でビルドして渡します6 。
通常の cdk diff はデプロイ済みのスタックと比べるので認証情報が要ります。デプロイ前のレビューなら、承認済みの synth 結果をファイルに保存しておき、npx cdk diff --template base.template.json で比べれば、認証情報なしで IAM の表を含む差分を出せます(CDK CLI 2.1144.0 で確認)。
grant メソッドを使い、その中でも必要な操作だけを渡すことです。grantReadWriteData は DynamoDB の 12 個の操作を許可しますが、table.grant(fn, 'dynamodb:PutItem', 'dynamodb:GetItem') なら 2 個です。さらに aws-cdk-lib/ assertions で権限のテストを書いておくと、あとから広げられたときに気づけます。
TableV2 です。CDK の公式 README が、単一のテーブルでもレプリカのあるテーブルでも TableV2 を推奨しています1 。synth すると CloudFormation の AWS::DynamoDB::GlobalTable になる点だけ、レビューのときに知っておくと戸惑いません。