Claude Code × AWS CloudFormation/CDK Complete Guide | Auto-Generate Infrastructure as Code
Accelerate AWS infrastructure as code with Claude Code. Working code for CloudFormation templates, CDK TypeScript stacks, and multi-stack design — based on Masa's real production experience.
“I hate clicking through the AWS Console every time to set up infrastructure” — if you’ve ever felt this way, you’re not alone. CloudFormation and CDK make infrastructure reproducible through code, but writing those templates takes time.
I manage this site’s infrastructure (Cloudflare Pages + Workers + D1) with Claude Code, and for AWS business infrastructure I also let Claude Code generate CloudFormation/CDK templates. Templates that used to take hours now finish in 1/5 the time.
CloudFormation vs CDK: Which Should You Choose?
CloudFormation: AWS-native service that describes infrastructure in JSON/YAML
CDK (Cloud Development Kit): Framework that describes infrastructure in TypeScript/Python,
then synthesizes it to CloudFormation
| Comparison | CloudFormation | CDK |
|---|---|---|
| Language | JSON / YAML | TypeScript, Python, Java, etc. |
| Type Safety | None | Excellent (fully type-safe with TypeScript) |
| Reusability | Low (copy-paste) | High (abstracted with classes & functions) |
| Learning Curve | Low | Medium |
| Claude Code Compatibility | Excellent | Outstanding |
For new projects, CDK TypeScript is strongly recommended. It has outstanding compatibility with Claude Code, and combined with type completion it produces high-quality code.
Step 1: Convert Existing AWS Resources to CloudFormation
A pattern for when you want to codify an existing AWS environment after the fact.
claude -p "
Convert the following AWS setup into a CloudFormation template (YAML).
[Current Setup]
- VPC: 10.0.0.0/16, 2 public subnets, 2 private subnets
- EC2: t3.medium, Amazon Linux 2023, placed in public subnet
- RDS: MySQL 8.0, db.t3.micro, placed in private subnet
- ALB: HTTPS (ACM certificate), forwarding to EC2
- Security Groups: allow 443 from ALB, allow 3306 from EC2 to RDS only
[Requirements]
- Environment (dev/staging/prod) switchable via parameter
- Prefix resource names with the environment name
- Output ALB DNS name in CloudFormation Outputs
"
Generated CloudFormation template (excerpt):
AWSTemplateFormatVersion: "2010-09-09"
Description: "Web Application Infrastructure"
Parameters:
Environment:
Type: String
AllowedValues: [dev, staging, prod]
Default: dev
DBPassword:
Type: String
NoEcho: true # Mask the password
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub "${Environment}-vpc"
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.1.0/24
AvailabilityZone: !Select [0, !GetAZs ""]
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub "${Environment}-public-1"
# ALB Security Group
ALBSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: ALB Security Group
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
Outputs:
ALBDnsName:
Value: !GetAtt ApplicationLoadBalancer.DNSName
Export:
Name: !Sub "${Environment}-alb-dns"
Step 2: Build a Serverless Stack with CDK
With CDK you write in TypeScript, making logic reuse straightforward.
# Initialize CDK project
mkdir my-infra && cd my-infra
npx cdk init app --language typescript
claude -p "
Implement the following serverless setup in CDK TypeScript in lib/my-infra-stack.ts.
[Setup]
- API Gateway (REST API) + Lambda (Node.js 20)
- DynamoDB table (PAY_PER_REQUEST)
- S3 bucket (private, versioning enabled)
- CloudFront (S3 origin, custom domain support)
- Pass DynamoDB table name and S3 bucket name to Lambda via environment variables
- Auto-attach least-privilege IAM role to Lambda
Tag all resources appropriately,
and enable deletion protection in production (NODE_ENV=production).
"
Generated CDK code:
// lib/my-infra-stack.ts
import * as cdk from "aws-cdk-lib";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as apigateway from "aws-cdk-lib/aws-apigateway";
import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
import * as s3 from "aws-cdk-lib/aws-s3";
import * as cloudfront from "aws-cdk-lib/aws-cloudfront";
import * as origins from "aws-cdk-lib/aws-cloudfront-origins";
export class MyInfraStack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const isProd = process.env.NODE_ENV === "production";
// DynamoDB table
const table = new dynamodb.Table(this, "AppTable", {
partitionKey: { name: "PK", type: dynamodb.AttributeType.STRING },
sortKey: { name: "SK", type: dynamodb.AttributeType.STRING },
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
pointInTimeRecovery: true,
deletionProtection: isProd,
removalPolicy: isProd ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY,
});
// S3 bucket
const bucket = new s3.Bucket(this, "AssetsBucket", {
versioned: true,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
encryption: s3.BucketEncryption.S3_MANAGED,
deletionProtection: isProd,
removalPolicy: isProd ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY,
});
// Lambda function
const apiLambda = new lambda.Function(this, "ApiLambda", {
runtime: lambda.Runtime.NODEJS_20_X,
handler: "index.handler",
code: lambda.Code.fromAsset("src/lambda"),
environment: {
TABLE_NAME: table.tableName,
BUCKET_NAME: bucket.bucketName,
NODE_ENV: process.env.NODE_ENV ?? "development",
},
timeout: cdk.Duration.seconds(30),
memorySize: 512,
});
// Grant least-privilege permissions to Lambda
table.grantReadWriteData(apiLambda);
bucket.grantReadWrite(apiLambda);
// API Gateway
const api = new apigateway.RestApi(this, "AppApi", {
restApiName: "MyApp API",
defaultCorsPreflightOptions: {
allowOrigins: apigateway.Cors.ALL_ORIGINS,
allowMethods: apigateway.Cors.ALL_METHODS,
},
});
api.root.addProxy({
defaultIntegration: new apigateway.LambdaIntegration(apiLambda),
});
// CloudFront
const distribution = new cloudfront.Distribution(this, "Distribution", {
defaultBehavior: {
origin: new origins.S3Origin(bucket),
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
},
});
// Output
new cdk.CfnOutput(this, "ApiUrl", { value: api.url });
new cdk.CfnOutput(this, "CloudFrontUrl", { value: distribution.distributionDomainName });
}
}
Step 3: Updating Existing Stacks and Reviewing Diffs
# Review diff before deploying
npx cdk diff
# Have Claude Code explain the diff
claude -p "
Read this cdk diff output and explain what changed in plain English.
Pay special attention to security group and IAM policy changes.
$(npx cdk diff 2>&1)
"
Step 4: Designing a Multi-Stack Architecture
Production-scale infrastructure is managed by splitting it into multiple stacks.
claude -p "
Design the following multi-stack setup in CDK TypeScript.
[Stack Split Strategy]
- NetworkStack: VPC, subnets, security groups
- DatabaseStack: RDS, ElastiCache (depends on NetworkStack)
- ApplicationStack: ECS, ALB, AutoScaling (depends on NetworkStack, DatabaseStack)
- MonitoringStack: CloudWatch dashboards, alarms, SNS notifications
Include stack dependencies and how to use CfnOutput / Fn.importValue.
"
// bin/app.ts
import * as cdk from "aws-cdk-lib";
import { NetworkStack } from "../lib/network-stack";
import { DatabaseStack } from "../lib/database-stack";
import { ApplicationStack } from "../lib/application-stack";
import { MonitoringStack } from "../lib/monitoring-stack";
const app = new cdk.App();
const env = { account: process.env.CDK_ACCOUNT, region: "us-east-1" };
const network = new NetworkStack(app, "NetworkStack", { env });
const database = new DatabaseStack(app, "DatabaseStack", { env, vpc: network.vpc });
const application = new ApplicationStack(app, "ApplicationStack", {
env,
vpc: network.vpc,
database: database.cluster,
});
new MonitoringStack(app, "MonitoringStack", {
env,
alb: application.alb,
database: database.cluster,
});
Step 5: Letting Claude Code Handle CloudFormation/CDK Debugging
Claude Code can also help resolve deployment failures.
claude -p "
CDK deploy is failing with the following error.
Explain the cause and how to fix it:
$(npx cdk deploy 2>&1 | tail -30)
"
Common failure patterns:
- ROLLBACK_COMPLETE: Previous deployment failed, leaving the stack broken → run
cdk destroythen redeploy - UPDATE_ROLLBACK_FAILED: Conflict with manual changes → resolve manually in AWS Console
- Resource already exists: Name collision with an existing resource → rename the resource or import it
4 Common Pitfalls
1. Bypassing deletion protection with cdk destroy
In development environments, if you don’t set removalPolicy: DESTROY, resources will linger after cdk destroy. In production, use RETAIN to prevent accidental deletion.
2. Ignoring CloudFormation drift detection
When you change resources manually in the AWS Console, CloudFormation falls out of sync with reality (drift). Run drift detection in the CloudFormation Console once a month.
3. Writing secrets directly in templates
# Bad: password remains in template
DBPassword: "mysecretpassword"
# Good: retrieve from Secrets Manager or Parameter Store
DBPassword: !Sub "{{resolve:secretsmanager:prod/db-password}}"
4. Neglecting CDK version pinning
If CDK versions aren’t aligned across the team in package.json, diffs will appear. Commit package-lock.json to ensure everyone uses the same version.
Summary
| Task | Claude Code’s Contribution |
|---|---|
| CloudFormation generation | Complete YAML just from describing requirements |
| CDK implementation | Type-safe infrastructure code in TypeScript |
| Stack design | Multi-stack dependency architecture |
| Diff explanation | cdk diff output explained in plain English |
| Debugging | Root cause and fix from error logs |
Infrastructure as code is something we all know we should do but keep putting off. With Claude Code, you just describe “the AWS setup I need” and the template is done, dramatically lowering the barrier to adopting IaC.
Related Articles
- Claude Code × AWS Lambda Complete Guide
- Claude Code × AWS IAM Complete Guide
- Claude Code × AWS DynamoDB Complete Guide
References
Level up your Claude Code workflow
50 battle-tested prompt templates you can copy-paste into Claude Code right now.
Free PDF: Claude Code Cheatsheet in 5 Minutes
Just enter your email and we'll send you the single-page A4 cheatsheet right away.
We handle your data with care and never send spam.
About the Author
Masa
Engineer obsessed with Claude Code. Runs claudecode-lab.com, a 10-language tech media with 2,000+ pages.
Related Posts
Claude Code × Amazon Bedrock Complete Guide | Running Claude in Production on AWS
Complete guide to using Amazon Bedrock with Claude Code. From IAM authentication, streaming, Lambda integration, RAG implementation, to cost optimization — based on Masa's real production experience.
Claude Code × AWS CodePipeline/CodeBuild Complete Guide | Automate CI/CD Pipeline Build
Automatically build CI/CD with AWS CodePipeline & CodeBuild using Claude Code. Real code examples for pipeline design, buildspec.yml generation, test automation, and CDK infrastructure.
Claude Code × AWS CloudWatch Complete Guide | Log Analysis, Alarm Setup & Dashboard Automation
Boost AWS CloudWatch efficiency with Claude Code. Real-world code for log pattern analysis, automatic alarm configuration, metrics dashboards, and incident investigation.