AWS Cloud Development Kit

CDK constructs generate CloudFormation templates from code. It is an infrastructure programming model, not a way around template lifecycle, IAM permissions or deployment review.

Prerequisites: CloudFormation, CLI profiles and the selected language/runtime.

Constructs, context and synthesis

Apps contain stacks; constructs model resources and higher-level patterns. Distinguish L1 resource definitions from L2/L3 abstractions, and inspect generated defaults rather than assuming they match production security. Pin dependencies and runtime/toolchain versions.

Commit relevant cdk.context.json to source control for reproducible synthesis; it caches lookup values, not credentials. Review context when environments change. Never commit secret values. An isolated synth run can still need valid lookup context/credentials unless the app is designed to avoid lookups.

Bootstrap resources support deployment/assets and need scoped trust/permissions. Cross-account/Region deployment should declare its environment and role path explicitly. Custom resources can call APIs beyond standard CloudFormation resources, but introduce permissions, retry/idempotency and deletion responsibilities.

Assertions and deployment lifecycle

Use the current CDK assertions library for the pinned language/version. In TypeScript, resourceCountIs(type, count) counts resources; hasResource(type, 1) is not the correct count assertion. Test security-sensitive generated properties, not only successful synthesis.

npx cdk synth
npx cdk diff

These CLI examples require an existing CDK project and pinned local dependencies. They are not a complete application and may perform context lookups. Review logical-ID changes, replacements, permissions and retained resources before deployment.

Review/rollback exercise

Create a minimal sandbox app following the official language-specific starter, with a budget and a nonproduction deployment role. Add one resource and assert its count plus security property. Synthesize and inspect the CloudFormation output; deliberately weaken a property and confirm the test fails.

Diff a harmless change, review it and deploy only into the authorized sandbox. Inspect stack events/outputs and compare desired versus actual state. Destroy the sandbox stack only after checking retention/deletion policies; inspect retained assets and bootstrap resources separately. Do not remove shared bootstrapping used by other stacks as generic cleanup.

Constructs and a concrete minimal app

CDK L1 constructs expose CloudFormation resources; higher-level constructs add defaults/abstractions. Inspect synthesized output and assertions rather than assuming a high-level construct creates every application security dependency.

This TypeScript app is for an initialized project with a pinned CDK v2/toolchain. It creates a versioned, public-access-blocked bucket retained on stack deletion. Retention is deliberate: cleanup requires reviewing/removing disposable object versions separately.

import { App, RemovalPolicy, Stack, StackProps } from "aws-cdk-lib";
import { BlockPublicAccess, Bucket } from "aws-cdk-lib/aws-s3";
import { Construct } from "constructs";
 
export class SandboxStorageStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);
    new Bucket(this, "SandboxBucket", {
      versioned: true,
      blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
      removalPolicy: RemovalPolicy.RETAIN,
    });
  }
}
 
const app = new App();
new SandboxStorageStack(app, "SandboxStorageStack");

This is an app source example, not a complete package/CI configuration or a deployed stack. Pin package versions, declare the target environment when needed and verify synthesis context/bootstrap requirements.

Correct assertions and test workflow

In the matching initialized test project:

import { App } from "aws-cdk-lib";
import { Template } from "aws-cdk-lib/assertions";
import { SandboxStorageStack } from "../lib/sandbox-storage-stack";
 
test("bucket is versioned and blocks public access", () => {
  const stack = new SandboxStorageStack(new App(), "TestStack");
  const template = Template.fromStack(stack);
  template.resourceCountIs("AWS::S3::Bucket", 1);
  template.hasResourceProperties("AWS::S3::Bucket", {
    VersioningConfiguration: { Status: "Enabled" },
    PublicAccessBlockConfiguration: {
      BlockPublicAcls: true,
      BlockPublicPolicy: true,
      IgnorePublicAcls: true,
      RestrictPublicBuckets: true,
    },
  });
});

The import path/test runner must match your project. Counting uses resourceCountIs, while properties use hasResourceProperties. Unit assertions cannot prove live permissions, network reachability or recovery.

Context, assets and custom resources

Context caches lookup values for repeatable synthesis; commit appropriate cdk.context.json without secrets. Refresh lookups deliberately when environments change and review resulting diffs. Bootstrapping creates shared deployment/asset infrastructure with trust roles; don’t destroy it as generic application cleanup.

Custom resources can call supported APIs not directly covered by ordinary resource types, but must implement safe create/update/delete, stable physical IDs, retries/idempotency and scoped roles. Code compilation is not proof the custom resource can recover from partial failure.

Use synth → assertions → diff/change review → sandbox deploy → live observation. Record logical-ID/resource replacement effects and retained assets. A code rename can trigger infrastructure replacement; treat it as a deployment decision.

Worked two-stack reference and deployment order

