Use Cases

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
ComparisonCloudFormationCDK
LanguageJSON / YAMLTypeScript, Python, Java, etc.
Type SafetyNoneExcellent (fully type-safe with TypeScript)
ReusabilityLow (copy-paste)High (abstracted with classes & functions)
Learning CurveLowMedium
Claude Code CompatibilityExcellentOutstanding

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 destroy then 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

TaskClaude Code’s Contribution
CloudFormation generationComplete YAML just from describing requirements
CDK implementationType-safe infrastructure code in TypeScript
Stack designMulti-stack dependency architecture
Diff explanationcdk diff output explained in plain English
DebuggingRoot 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.

References

#claude-code #aws #cloudformation #cdk #iac #typescript

Level up your Claude Code workflow

50 battle-tested prompt templates you can copy-paste into Claude Code right now.

Free

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.

Masa

About the Author

Masa

Engineer obsessed with Claude Code. Runs claudecode-lab.com, a 10-language tech media with 2,000+ pages.