AWS CDK skill for Claude Code
CDK diffs you can trust before you hit deploy. Claude structures CDK apps into explicit-environment stateful and stateless stacks, uses L2 grant methods instead of hand-written policies, and reads every cdk diff for replacements before deploy. It keeps logical IDs stable through refactors, untangles cross-stack export deadlocks, and adds assertion tests and cdk-nag so encryption and retention regressions fail CI.
- name
aws-cdk - size194 lines · ≈2,100 tokens
- files1
- licenseMIT
- updated
What Claude sees before loading it
This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.
description: Builds AWS infrastructure with CDK v2 in TypeScript - app and stack structure, L1/L2/L3 constructs, environments and context, stable logical IDs, removal policies for stateful resources, cross-stack references without deadlocks, cdk diff review before deploy, assertion and snapshot tests, cdk-nag checks, and CDK Pipelines. Use when writing or reviewing CDK code, when cdk deploy wants to replace a database or bucket, when a stack is stuck on exports, or when adding tests and CI for CDK apps. Try asking
- cdk diff says it will replace our DynamoDB table after a refactor. Why?
- Add assertion tests and cdk-nag to this CDK app
- Split this CDK stack into data and API stacks without losing resources
Use it when
- Writing or reviewing AWS CDK TypeScript code
- A diff shows unexpected replacement of a stateful resource
- A deploy fails because an export is still in use
- Adding tests, policy checks or pipelines to a CDK app
Skip it when
- The infrastructure is Terraform or OpenTofu — use terraform-iac
- The question is only about an IAM policy's contents — use aws-iam
How to check the result
Tests and cdk synth with cdk-nag pass, and cdk diff shows no replacement of stateful resources and only intended IAM changes before deploy.
The common mistake it prevents
Renaming or moving a construct during a refactor, which changes its logical ID and makes CloudFormation delete and recreate the database.
A safe CDK app produces a cdk diff that reviewers can trust: logical IDs stay stable,
stateful resources are retained and never replaced by accident, environments are
explicit, tests assert the security-relevant properties, and production deploys require
human approval of broadened permissions.
Workflow
- [ ] 1. Read cdk.json, bin/*.ts (app + stacks + envs), package.json (aws-cdk-lib, CLI versions)
- [ ] 2. Locate constructs being changed and whether they are stateful
- [ ] 3. Make the change with L2 constructs; isolate stateful resources
- [ ] 4. npm test (assertions/snapshots) and cdk synth
- [ ] 5. cdk diff against the target environment; read every replacement and IAM change
- [ ] 6. Deploy to dev/staging; production only after diff review and user confirmation
Project structure
bin/app.ts # instantiate stacks per environment; no resources here
lib/
data-stack.ts # stateful: databases, buckets, queues, KMS keys (termination protection)
api-stack.ts # stateless: Lambda, API Gateway, ECS services
constructs/ # reusable L3 constructs with typed props
test/
api-stack.test.ts
cdk.json # app command, feature flags, context
cdk.context.json # cached lookups: commit it
// bin/app.ts
const app = new cdk.App();
const prod = { account: "123456789012", region: "eu-west-1" };
const data = new DataStack(app, "Orders-Data-Prod", { env: prod, terminationProtection: true });
new ApiStack(app, "Orders-Api-Prod", { env: prod, table: data.table });
cdk.Tags.of(app).add("app", "orders");
- Explicit
envper stack for anything deployed; environment-agnostic stacks cannot use lookups (Vpc.fromLookup) and get region-agnostic behavior. - Separate stateful and stateless stacks: stateless stacks can be destroyed and recreated; stateful ones change rarely and carry termination protection.
- Pass constructs between stacks as typed props (
table: dynamodb.ITable), not by name strings.
Constructs
| Level | Use when |
|---|---|
L2 (s3.Bucket, lambda.Function) | Default: sane defaults, grant* methods for IAM |
L3 / patterns (ecs_patterns.ApplicationLoadBalancedFargateService) | The pattern fits exactly; otherwise compose L2s |
L1 (CfnBucket) | Property not exposed by L2; or use escape hatches on the L2 |
Escape hatch for a missing property:
const cfnBucket = bucket.node.defaultChild as s3.CfnBucket;
cfnBucket.addPropertyOverride("NotificationConfiguration.EventBridgeConfiguration.EventBridgeEnabled", true);
Use grant* methods (table.grantReadData(fn), bucket.grantPut(fn, "uploads/*"))
instead of hand-written policy statements: they scope to the resource ARN and include
companion KMS permissions.
Logical IDs and replacements
The logical ID comes from the construct path (Stack/Construct/Child). Renaming a
construct ID, moving it into another construct, or moving it to another stack changes
the logical ID, and CloudFormation deletes the old resource and creates a new one.
| Change | Result |
|---|---|
| Rename construct id or move in tree | Replacement (data loss for stateful resources) |
| Change an immutable property (table key schema, bucket name, RDS engine in some cases) | Replacement |
Set a physical name (bucketName, tableName) | Blocks replacement updates and multi-env deploys; avoid unless required |
Protect stateful resources:
const table = new dynamodb.TableV2(this, "Orders", {
partitionKey: { name: "pk", type: dynamodb.AttributeType.STRING },
removalPolicy: cdk.RemovalPolicy.RETAIN,
pointInTimeRecoverySpecification: { pointInTimeRecoveryEnabled: true },
deletionProtection: true,
});
If a refactor must move a resource, either keep the logical ID with
(resource.node.defaultChild as cdk.CfnResource).overrideLogicalId("OrdersXXXX"), use
cdk import to adopt the retained resource in its new location, or use the CDK CLI’s
refactor support where available. Always check the diff for [-]/[+] pairs on the same
resource type.
Cross-stack references
Passing a construct from stack A to stack B creates a CloudFormation export in A. Later removing that usage fails with “Export … cannot be deleted as it is in use”. Avoid the deadlock:
- Remove the consumer usage first and deploy B; keep the export alive in A with
this.exportValue(resource.attr)for one deploy; then remove it. - For loosely coupled stacks, share via SSM parameters (
ssm.StringParameter) read at deploy time instead of exports.
Diff and deploy
npx cdk synth # fails on errors, runs aspects (cdk-nag)
npx cdk diff Orders-Api-Prod # read replacements, IAM, security group changes
npx cdk deploy Orders-Api-Prod --require-approval broadening
npx cdk deploy Orders-Api-Dev --hotswap # dev only: bypasses CloudFormation for code changes
npx cdk watch Orders-Api-Dev # dev loop
Review checklist for every diff:
- Any
replaceon a database, table, bucket, queue, KMS key, user pool? Stop and redesign. - IAM statement changes: wider actions or
*resources? - Security group changes opening
0.0.0.0/0? - Unexpected resources from feature flag or library upgrades?
--hotswap and watch cause drift from the template; never use them against
production. Production deploys require user confirmation after the diff is shown.
Testing
import { App } from "aws-cdk-lib";
import { Template, Match } from "aws-cdk-lib/assertions";
test("bucket blocks public access and enforces TLS", () => {
const app = new App();
const stack = new DataStack(app, "Test", { env: { account: "111111111111", region: "eu-west-1" } });
const t = Template.fromStack(stack);
t.hasResourceProperties("AWS::S3::Bucket", {
PublicAccessBlockConfiguration: { BlockPublicAcls: true, BlockPublicPolicy: true, IgnorePublicAcls: true, RestrictPublicBuckets: true },
});
t.hasResourceProperties("AWS::S3::BucketPolicy", {
PolicyDocument: { Statement: Match.arrayWith([Match.objectLike({ Effect: "Deny", Condition: { Bool: { "aws:SecureTransport": "false" } } })]) },
});
t.hasResource("AWS::DynamoDB::GlobalTable", { DeletionPolicy: "Retain" });
});
Fine-grained assertions for security and stateful properties; snapshot tests
(expect(t.toJSON()).toMatchSnapshot()) only as a change detector, reviewed on update.
Tests must not perform lookups: pass explicit env and mock context or use fromAttributes.
Policy-as-code with cdk-nag:
import { Aspects } from "aws-cdk-lib";
import { AwsSolutionsChecks, NagSuppressions } from "cdk-nag";
Aspects.of(app).add(new AwsSolutionsChecks({ verbose: true }));
NagSuppressions.addResourceSuppressions(bucket, [{ id: "AwsSolutions-S1", reason: "Access logs bucket itself" }]);
Every suppression carries a reason.
Versions and bootstrapping
aws-cdk-lib(library) and theaws-cdkCLI are versioned separately since 2025 (CLI 2.1000+). Keep the CLI at or above what the library requires; pin both in package.json and run withnpx cdk.- Bootstrap each account/region once:
npx cdk bootstrap aws://123456789012/eu-west-1. For cross-account pipelines add--trust <pipeline-account> --cloudformation-execution-policies <arn>; avoid AdministratorAccess as the execution policy in production accounts. - New feature flags in
cdk.jsonchange synthesized output; review diffs after upgrades.
Gotchas
Date.now()or random values in construct code produce a diff on every synth.- Asset hashes change when unrelated files are bundled; set
excludepatterns. - Lambda
NodejsFunctionbundles with esbuild; Docker is needed only if esbuild is not installed. RemovalPolicy.DESTROYplusautoDeleteObjects: trueon a bucket deletes data on stack deletion; use only for ephemeral environments.- Stack size limit: 500 resources per stack; split before hitting it.
Verification
npm testpasses, including assertions on encryption, public access, retention.cdk synthsucceeds with cdk-nag clean (or justified suppressions).cdk diffshows no replacement of stateful resources and only intended IAM changes; share it with the user before any production deploy.- After deploy,
aws cloudformation describe-stack-eventsshows no rollbacks; drift detection shows no drift.
About this skill
Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.