Use an initialized TypeScript CDK v2 app in an isolated project, with a pinned aws-cdk-lib/constructs dependency lockfile and an approved sandbox deployment profile. For a new project, follow the TypeScript starter. The code below is a complete alternative app entry point, not an extra invocation to append to the earlier single-stack example. Replace the generated app entry point with it and ensure cdk.json invokes that file. Do not rename an existing production app’s stacks to run a lab.

import { App, CfnOutput, RemovalPolicy, Stack, StackProps } from "aws-cdk-lib";
import { BlockPublicAccess, Bucket, IBucket } from "aws-cdk-lib/aws-s3";
import { StringParameter } from "aws-cdk-lib/aws-ssm";
import { Construct } from "constructs";
 
class LabStorageStack extends Stack {
  readonly bucket: Bucket;
  constructor(scope: Construct, id: string, props: StackProps) {
    super(scope, id, props);
    this.bucket = new Bucket(this, "Data", {
      versioned: true,
      blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
      removalPolicy: RemovalPolicy.RETAIN,
    });
    new CfnOutput(this, "BucketName", { value: this.bucket.bucketName });
  }
}
 
interface LabConfigProps extends StackProps {
  readonly sourceBucket: IBucket;
}
class LabConfigStack extends Stack {
  constructor(scope: Construct, id: string, props: LabConfigProps) {
    super(scope, id, props);
    new StringParameter(this, "BucketConfiguration", {
      parameterName: "/cloudnative-wiki/cdk/lab-bucket-name",
      stringValue: props.sourceBucket.bucketName,
    });
  }
}
 
const app = new App();
const env = {
  account: process.env.CDK_DEFAULT_ACCOUNT,
  region: process.env.CDK_DEFAULT_REGION,
};
const storage = new LabStorageStack(app, "WikiLabStorage", { env });
new LabConfigStack(app, "WikiLabConfig", { env, sourceBucket: storage.bucket });

The consumer stores the producer’s generated bucket name, not a hard-coded guess. CDK creates the same-account/Region CloudFormation export/import and dependency. A configuration reference is not permission to read bucket objects. Keep both stacks in the same explicit environment for this demonstration; cross-account/Region sharing needs a different supported design.

aws sts get-caller-identity --profile sandbox
npx cdk synth --profile sandbox
npx cdk diff --profile sandbox WikiLabStorage WikiLabConfig
npx cdk deploy --profile sandbox WikiLabStorage WikiLabConfig
aws ssm get-parameter --profile sandbox \
  --name /cloudnative-wiki/cdk/lab-bucket-name \
  --query Parameter.Value --output text
aws cloudformation describe-stacks --profile sandbox --stack-name WikiLabStorage \
  --query 'Stacks[0].Outputs' --output json

Set the Region consistently on the profile. Bootstrap only if this approved environment needs it, using reviewed deployment trust and an exact account/Region target. Before deploy, inspect the synthesized S3 Versioning/PublicAccessBlockConfiguration, the export, consumer Fn::ImportValue and dependency; check the diff for replacements. Expected: the parameter equals the BucketName output. Try reading the parameter with a second identity lacking ssm:GetParameter; expect AccessDenied, not a missing bucket.

As a local failure experiment, remove versioned: true in the isolated app and use the earlier hasResourceProperties assertion for VersioningConfiguration; the assertion should fail. Restore the property before deployment. A passed synth/test is not evidence that the live profile can deploy or recover data.

Deleting or changing an exported value while the consumer still imports it is constrained by CloudFormation. Destroy the dedicated consumer first, then the producer after reviewing the diff/retention:

npx cdk destroy --profile sandbox WikiLabConfig
npx cdk destroy --profile sandbox WikiLabStorage

Inspect the retained private versioned bucket and remove only its reviewed disposable versions/bucket if retention is no longer required. Parameter deletion is owned by the consumer stack; shared bootstrap resources are not lab cleanup targets. Do not apply these names to existing unrelated stacks. CDK references and cross-stack constraints.

IaC ownership comparison

ConcernCDKCloudFormationTerraform
Desired state authoringLanguage constructs synthesize templatesYAML/JSON templatesProvider-backed configuration
Deployment/state ownerCloudFormation stacks plus CDK assets/contextCloudFormation stack stateTerraform state/backend and providers
Change evidenceGenerated template, assertions and diff/change reviewTemplate/change set/eventsPlan plus provider/state interpretation
Drift/recoveryUses supported CloudFormation mechanisms; not automatic repair of every changeDetection has resource/property limits; repair is a deliberate operationRefresh/plan/apply behavior depends on provider/configuration; not universal automatic recovery
Cross-unit dependencyStack references can synthesize exports/importsExports/imports have lifecycle constraintsExplicit configuration/state/data-source dependencies

No row promises a fixed engineering-time saving or automatic drift correction. Choose from team/tooling/provider and lifecycle requirements, and test retained-resource ownership separately.

References

Across the wiki