learn AWS Cloud Development Kit (CDK)

see kitchen-sink/


  • express resources using general purpose programming languages (ts/js/python/java/C#)
  • constructs - construct levels 1, 2, 3. cfn (L1), CDK (L2), pattern/solution (L3)
  • synth to cfn
  • cloud assemblies - cfn + source code, docker images, assets (s3)
  • aspects - ability to visit each node/resource in stack and apply changes
  • Application -> Stacks -> Constructs
  • Parameters - cfn parameters. can pass in via cdk synth.
  • Runtime context - key-value pairs that can be associated with a stack or construct. Can only be string values (kind of like parameters)
  • [tf|k8s] CDKs
  • jsii - core/foundational tech for multi-language/polyglot support. bind any language to underlying typescript implementation.
  • CDK pipelines for CI/CD
  • Custom Logical Names - shows how to hook into an provide own resource names. Can be used for IAM policies based on resource name prefixes
  • Usage with Permissions Boundaries - class PermissionsBoundary · AWS CDK. e.g. PermissionsBoundary.of(this).apply(permissionsBoundariesPolicy);

Key Files and Directories

  • bin - entry point to CDK app. imports 1 or more stacks from lib folder
  • lib/*-stack.* - define stacks here which contain constructs
  • cdk.json - cdk configuration
  • test - unit/integration/e2e tests
  • cdk.out - CDK assembly / synth output (cfn, assets, etc.)

Common Areas

  • @aws-cdk/core - App, Stack, Stack.account, Stack.region, Stack.tags, Stack.terminationProtection, Construct, Duration, CfnOutput
  • lambda.Function, lambda.Code.fromAsset, lambda.Code.fromInline
  • @aws-cdk/aws-iam - Role, User, Group, PolicyStatement

Common Steps

# init cdk app
cdk init app --language javascript
cdk init app --language typescript

# list stacks in the app
cdk list

# [optional] build for ts -> js.  not required for js
npm run build

# synthesize to cfn (`cdk.out`)
cdk synth

# run tests
npm test

# compare the specified stack with the deployed stack
cdk diff

# deploy
cdk deploy

# force deploy, no prompt
cdk deploy  --force --require-approval never

# delete
cdk destroy [STACKS..]

CDK Internals

Details on the inner workings of CDK.

CDK Tree

Core of CDK is based on tree structure similar to the DOM.

  • node: ConstructNode - accessed via this.node - root of the tree.

  • node.children

  • node.findChild(id: string) - search for child in tree with id. new s3.Bucket(this, "Assets"). "Assets" is the id.

  • node.tryFindChild(id: string) - same as findChild but won’t throw if id doesn’t exist. Will return undefined.

  • node.defaultChild - reference to primary level 1 construct (Cfn*)

  • common L1 construct methods

    • overrideLogicalId
    • addOverride(propertyPath, value) - e.g. cfnBucket.addOverride("Properties.BucketName", "my-bucket-name-01")
      • need to use for cfn attributes that are outside of the Properties. e.g. DeletionPolicy
      • add bucket name to Metadata - cfnBucket.addOverride("Metadata.BucketName", cfnBucket.ref)
    • addDeletionOverride(propertyPath) - remove a property. e.g. cfnBucket.addDeletionOverride("Properties.BucketName")
    • addPropertyOverride(propertyPath) - cfnBucket.addPropertyOverride("Properties.BucketName", "my-bucket-name-01")
  • all Fn::GetAtt return values can be accessed via a property named Att${Return Value Name}. e.g. cfnBucket.AttArn, cfnBucket.AttDomainName

CDK Path

  • every resource defined in CDK tree has a path
  • this.node.path - concatenation of path traversal to the node in the CDK tree. (e.g. MyStack/Assets)
  • this path is added to the Metadata property for each resource in cfn.

CDK Identifiers

  • Logical ID - used in cfn template. scoped within stack. calculated using CDK path (sanitized id (no stack id) + hash)
  • Unique ID - uniquely identify resource within CDK application. scoped within CDK application, which may be composed of multiple stacks. sanitized id (including stack) + hash
CDK Identifiers Calculation Image

Common CDK Snippets

// durations
Duration.seconds(300)   // 5 minutes
Duration.minutes(5)     // 5 minutes
Duration.hours(1)       // 1 hour
Duration.days(7)        // 7 days
Duration.parse('PT5M')  // 5 minutes

// sizes
Size.kibibytes(200) // 200 KiB
Size.mebibytes(5)   // 5 MiB
Size.gibibytes(40)  // 40 GiB
Size.tebibytes(200) // 200 TiB
Size.pebibytes(3)   // 3 PiB

// create secret
const secret = SecretValue.secretsManager('secretId', {
  jsonField: 'password', // optional: key of a JSON field to retrieve (defaults to all content),
  versionId: 'id',       // optional: id of the version (default AWSCURRENT)
  versionStage: 'stage', // optional: version stage name (default AWSCURRENT)

// get default VPC
const vpc = ec2.Vpc.fromLookup(stack, 'VPC', {
  // This imports the default VPC but you can also
  // specify a 'vpcName' or 'tags'.
  isDefault: true,

// custom resource
const fn = new lambda.Function(this, 'MyProvider', functionProps);

new CustomResource(this, 'MyResource', {
  serviceToken: fn.functionArn,

// OR

const serviceToken = CustomResourceProvider.getOrCreate(this, 'Custom::MyCustomResourceType', {
  codeDirectory: `${__dirname}/my-handler`,
  runtime: CustomResourceProviderRuntime.NODEJS_12_X,
  description: "Lambda function created by the custom resource provider",

new CustomResource(this, 'MyResource', {
  resourceType: 'Custom::MyCustomResourceType',
  serviceToken: serviceToken

// bastion host
const host = new ec2.BastionHostLinux(this, 'BastionHost', { vpc });