AWS CDK in Practice: Components, Commands, and Versions

AWS CDK in Practice: Components, Commands, and Versions In the first part of this series, we covered the fundamentals of AWS CDK and why it's transforming how developers approach infrastructure as code. Now, let's dive deeper into the practical aspects of working with CDK by exploring its core components, essential commands, and version differences. Core Components of AWS CDK AWS CDK has three main building blocks that structure your infrastructure code: Source: AWS Black Belt Online Seminar App The App represents the top-level container in a CDK project. It serves as the entry point for defining stacks and their dependencies, even across multiple AWS regions. #!/usr/bin/env node import 'source-map-support/register'; import * as cdk from 'aws-cdk-lib'; // Import stacks import { Stack1 } from '../lib/stacks/stack1'; import { Stack2 } from '../lib/stacks/stack2'; const app = new cdk.App(); // Default region stack const stack1 = new Stack1(app, 'Stack1', { env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION, } }); // Tokyo region stack const stack2 = new Stack2(app, 'Stack2', { env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: "ap-northeast-1", } }); stack2.addDependency(stack1); // Define dependency relationship // US East region stack const stack3 = new Stack2(app, 'Stack3', { env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: "us-east-1", } }); stack3.addDependency(stack1); // Define dependency relationship The App is responsible for: Orchestrating multiple stacks Managing cross-stack dependencies Synthesizing CloudFormation templates Coordinating deployments across regions and accounts Stack A Stack corresponds directly to a CloudFormation stack and contains the resources you want to deploy together as a unit. Each CDK stack will become a separate CloudFormation stack in your AWS account. import { StackProps } from 'aws-cdk-lib'; import * as cdk from 'aws-cdk-lib'; import { Construct } from 'constructs'; import { aws_s3 as s3, aws_ec2 as ec2, aws_iam as iam, aws_kms as kms, } from 'aws-cdk-lib'; interface Stack1Props extends StackProps { readonly param1: string; readonly param2: string; } export class Stack1 extends cdk.Stack { constructor(scope: Construct, id: string, props: Stack1Props) { super(scope, id, props); // Stack resources are defined here // ... } } Stacks allow you to: Group related resources Manage deployment boundaries Control access and permissions Handle resource dependencies Construct The Construct is where AWS resources are defined within a Stack. This is where the real magic of AWS CDK happens, as it provides three different levels of abstraction: Source: AWS Black Belt Online Seminar L1 Constructs (Low-Level) L1 Constructs, also called CloudFormation Resource Constructs, have a one-to-one mapping with CloudFormation resources. They always begin with "Cfn" and provide the closest representation to raw CloudFormation. const bucket = new s3.CfnBucket(this, "MyBucket", { bucketName: "my-specific-bucket-name", }); While L1 constructs give you complete control, they require more verbose configuration and lack the built-in best practices of higher-level constructs. L2 Constructs (High-Level) L2 Constructs wrap L1 Constructs and provide intelligent defaults and higher-level methods. They significantly reduce the amount of code you need to write by generating reasonable defaults for properties you don't explicitly specify. const bucket = new s3.Bucket(this, "MyBucket", { versioned: true, // BucketName not required - CDK will generate a unique name }); L2 Constructs provide: Sensible defaults Methods that simplify common tasks Built-in best practices Integration with other AWS services This is the level at which you'll spend most of your time working with CDK. L3 Constructs (Patterns) L3 Constructs are high-level abstractions that can create multiple related resources to implement common architectural patterns. For example, the ECS Patterns module can create all the resources needed for a load-balanced Fargate service with just a few lines of code. // Create a cluster const cluster = new ecs.Cluster(this, "MyCluster", { vpc: props.vpc, }); // Deploy a load-balanced Fargate service with a single command const loadBalancedFargateService = new ecs_patterns.ApplicationLoadBalancedFargateService( this, "MyFargateService", { cluster: cluster, cpu: 4096, memoryLimitMiB: 30720, publicLoadBalancer: true, desiredCount: 6, taskImageOptions: { image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), }, } ); This single construct creates an ECS service, ALB, security groups, IAM roles, and more--typically equivalent to 200-300 lines of Clo

Mar 31, 2025 - 16:38
 0
AWS CDK in Practice: Components, Commands, and Versions

AWS CDK in Practice: Components, Commands, and Versions

In the first part of this series, we covered the fundamentals of AWS CDK and why it's transforming how developers approach infrastructure as code. Now, let's dive deeper into the practical aspects of working with CDK by exploring its core components, essential commands, and version differences.

Core Components of AWS CDK

AWS CDK has three main building blocks that structure your infrastructure code:

CDK Core Components

App

The App represents the top-level container in a CDK project. It serves as the entry point for defining stacks and their dependencies, even across multiple AWS regions.

#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';

// Import stacks
import { Stack1 } from '../lib/stacks/stack1';
import { Stack2 } from '../lib/stacks/stack2';

const app = new cdk.App();

// Default region stack
const stack1 = new Stack1(app, 'Stack1', {
  env: {
    account: process.env.CDK_DEFAULT_ACCOUNT,
    region: process.env.CDK_DEFAULT_REGION,
  }
});

// Tokyo region stack
const stack2 = new Stack2(app, 'Stack2', {
  env: {
    account: process.env.CDK_DEFAULT_ACCOUNT,
    region: "ap-northeast-1",
  }
});
stack2.addDependency(stack1); // Define dependency relationship

// US East region stack
const stack3 = new Stack2(app, 'Stack3', {
  env: {
    account: process.env.CDK_DEFAULT_ACCOUNT,
    region: "us-east-1",
  }
});
stack3.addDependency(stack1); // Define dependency relationship

The App is responsible for:

  • Orchestrating multiple stacks
  • Managing cross-stack dependencies
  • Synthesizing CloudFormation templates
  • Coordinating deployments across regions and accounts

Stack

A Stack corresponds directly to a CloudFormation stack and contains the resources you want to deploy together as a unit. Each CDK stack will become a separate CloudFormation stack in your AWS account.

import { StackProps } from 'aws-cdk-lib';
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import {
  aws_s3 as s3,
  aws_ec2 as ec2, 
  aws_iam as iam,
  aws_kms as kms,
} from 'aws-cdk-lib';

interface Stack1Props extends StackProps {
  readonly param1: string;
  readonly param2: string;
}

export class Stack1 extends cdk.Stack {
  constructor(scope: Construct, id: string, props: Stack1Props) {
    super(scope, id, props);

    // Stack resources are defined here
    // ...
  }
}

Stacks allow you to:

  • Group related resources
  • Manage deployment boundaries
  • Control access and permissions
  • Handle resource dependencies

Construct

The Construct is where AWS resources are defined within a Stack. This is where the real magic of AWS CDK happens, as it provides three different levels of abstraction:

CDK Construct Levels

L1 Constructs (Low-Level)

L1 Constructs, also called CloudFormation Resource Constructs, have a one-to-one mapping with CloudFormation resources. They always begin with "Cfn" and provide the closest representation to raw CloudFormation.

const bucket = new s3.CfnBucket(this, "MyBucket", {
  bucketName: "my-specific-bucket-name",
});

While L1 constructs give you complete control, they require more verbose configuration and lack the built-in best practices of higher-level constructs.

L2 Constructs (High-Level)

L2 Constructs wrap L1 Constructs and provide intelligent defaults and higher-level methods. They significantly reduce the amount of code you need to write by generating reasonable defaults for properties you don't explicitly specify.

const bucket = new s3.Bucket(this, "MyBucket", {
  versioned: true,
  // BucketName not required - CDK will generate a unique name
});

L2 Constructs provide:

  • Sensible defaults
  • Methods that simplify common tasks
  • Built-in best practices
  • Integration with other AWS services

This is the level at which you'll spend most of your time working with CDK.

L3 Constructs (Patterns)

L3 Constructs are high-level abstractions that can create multiple related resources to implement common architectural patterns. For example, the ECS Patterns module can create all the resources needed for a load-balanced Fargate service with just a few lines of code.

// Create a cluster
const cluster = new ecs.Cluster(this, "MyCluster", {
  vpc: props.vpc,
});

// Deploy a load-balanced Fargate service with a single command
const loadBalancedFargateService = 
  new ecs_patterns.ApplicationLoadBalancedFargateService(
    this,
    "MyFargateService",
    {
      cluster: cluster,
      cpu: 4096,
      memoryLimitMiB: 30720,
      publicLoadBalancer: true,
      desiredCount: 6,
      taskImageOptions: {
        image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
      },
    }
  );

This single construct creates an ECS service, ALB, security groups, IAM roles, and more--typically equivalent to 200-300 lines of CloudFormation code.

L3 Constructs are perfect when they align with your requirements, but they're less customizable and fewer are available, so most CDK development will primarily use L2 Constructs.

Essential CDK Commands

Here are the key commands you'll use when working with AWS CDK:

cdk init

This command initializes a new CDK project in your current directory. You need to specify the language you want to use:

cdk init app --language typescript

This creates the basic folder structure and configuration files needed for a CDK project.

cdk bootstrap

Before deploying CDK applications to an AWS environment for the first time, you need to bootstrap it:

cdk bootstrap

Bootstrapping creates a CloudFormation stack called "CDKToolkit" in your AWS account, which includes resources that CDK needs for deployment:

  • An S3 bucket for storing templates and assets
  • IAM roles for deployment operations
  • ECR repositories for container images
  • SSM parameters for version tracking

This only needs to be done once per account/region combination.

cdk deploy

This command deploys your CDK stacks to AWS:

cdk deploy --all                # Deploy all stacks
# OR
cdk deploy Stack1 Stack2        # Deploy specific stacks

Behind the scenes, cdk deploy:

  1. Synthesizes CloudFormation templates
  2. Uploads assets to the bootstrap bucket
  3. Initiates CloudFormation stack deployments
  4. Monitors deployment progress

cdk diff

This command compares your current CDK code with the deployed stacks:

cdk diff --all                  # Check differences for all stacks
# OR
cdk diff Stack1 Stack2          # Check specific stacks

This is invaluable for understanding what will change before you deploy.

cdk synth

This command synthesizes CloudFormation templates from your CDK code:

cdk synth                       # Generate CloudFormation templates
# OR
cdk synth --path-metadata false # Generate without metadata
# OR
cdk synth -q                    # Quiet mode (only show console.log output)

This is useful for:

  • Checking the generated CloudFormation before deployment
  • Creating templates for review or manual deployment
  • Debugging resource configurations

cdk destroy

This command removes deployed stacks:

cdk destroy --all               # Destroy all stacks
# OR
cdk destroy Stack1 Stack2       # Destroy specific stacks
# OR
cdk destroy --force             # Force destruction (ignore dependencies)

Always use this command with caution, especially in production environments.

cdk list / cdk ls

This command lists all stacks in your CDK application:

cdk list
# OR
cdk ls

CDK Versions

CDK v2

The current version of AWS CDK is v2, which became generally available in December 2021. It introduced significant improvements over v1, most notably the consolidation of all construct libraries into a single package: aws-cdk-lib.

In CDK v1, you had to install separate packages for each AWS service:

// CDK v1 approach
npm install @aws-cdk/aws-lambda
npm install @aws-cdk/aws-cloudfront
npm install @aws-cdk/aws-iam
npm install @aws-cdk/aws-s3

import * as cdk from "@aws-cdk/core";
import * as cloudfront from "@aws-cdk/aws-cloudfront";
import * as s3 from "@aws-cdk/aws-s3";
import * as iam from "@aws-cdk/aws-iam";

This created version compatibility challenges when packages were installed at different times.

CDK v2 simplifies things considerably:

// CDK v2 approach
npm install aws-cdk-lib

import {
  aws_s3 as s3,
  aws_iam as iam,
  aws_cloudfront as cloudfront,
} from "aws-cdk-lib";

Recent Change: CLI and Library Separation

As of February 2025, AWS has separated the CDK CLI from the construct libraries:

// Before the split
"devDependencies": {
  "aws-cdk": "2.174.0"
},
"dependencies": {
  "aws-cdk-lib": "2.174.0"
}

// After the split
"devDependencies": {
  "aws-cdk": "2.1000.0" // CLI now follows a new versioning scheme
},
"dependencies": {
  "aws-cdk-lib": "2.180.0" // Construct library continues with the original versioning
}

To minimize the impact of this separation:

  1. Pin specific versions in CI/CD environments
  2. Manage dependencies carefully
  3. Use peerDependencies to manage relationships

Construct IDs: An Important Detail

When defining resources in CDK, you assign each construct a logical ID, which becomes part of the CloudFormation logical ID in the generated template:

const bucket = new s3.Bucket(this, "LogsBucket", {});

In this example, "LogsBucket" is the Construct ID. This ID:

  • Must be unique within its scope
  • Is used to generate the CloudFormation logical ID (e.g., "LogsBucket9C4D8843")
  • Should follow PascalCase convention
  • Should be simple and descriptive
  • Should avoid unnecessary repetition in nested constructs

Conclusion

Understanding the core components, commands, and versioning of AWS CDK is essential for effective infrastructure development. The App, Stack, and Construct hierarchy provides a flexible framework for organizing your infrastructure code, while the different construct levels let you choose the right abstraction for each task.

CDK v2's unified package approach simplifies dependency management, and the essential commands give you fine-grained control over the development and deployment lifecycle.

In the next article, we'll explore AWS CDK development workflow, best practices for project structure, and strategies for organizing your CDK code.

Have you migrated from CloudFormation to CDK or from CDK v1 to v2? What challenges did you face? Share your experiences in the comments!

This article is part of the "Mastering AWS CDK" series, where we explore all aspects of using the AWS Cloud Development Kit for infrastructure as code.