# AWS Cloud Development Kit (CDK) Developer Guide The AWS Cloud Development Kit (AWS CDK) is an open-source software development framework for defining cloud infrastructure in code and provisioning it through AWS CloudFormation. The CDK consists of two primary parts: the AWS CDK Construct Library (a collection of pre-written modular and reusable pieces of code called constructs) and the AWS CDK Toolkit (CLI tools for managing and interacting with CDK apps). You compose constructs together into stacks and apps, then deploy through AWS CloudFormation. The AWS CDK supports TypeScript, JavaScript, Python, Java, C#/.Net, and Go. You can use any of these languages to define reusable cloud components known as constructs, compose them into stacks and apps, and deploy to provision or update your AWS resources. The CDK provides three levels of constructs: L1 (low-level CloudFormation resources), L2 (curated constructs with sensible defaults), and L3 (patterns representing complete AWS architectures). ## CDK CLI Installation Install the AWS CDK CLI globally using npm to interact with your CDK applications. ```bash # Install the latest version globally npm install -g aws-cdk # Install a specific version npm install -g aws-cdk@2.100.0 # Verify installation cdk --version # For local project installation (without -g) npm install aws-cdk npx aws-cdk --version ``` ## Creating a New CDK Project Initialize a new CDK project with the desired language template. ```bash # Create a new directory and initialize TypeScript project mkdir my-cdk-app cd my-cdk-app cdk init app --language typescript # Initialize with other languages cdk init app --language python cdk init app --language java cdk init app --language csharp cdk init app --language go # Initialize with sample-app template (includes SQS queue and SNS topic) cdk init sample-app --language typescript ``` ## Defining a CDK Stack A CDK stack is the smallest unit of deployment representing a collection of AWS resources. Define stacks by extending the Stack class. ```typescript // lib/my-stack.ts import * as cdk from 'aws-cdk-lib'; import * as s3 from 'aws-cdk-lib/aws-s3'; import * as lambda from 'aws-cdk-lib/aws-lambda'; import { Construct } from 'constructs'; export class MyStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // Create an S3 bucket with versioning enabled const bucket = new s3.Bucket(this, 'MyBucket', { versioned: true, encryption: s3.BucketEncryption.S3_MANAGED, removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, }); // Create a Lambda function const fn = new lambda.Function(this, 'MyFunction', { runtime: lambda.Runtime.NODEJS_18_X, handler: 'index.handler', code: lambda.Code.fromInline(` exports.handler = async (event) => { return { statusCode: 200, body: 'Hello from Lambda!' }; }; `), environment: { BUCKET_NAME: bucket.bucketName, }, }); // Grant Lambda read access to the bucket bucket.grantRead(fn); // Output the bucket name new cdk.CfnOutput(this, 'BucketName', { value: bucket.bucketName, }); } } ``` ## Creating a CDK App The App construct is the root of every CDK application. Define your app in the application file. ```typescript // bin/my-app.ts import * as cdk from 'aws-cdk-lib'; import { MyStack } from '../lib/my-stack'; const app = new cdk.App(); // Create a stack in the default environment new MyStack(app, 'MyStack'); // Create stacks with specific environments new MyStack(app, 'DevStack', { env: { account: '123456789012', region: 'us-east-1' }, stackName: 'my-dev-stack', }); new MyStack(app, 'ProdStack', { env: { account: '987654321098', region: 'us-west-2' }, stackName: 'my-prod-stack', }); // Use environment variables for dynamic configuration new MyStack(app, 'DynamicStack', { env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION, }, }); app.synth(); ``` ## Bootstrapping an AWS Environment Bootstrap deploys the CDKToolkit stack to provision resources needed for CDK deployments. ```bash # Bootstrap the default environment cdk bootstrap # Bootstrap a specific account/region cdk bootstrap aws://123456789012/us-east-1 # Bootstrap with a specific profile cdk bootstrap --profile prod # Bootstrap multiple environments cdk bootstrap aws://123456789012/us-east-1 aws://123456789012/us-west-2 # Bootstrap with cross-account trust cdk bootstrap aws://123456789012/us-east-1 \ --trust 234567890123 \ --cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess # Bootstrap with custom qualifier (for multiple CDK apps in same account) cdk bootstrap --qualifier myapp123 # Export bootstrap template for customization cdk bootstrap --show-template > bootstrap-template.yaml # Use custom bootstrap template cdk bootstrap --template my-bootstrap-template.yaml ``` ## Synthesizing CloudFormation Templates Synthesize your CDK app to generate CloudFormation templates without deploying. ```bash # Synthesize all stacks cdk synth # Synthesize a specific stack cdk synth MyStack # Synthesize multiple stacks cdk synth Stack1 Stack2 # Output in JSON format cdk synth --json MyStack # Output to custom directory cdk synth --output=./templates # Pass context values during synthesis cdk synth --context key1=value1 --context key2=value2 MyStack # Synthesize with different context per stack cdk synth --context Stack1:env=dev --context Stack2:env=prod Stack1 Stack2 ``` ## Deploying CDK Stacks Deploy one or more stacks to your AWS environment. ```bash # Deploy a single stack cdk deploy MyStack # Deploy all stacks cdk deploy --all # Deploy multiple specific stacks cdk deploy Stack1 Stack2 # Deploy with auto-approve (no confirmation prompts) cdk deploy --require-approval never # Deploy without rollback on failure (faster iteration) cdk deploy --no-rollback # Deploy with hotswap for faster Lambda updates cdk deploy --hotswap # Deploy with watch mode (continuous deployment on file changes) cdk deploy --watch # Deploy with parameters cdk deploy --parameters MyStack:BucketName=my-bucket # Deploy with outputs written to file cdk deploy --outputs-file outputs.json # Deploy with specific profile cdk deploy --profile prod # Deploy to multiple stacks in parallel cdk deploy --concurrency 3 --all # Deploy without creating change set (faster) cdk deploy --method=direct # Prepare change set without deploying cdk deploy --method=prepare-change-set --change-set-name=my-changeset ``` ## Comparing Stack Changes Use cdk diff to compare your local changes with the deployed stack. ```bash # Compare all stacks cdk diff # Compare a specific stack cdk diff MyStack # Compare against a saved template cdk diff --template ~/templates/MyStack.old.yaml MyStack # Compare with context values cdk diff --context env=prod MyStack ``` ## Listing Stacks List all stacks defined in your CDK application. ```bash # List all stacks cdk list cdk ls # List with detailed information cdk list --long # List stacks matching a pattern cdk list "*Stack" # List all stacks including nested in CDK Pipelines cdk list '**' ``` ## Working with L1 Constructs (CloudFormation Resources) L1 constructs map directly to CloudFormation resources with the Cfn prefix. ```typescript import * as cdk from 'aws-cdk-lib'; import * as s3 from 'aws-cdk-lib/aws-s3'; // Create an L1 S3 bucket construct const cfnBucket = new s3.CfnBucket(this, 'MyCfnBucket', { bucketName: 'my-bucket-name', versioningConfiguration: { status: 'Enabled', }, corsConfiguration: { corsRules: [{ allowedOrigins: ['*'], allowedMethods: ['GET', 'PUT'], allowedHeaders: ['*'], maxAge: 3000, }], }, lifecycleConfiguration: { rules: [{ id: 'ExpireOldVersions', status: 'Enabled', noncurrentVersionExpiration: { noncurrentDays: 90, }, }], }, }); // Access CloudFormation attributes const bucketArn = cfnBucket.attrArn; const bucketDomainName = cfnBucket.attrDomainName; ``` ## Working with L2 Constructs (Curated Constructs) L2 constructs provide higher-level abstractions with sensible defaults and helper methods. ```typescript import * as cdk from 'aws-cdk-lib'; import * as s3 from 'aws-cdk-lib/aws-s3'; import * as iam from 'aws-cdk-lib/aws-iam'; import * as lambda from 'aws-cdk-lib/aws-lambda'; import * as sqs from 'aws-cdk-lib/aws-sqs'; // Create an L2 S3 bucket with encryption and lifecycle rules const bucket = new s3.Bucket(this, 'MyBucket', { versioned: true, encryption: s3.BucketEncryption.KMS, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, lifecycleRules: [{ expiration: cdk.Duration.days(365), transitions: [{ storageClass: s3.StorageClass.INFREQUENT_ACCESS, transitionAfter: cdk.Duration.days(30), }], }], }); // Create a Lambda function with code from asset const fn = new lambda.Function(this, 'MyFunction', { runtime: lambda.Runtime.NODEJS_18_X, handler: 'index.handler', code: lambda.Code.fromAsset('./lambda'), timeout: cdk.Duration.seconds(30), memorySize: 256, }); // Create an SQS queue const queue = new sqs.Queue(this, 'MyQueue', { visibilityTimeout: cdk.Duration.seconds(300), retentionPeriod: cdk.Duration.days(14), }); // Grant permissions using helper methods bucket.grantReadWrite(fn); queue.grantSendMessages(fn); // Add environment variables fn.addEnvironment('BUCKET_NAME', bucket.bucketName); fn.addEnvironment('QUEUE_URL', queue.queueUrl); ``` ## Working with L3 Constructs (Patterns) L3 constructs represent complete architectures for common use cases. ```typescript 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 ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; // Create a VPC const vpc = new ec2.Vpc(this, 'MyVpc', { maxAzs: 3, }); // Create an ECS cluster const cluster = new ecs.Cluster(this, 'MyCluster', { vpc: vpc, }); // Create a load-balanced Fargate service using L3 pattern const fargateService = new ecs_patterns.ApplicationLoadBalancedFargateService( this, 'MyFargateService', { cluster: cluster, cpu: 512, desiredCount: 2, memoryLimitMiB: 1024, taskImageOptions: { image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), containerPort: 80, environment: { NODE_ENV: 'production', }, }, publicLoadBalancer: true, healthCheckGracePeriod: cdk.Duration.seconds(60), } ); // Configure auto-scaling const scaling = fargateService.service.autoScaleTaskCount({ minCapacity: 2, maxCapacity: 10, }); scaling.scaleOnCpuUtilization('CpuScaling', { targetUtilizationPercent: 70, }); // Output the load balancer URL new cdk.CfnOutput(this, 'LoadBalancerDNS', { value: fargateService.loadBalancer.loadBalancerDnsName, }); ``` ## Managing IAM Permissions Use the IAM module to manage access control and permissions. ```typescript import * as iam from 'aws-cdk-lib/aws-iam'; import * as s3 from 'aws-cdk-lib/aws-s3'; // Create an IAM role with service principal const role = new iam.Role(this, 'MyRole', { assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), managedPolicies: [ iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), ], }); // Add inline policy to role role.addToPolicy(new iam.PolicyStatement({ effect: iam.Effect.ALLOW, actions: ['s3:GetObject', 's3:PutObject'], resources: ['arn:aws:s3:::my-bucket/*'], })); // Create a bucket and grant permissions const bucket = new s3.Bucket(this, 'MyBucket'); // Grant read access to a role bucket.grantRead(role); // Grant read/write access bucket.grantReadWrite(role); // Add resource policy to bucket bucket.addToResourcePolicy(new iam.PolicyStatement({ effect: iam.Effect.ALLOW, actions: ['s3:GetObject'], resources: [bucket.arnForObjects('*')], principals: [new iam.AccountPrincipal('123456789012')], })); // Reference existing IAM resources const existingRole = iam.Role.fromRoleArn(this, 'ExistingRole', 'arn:aws:iam::123456789012:role/existing-role' ); const existingPolicy = iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonS3ReadOnlyAccess'); ``` ## Creating Custom Constructs Build reusable infrastructure components by creating custom constructs. ```typescript import { Construct } from 'constructs'; import * as s3 from 'aws-cdk-lib/aws-s3'; import * as sns from 'aws-cdk-lib/aws-sns'; import * as s3_notifications from 'aws-cdk-lib/aws-s3-notifications'; export interface NotifyingBucketProps { prefix?: string; bucketProps?: s3.BucketProps; } export class NotifyingBucket extends Construct { public readonly bucket: s3.Bucket; public readonly topic: sns.Topic; constructor(scope: Construct, id: string, props: NotifyingBucketProps = {}) { super(scope, id); // Create the bucket this.bucket = new s3.Bucket(this, 'Bucket', { ...props.bucketProps, }); // Create SNS topic for notifications this.topic = new sns.Topic(this, 'Topic'); // Add object created notification this.bucket.addObjectCreatedNotification( new s3_notifications.SnsDestination(this.topic), { prefix: props.prefix } ); } } // Usage in a stack const notifyingBucket = new NotifyingBucket(this, 'MyNotifyingBucket', { prefix: 'uploads/', bucketProps: { versioned: true, }, }); // Subscribe to the topic notifyingBucket.topic.addSubscription( new sns_subscriptions.EmailSubscription('admin@example.com') ); ``` ## Working with Nested Stacks Use nested stacks to organize resources and work around the CloudFormation 500-resource limit. ```typescript import * as cdk from 'aws-cdk-lib'; import { Construct } from 'constructs'; // Define a nested stack class DatabaseStack extends cdk.NestedStack { public readonly dbEndpoint: string; constructor(scope: Construct, id: string, props?: cdk.NestedStackProps) { super(scope, id, props); // Define database resources here this.dbEndpoint = 'db.example.com'; } } // Use nested stack in parent stack class MainStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); const dbStack = new DatabaseStack(this, 'Database'); // Use outputs from nested stack new cdk.CfnOutput(this, 'DatabaseEndpoint', { value: dbStack.dbEndpoint, }); } } ``` ## Using Context and Environment Variables Pass runtime configuration using context values and environment variables. ```typescript import * as cdk from 'aws-cdk-lib'; // Access context values in your stack const envName = this.node.tryGetContext('env') || 'dev'; const instanceType = this.node.tryGetContext('instanceType') || 't3.micro'; // cdk.json configuration // { // "app": "npx ts-node bin/app.ts", // "context": { // "env": "prod", // "instanceType": "t3.large" // } // } // Command line context // cdk deploy --context env=staging --context instanceType=t3.medium // Environment-based configuration const isProd = envName === 'prod'; const config = { instanceCount: isProd ? 3 : 1, enableLogging: isProd, removalPolicy: isProd ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, }; ``` ## Importing Existing Resources Import existing AWS resources into your CDK stack. ```bash # Import existing resources during deployment cdk deploy --import-existing-resources ``` ```typescript import * as s3 from 'aws-cdk-lib/aws-s3'; import * as ec2 from 'aws-cdk-lib/aws-ec2'; // Import existing S3 bucket by name const existingBucket = s3.Bucket.fromBucketName(this, 'ExistingBucket', 'my-existing-bucket'); // Import existing bucket by ARN const bucketByArn = s3.Bucket.fromBucketArn(this, 'BucketByArn', 'arn:aws:s3:::my-existing-bucket' ); // Import existing VPC by ID const existingVpc = ec2.Vpc.fromLookup(this, 'ExistingVpc', { vpcId: 'vpc-12345678', }); // Import VPC by tags const vpcByTags = ec2.Vpc.fromLookup(this, 'VpcByTags', { tags: { Environment: 'Production', }, }); // Use imported resources new lambda.Function(this, 'MyFunction', { vpc: existingVpc, // ...other props }); ``` ## Stack Outputs and Cross-Stack References Share values between stacks using outputs and cross-stack references. ```typescript // In StackA - export values export class StackA extends cdk.Stack { public readonly bucket: s3.Bucket; public readonly bucketArn: string; constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); this.bucket = new s3.Bucket(this, 'SharedBucket'); this.bucketArn = this.bucket.bucketArn; // Create explicit output new cdk.CfnOutput(this, 'BucketArnOutput', { value: this.bucket.bucketArn, exportName: 'SharedBucketArn', }); } } // In StackB - import values export class StackB extends cdk.Stack { constructor(scope: Construct, id: string, stackA: StackA, props?: cdk.StackProps) { super(scope, id, props); // Reference directly (automatic cross-stack reference) const fn = new lambda.Function(this, 'MyFunction', { environment: { BUCKET_NAME: stackA.bucket.bucketName, }, // ...other props }); // Grant permissions across stacks stackA.bucket.grantRead(fn); } } // In app file const stackA = new StackA(app, 'StackA'); const stackB = new StackB(app, 'StackB', stackA); // StackB will automatically depend on StackA ``` The AWS CDK transforms how teams build and manage cloud infrastructure by bringing the full power of programming languages to infrastructure definition. Common use cases include creating reusable infrastructure components, implementing multi-account deployment strategies, building CI/CD pipelines with CDK Pipelines, and developing custom L3 constructs for organization-specific patterns. Integration patterns typically involve combining CDK with existing DevOps workflows: using `cdk synth` in CI pipelines to generate and validate templates, leveraging `cdk diff` for change review processes, and implementing progressive deployment strategies with CDK Pipelines. The CDK's ability to share constructs as npm/pip/maven packages enables infrastructure code reuse across teams and organizations, while its native CloudFormation integration ensures compatibility with existing AWS tooling and governance frameworks.