8.5 — CDK: Cloud Development Kit
The AWS CDK lets you define infrastructure using real programming languages (TypeScript, Python, Java, C#, Go) instead of YAML. Under the hood, CDK generates CloudFormation templates.
CDK App (TypeScript / Python)
│
▼
cdk synth
│
▼
CloudFormation template (YAML)
│
▼
cdk deploy
│
▼
CloudFormation Stack → AWS Resources
Why CDK?
| CloudFormation YAML |
CDK (TypeScript) |
| Copy-paste for loops |
Use real for loops |
| No IDE autocomplete |
Full IDE support, type checking |
| 200 lines of YAML for a VPC |
new ec2.Vpc(this, 'MyVpc') — one line |
| Can’t unit test templates |
Write Jest/pytest tests for infrastructure |
Installation
# Install CDK CLI
$ npm install -g aws-cdk
# Verify
$ cdk --version
2.150.0 (build abc123)
Creating a CDK Project
$ mkdir my-infra && cd my-infra
# Initialize a TypeScript CDK project
$ cdk init app --language typescript
# Project structure:
# my-infra/
# ├── bin/
# │ └── my-infra.ts ← App entry point
# ├── lib/
# │ └── my-infra-stack.ts ← Your infrastructure
# ├── test/
# │ └── my-infra.test.ts ← Tests
# ├── cdk.json
# ├── package.json
# └── tsconfig.json
Example: VPC + ECS Fargate Service
import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as ecsPatterns from 'aws-cdk-lib/aws-ecs-patterns';
export class MyAppStack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// VPC with public and private subnets
const vpc = new ec2.Vpc(this, 'AppVpc', {
maxAzs: 2,
natGateways: 1,
});
// ECS Cluster
const cluster = new ecs.Cluster(this, 'AppCluster', {
vpc,
containerInsights: true,
});
// Fargate service behind an ALB (one construct!)
const service = new ecsPatterns.ApplicationLoadBalancedFargateService(
this, 'AppService', {
cluster,
cpu: 512,
memoryLimitMiB: 1024,
desiredCount: 2,
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry('nginx:latest'),
containerPort: 80,
},
publicLoadBalancer: true,
}
);
// Auto-scaling
const scaling = service.service.autoScaleTaskCount({
minCapacity: 2,
maxCapacity: 10,
});
scaling.scaleOnCpuUtilization('CpuScaling', {
targetUtilizationPercent: 70,
});
// Output the ALB URL
new cdk.CfnOutput(this, 'LoadBalancerUrl', {
value: service.loadBalancer.loadBalancerDnsName,
});
}
}
That single file creates: a VPC, subnets, NAT gateway, ECS cluster, Fargate task definition, ECS service, ALB, target group, security groups, and auto-scaling. In CloudFormation YAML, this would be 400+ lines.
CDK Workflow
# Bootstrap (once per account/region — creates CDK staging resources)
$ cdk bootstrap aws://123456789012/ap-south-1
# Synthesize — generate CloudFormation template
$ cdk synth
# Output: cdk.out/MyAppStack.template.json
# Diff — see what will change (like a change set)
$ cdk diff
Stack MyAppStack
Resources
[+] AWS::ECS::Service AppService/Service
[~] AWS::EC2::SecurityGroup AppService/LB/SecurityGroup
# Deploy
$ cdk deploy
# ✅ MyAppStack
# Outputs:
# MyAppStack.LoadBalancerUrl = MyApp-AppSe-abc123.ap-south-1.elb.amazonaws.com
# Destroy
$ cdk destroy
CDK Constructs — Three Levels
| Level |
Name |
Abstraction |
Example |
| L1 |
CFN Resources |
1:1 with CloudFormation |
CfnVPC, CfnBucket — raw, verbose |
| L2 |
Curated |
AWS-managed defaults + helper methods |
ec2.Vpc, s3.Bucket — most common |
| L3 |
Patterns |
Multi-resource patterns |
ApplicationLoadBalancedFargateService — magic |
Testing Infrastructure
import * as cdk from 'aws-cdk-lib';
import { Template } from 'aws-cdk-lib/assertions';
import { MyAppStack } from '../lib/my-infra-stack';
test('Creates ECS Cluster', () => {
const app = new cdk.App();
const stack = new MyAppStack(app, 'TestStack');
const template = Template.fromStack(stack);
template.resourceCountIs('AWS::ECS::Cluster', 1);
template.hasResourceProperties('AWS::ECS::Service', {
DesiredCount: 2,
});
});
Run tests:
$ npm test
PASS test/my-infra.test.ts
✓ Creates ECS Cluster (250ms)
| Scenario |
Recommendation |
| Small templates, quick setups |
CloudFormation YAML |
| Team knows TypeScript/Python |
CDK |
| Need loops, conditionals, functions |
CDK |
| Want unit-testable infrastructure |
CDK |
| Must follow strict YAML-only policy |
CloudFormation |
| Multi-cloud (Terraform better) |
Neither — use Terraform |
Common Mistakes
| Mistake |
Why It Hurts |
Fix |
Forgetting cdk bootstrap |
Deploy fails with staging bucket errors |
Run cdk bootstrap once per account/region |
Not running cdk diff before deploy |
Surprise deletions |
Always diff first |
| Using L1 constructs for everything |
Verbose, no defaults |
Use L2/L3 constructs — they set sensible defaults |
| Not writing tests |
Infra bugs caught in production |
Test with Template.fromStack() assertions |
Quick Reference
| Task |
Command |
| Init project |
cdk init app --language typescript |
| Bootstrap |
cdk bootstrap aws://ACCOUNT/REGION |
| Synth |
cdk synth |
| Diff |
cdk diff |
| Deploy |
cdk deploy |
| Destroy |
cdk destroy |
| List stacks |
cdk list |
Next → IaC Practice Exercises