AWS for DevOps — CDK: Cloud Development Kit

3 minute read

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)

CDK vs CloudFormation — When to Use What

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

Updated: