
Aws Cdk
- 4.6k installs
- 2.2k repo stars
- Updated August 4, 2026
- aws/agent-toolkit-for-aws
aws-cdk is an agent skill for authoring, deploying, and troubleshooting AWS CDK stacks with TypeScript or Python including diff, drift, import, and safe refactor patterns.
About
aws-cdk is a domain skill for authoring and operating AWS CDK infrastructure with TypeScript or Python constructs, covering bootstrap, synth, diff, deploy, compliance, drift detection, resource import, and safe refactoring. Critical warnings document deadly embrace cross-stack export deadlocks requiring weakened references across three deploys, construct ID renames that trigger CloudFormation replacement, UPDATE_ROLLBACK_FAILED recovery via cdk rollback, and non-empty S3 buckets needing removalPolicy DESTROY plus autoDeleteObjects true. Workflows map bootstrap, cdk init, cdk-nag AwsSolutionsChecks, cdk drift with optional --fail in CI, cdk import, and cdk refactor without property changes in the same deploy. Troubleshooting ties DeployFailed to verbose deploy and diagnose commands, credential errors to aws sts get-caller-identity, asset bundling failures to Docker and path issues, and dependency cycles to shared stack extraction or SSM late binding. Construct guidance prefers L2 constructs with escape hatches via node.defaultChild addPropertyOverride. Security recommendations include OIDC CI credentials, permissions boundaries on bootstrap, grant helpers for IAM, cdk-nag with --st.
- Covers CDK bootstrap, synth, diff, deploy, drift, import, and refactor workflows.
- Documents deadly embrace cross-stack reference removal across three deploy steps.
- Construct ID changes trigger replacement; always cdk diff before production deploy.
- Troubleshooting tables for credentials, assets, dependency cycles, and rollback failures.
- Recommends cdk-nag AwsSolutionsChecks, OIDC CI auth, and terminationProtection on stateful stacks.
Aws Cdk by the numbers
- 4,600 all-time installs (skills.sh)
- +516 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #118 of 1,039 Cloud & Infrastructure skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
aws-cdk capabilities & compatibility
- Capabilities
- cdk project bootstrap and typescript or python i · synth, diff, deploy, drift, and import workflow · cross stack reference and refactor safety proced · troubleshooting maps for credentials, assets, an · construct l2 preference with escape hatch overri
- Works with
- aws · docker · terraform · kubernetes
- Use cases
- devops · ci cd · api development
What aws-cdk says it does
Always diff before deploy to prod
npx skills add https://github.com/aws/agent-toolkit-for-aws --skill aws-cdkAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 4.6k |
|---|---|
| repo stars | ★ 2.2k |
| Security audit | 3 / 3 scanners passed |
| Last updated | August 4, 2026 |
| Repository | aws/agent-toolkit-for-aws ↗ |
How do I write or fix AWS CDK stacks without accidental resource replacement, cross-stack deadlocks, or stuck CloudFormation rollbacks?
Author, deploy, troubleshoot, and safely refactor AWS CDK stacks in TypeScript or Python with synth, diff, drift, and import workflows.
Who is it for?
Developers implementing AWS infrastructure as CDK who need construct patterns plus deployment troubleshooting guardrails.
Skip if: Skip for raw CloudFormation YAML, Terraform, Pulumi, or CI/CD pipelines beyond CDK Pipelines scope.
When should I use this skill?
User mentions CDK constructs, cdk deploy, cdk diff, bootstrap, CloudFormation errors, stack import, or CDK drift.
What you get
Bootstrapped CDK app with linted constructs, successful synth and diff, deployed stacks, and documented fixes for common CDK CLI failures.
- CDK stack constructs
- cdk.context.json
- deployment troubleshooting notes
By the numbers
- Bundles 9 reference guides under skills/core-skills/aws-cdk/references/
- CDKToolkit bootstrap creates 4 IAM roles plus S3, ECR, and SSM resources
- Supports TypeScript and Python project initialization via cdk init
Files
AWS CDK
Overview
Domain expertise for CDK construct authoring, deployment workflows, compliance, drift, importing resources, safe refactoring, and troubleshooting CDK CLI / CloudFormation errors.
When NOT to use: Raw CloudFormation YAML/JSON. SAM. Terraform/Pulumi. CI/CD beyond CDK Pipelines. Use builtin knowledge or specialized skills for these.
Critical Warnings
Deadly embrace: Removing a cross-stack reference deadlocks deployment (Export ... cannot be deleted as it is in use by ...). Preferred fix: weaken the reference first — CrossStackReferences.of($RESOURCE).produce(ReferenceStrength.BOTH) then WEAK, then remove (three deploys). Legacy fallback: two-deploy this.exportValue() recipe. See troubleshooting-deployment.
Construct ID changes cause replacement: Renaming/moving a construct changes its logical ID → CloudFormation replaces the resource (data loss for stateful resources). Always cdk diff before deploy. See refactor-and-prevent-replacement.
UPDATE_ROLLBACK_FAILED: Stack is stuck. Fix with cdk rollback $STACK or cdk rollback $STACK --orphan <LogicalId>. See troubleshooting-deployment.
Non-empty S3 buckets persist after destroy: You MUST set both removalPolicy: DESTROY and autoDeleteObjects: true. Versioned buckets are worse — delete markers persist even after apparent deletion.
Common Workflows
| Task | Quick Command | Details |
|---|---|---|
| Bootstrap | cdk bootstrap aws://$ACCOUNT/$REGION | bootstrap-and-project-setup |
| New TS project | cdk init app --language typescript — use tsx, eslint-plugin-awscdk | bootstrap-and-project-setup |
| New Python project | cdk init app --language python — pin deps, use virtualenv | bootstrap-and-project-setup |
| Deploy | cdk synth --strict → cdk diff → cdk deploy | Always diff before deploy to prod |
| cdk-nag | Aspects.of(app).add(new AwsSolutionsChecks()) | compliance-and-drift |
| Drift | cdk drift $STACK (use --fail in CI) | compliance-and-drift |
| Import resource | cdk import (interactive or --resource-mapping for CI), cdk deploy --import-existing-resources | import-and-migrate |
| Refactor safely | cdk refactor --unstable=refactor — no property changes in same deploy | refactor-and-prevent-replacement |
Troubleshooting
| Error | Cause → Fix |
|---|---|
| DeployFailed / DeploymentError | CDK error isn't the root cause. cdk deploy $STACK --verbose, then cdk --unstable=diagnose diagnose $STACK (CLI ≥ 2.1120.0); else aws cloudformation describe-events --stack-name $STACK --filters FailedEvents=true — the first _FAILED event is the cause. Details |
| NoCredentials / ExpiredToken / AssumeRoleFailed | aws sts get-caller-identity + cdk doctor. Expired SSO, missing env, missing sts:AssumeRole. Details |
| Asset errors (CannotFindAsset, FailedToBundleAsset, AssetBuildFailed, AssetPublishFailed) | Path wrong, Docker not running, or bootstrap bucket perms. Use path.join(__dirname, ...). Details |
| AppRequired | Add "app": "npx tsx bin/my-app.ts" to cdk.json. Details |
| AnnotationErrors | Fix the underlying issue; suppress with NagSuppressions only as last resort. Details |
| ConcurrentReadLock / ConcurrentWriteLock | rm -rf cdk.out then re-run. Parallel CI: --output ./cdk.out.$BUILD_ID. Details |
| BootstrapVersionValidation | Re-bootstrap. Match --qualifier everywhere. Details |
| DependencyCycle | Extract shared resource into third stack or use SSM for late-binding. Details |
| UnresolvedAccount | Set explicit env: { account, region } on stack. Commit cdk.context.json. Details |
| NoStacksMatched | CDK uses logical ID (2nd constructor arg), not CFN name. cdk list to find IDs. Details |
| Cannot find module (synth time) | Run npx tsc --noEmit, check cdk.json app path matches tsconfig.json outDir, delete stale .js files. Python: activate venv. Details |
| V1 import paths / duplicate aws-cdk-lib | V1 @aws-cdk/* imports, wrong Construct import, duplicate lib copies in monorepos. Details |
| Lambda Cannot find module (runtime) | Wrong handler value, missing SDK v3 migration, Python deps not bundled. Details |
| API Gateway multi-stage conflicts | Set deploy: false on RestApi, create Deployment and Stage explicitly. Details |
Construct Patterns
Prefer L2. Use L1 with Mixins/Facades when L2 lacks a property. Escape hatches: node.defaultChild → addPropertyOverride. See construct-patterns.
Additional Resources
- Search AWS documentation for "CDK Developer Guide", "CDK API Reference" and "CDK Pipelines" respectively
Security Considerations
- OIDC for CI/CD credentials (no static keys)
--custom-permissions-boundaryon bootstrapgrant*()for inter-resource IAMcdk-nag+--strictin CI- Stateful resources in own stack with
terminationProtection: true - Commit
cdk.context.json
Bootstrap and Project Setup Reference
Table of Contents
- Bootstrap and Project Setup Reference
- Table of Contents
- Overview
- Bootstrap Procedure
- What Bootstrap Creates
- Bootstrap Command
- Cross-Account Trust
- Custom Qualifier
- Permissions Boundary
- Custom Bootstrap Template
- Bootstrap Constraints
- TypeScript Project Setup
- Prerequisites
- Initialize Project
- Project Structure
- Configure tsx
- Linting
- Common Commands
- Python Project Setup
- Prerequisites
- Initialize Project
- Virtual Environment
- Common Commands
- Version Management Best Practices
- CLI and Library Are Separate Release Tracks
- Feature Flags
---
Overview
Every CDK deployment target (account + region pair) MUST be bootstrapped before the first deployment. Projects MUST commit a lockfile and SHOULD use strict tooling to ensure reproducible builds.
---
Bootstrap Procedure
What Bootstrap Creates
The CDKToolkit CloudFormation stack provisions:
- An S3 bucket (file assets and CloudFormation templates)
- An ECR repository (Docker image assets)
- 4 IAM roles for user to assume (deploy, lookup, file-publishing, image-publishing)
- A CloudFormation execution role
- An SSM parameter (
/cdk-bootstrap/$QUALIFIER/version)
Bootstrap Command
cdk bootstrap aws://$ACCOUNT_ID/$REGIONBootstrap REQUIRES near-administrator permissions in the target account.
Cross-Account Trust
To allow a CI/CD account to deploy into a target account:
cdk bootstrap aws://$TARGET_ACCOUNT/$REGION \
--trust $CI_ACCOUNT_ID \
--cloudformation-execution-policies arn:aws:iam::aws:policy/$POLICY_NAMEThe --trust flag grants the specified account permission to assume the CDK roles. The --cloudformation-execution-policies flag MUST be provided with --trust to scope the CloudFormation execution role.
Custom Qualifier
To run multiple independent CDK environments in the same account/region:
cdk bootstrap aws://$ACCOUNT_ID/$REGION --qualifier $QUALIFIERThe qualifier MUST be alphanumeric and at most 10 characters. It distinguishes bootstrap resources from other CDK environments in the same account.
Permissions Boundary
To attach a permissions boundary to all IAM roles created by CDK:
cdk bootstrap aws://$ACCOUNT_ID/$REGION \
--custom-permissions-boundary $BOUNDARY_POLICY_NAMECustom Bootstrap Template
To use an organization-approved bootstrap template:
cdk bootstrap aws://$ACCOUNT_ID/$REGION --template $TEMPLATE_PATHBootstrap Constraints
- Deleting the
CDKToolkitstack MUST NOT be done — it breaks all deployments
in that account/region pair.
- Termination protection SHOULD be enabled on the
CDKToolkitstack. - Bootstrap MUST be re-run when upgrading to a CDK version that requires a newer
bootstrap stack version.
---
TypeScript Project Setup
Prerequisites
- Node.js ≥ 20 MUST be installed.
Initialize Project
cdk init app --language typescriptProject Structure
$PROJECT_ROOT/
├── bin/ # Entry point (App instantiation)
├── lib/ # Stack and construct definitions
├── cdk.json # CDK configuration
├── package.json
└── tsconfig.jsonConfigure tsx
The cdk.json app field SHOULD use tsx instead of ts-node for faster startup:
{
"app": "npx tsx bin/$APP_NAME.ts"
}Linting
Projects MUST enforce strict typing — any MUST NOT be used. Configure with:
eslint+prettiereslint-plugin-awscdkfor CDK-specific rules
Construct props interfaces SHOULD use readonly on all properties:
interface MyConstructProps {
readonly bucketName: string;
readonly enableVersioning: boolean;
}Common Commands
cdk synth # Synthesize CloudFormation template
cdk diff # Show pending changes
cdk deploy # Deploy stack(s)
cdk destroy # Tear down stack(s)
cdk list # List all stacks in the app---
Python Project Setup
Prerequisites
- Node.js ≥ 20 MUST be installed.
- Python ≥ 3.9 MUST be installed.
Initialize Project
cdk init app --language pythonVirtual Environment
After initialization, activate the virtualenv and install dependencies:
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtDependencies SHOULD be captured in requirements.txt (or poetry.lock / Pipfile.lock) and committed for reproducible builds. See Version Management Best Practices.
Common Commands
cdk synth # Synthesize CloudFormation template
cdk deploy # Deploy stack(s)
cdk bootstrap # Bootstrap target environment
cdk doctor # Check for potential problems---
Version Management Best Practices
- Commit lockfiles (
package-lock.json/poetry.lock/Pipfile.lock). Unlocked builds drift and lose determinism. - For CDK applications, use caret (`^`) ranges for
aws-cdk-libandconstructsindependencies— this is the officially recommended approach. The lockfile provides reproducibility; the caret range letsnpm updatepull compatible fixes and features.
{
"dependencies": {
"aws-cdk-lib": "^2.170.0",
"constructs": "^10.5.0"
}
}Teams that prefer exact pinning for stricter reproducibility SHOULD pair it with automated upgrade tooling (Dependabot, Renovate) to avoid falling behind.
- For construct libraries, declare
aws-cdk-libandconstructsaspeerDependencies(caret, widest compatible) and asdevDependenciesat the oldest supported exact version. - Experimental / alpha modules (e.g.
@aws-cdk/aws-*-alpha) SHOULD use exact versions — their APIs can change between releases without SemVer guarantees. - Automate upgrades: a weekly job that bumps
aws-cdk-lib, runscdk synthto catch breaking changes, deploys to a test environment, and opens a PR on success.
CLI and Library Are Separate Release Tracks
The CDK CLI (aws-cdk) and the library (aws-cdk-lib) are independent packages on different release tracks — their version numbers do NOT align. A CLI at 2.1001.x paired with a library at 2.200.x is normal. The compatibility contract is one-way: a newer CLI can read assemblies produced by older libraries, but an older CLI CANNOT read assemblies produced by newer libraries. The mismatch surfaces as:
This CDK CLI is not compatible with the CDK library used by your application.
(Cloud assembly schema version mismatch)The fix is to upgrade the CLI to a specific newer version. You MUST install aws-cdk as a dev dependency at an exact version and invoke it via npx cdk; you MUST NOT use aws-cdk@latest anywhere — it is non-deterministic, so a broken release can reach your pipeline instantly.
{
"devDependencies": {
"aws-cdk": "2.1010.0"
}
}npx cdk synth
npx cdk deploy $STACK_NAMEBump the pinned CLI version regularly (Dependabot / Renovate), on the same cadence as aws-cdk-lib.
Feature Flags
cdk.json's context object carries CDK feature flags — per-release opt-ins to behaviour changes. When upgrading aws-cdk-lib, review new flags and adopt them incrementally (inspect via cdk flags --unstable=flags). Do not flip everything to recommended in one commit.
Compliance and Drift Reference
Table of Contents
- Overview
- cdk-nag Setup and Rule Packs
- Installation
- Available Rule Packs
- Applying Rule Packs
- Suppression Patterns
- Drift Detection
- cdk drift vs cdk diff
- Running Drift Detection
- Drift Resolution Strategies
- CI Integration
- Strict Mode
- cdk-nag in CI
- Drift in CI
- Security Scanning Layers
- Strict Mode Rollout
---
Overview
CDK applications MUST be scanned for compliance violations before deployment and monitored for drift after deployment. cdk-nag provides compile-time policy enforcement. cdk drift detects runtime configuration changes made outside CDK.
---
cdk-nag Setup and Rule Packs
Installation
npm install cdk-nagcdk-nag MUST be wired in before the first deploy to prevent non-compliant resources from ever reaching production.
Available Rule Packs
| Rule Pack | Use Case |
|---|---|
AwsSolutionsChecks | General AWS best practices |
HIPAASecurityChecks | HIPAA compliance |
NIST80053R5Checks | NIST 800-53 Rev 5 compliance |
PCIDSS321Checks | PCI DSS 3.2.1 compliance |
For SOX compliance, apply both NIST80053R5Checks and AwsSolutionsChecks together.
Applying Rule Packs
Rule packs are applied as CDK Aspects:
import { Aspects } from 'aws-cdk-lib';
import { AwsSolutionsChecks } from 'cdk-nag';
Aspects.of($APP).add(new AwsSolutionsChecks());Multiple rule packs MAY be applied simultaneously:
Aspects.of($APP).add(new AwsSolutionsChecks());
Aspects.of($APP).add(new NIST80053R5Checks());---
Suppression Patterns
When a finding is intentionally accepted, suppress it with NagSuppressions.addResourceSuppressions(). Every suppression MUST include a documented reason:
import { NagSuppressions } from 'cdk-nag';
NagSuppressions.addResourceSuppressions($CONSTRUCT, [
{
id: '$RULE_ID',
reason: '$DOCUMENTED_JUSTIFICATION',
},
]);Suppressions MUST NOT be used to bypass findings without genuine justification.
---
Drift Detection
cdk drift vs cdk diff
cdk diffcompares the local CDK app against the last deployed template.
It shows what a new deployment would change.
cdk driftcompares the deployed template against the **actual live
resource state**. It shows out-of-band changes made outside CDK.
Running Drift Detection
Single stack:
cdk drift $STACK_NAMEAll stacks:
cdk drift---
Drift Resolution Strategies
When drift is detected, resolve it using one of these approaches (in order of preference):
1. Redeploy — Run cdk deploy $STACK_NAME to overwrite the drifted state with the CDK-defined state. This is the simplest resolution.
2. Adopt the change — If the out-of-band change is desired, update the CDK code to match the live state using Cfn<Resource>PropsMixin to adopt the drifted property values.
3. Fallback overrides — If Cfn<Resource>PropsMixin is not available for the resource type, use addPropertyOverride or node.defaultChild to set the property at the L1 level.
4. Handle deleted resources — If a resource was deleted outside CDK, remove it from the CDK code or re-import it.
Drift SHOULD be prevented proactively using SCPs (Service Control Policies) that restrict manual changes to CDK-managed resources.
---
CI Integration
Strict Mode
--strict MUST be passed on every cdk synth and cdk deploy in CI. Strict mode promotes warnings to build failures:
npx cdk synth --strict
npx cdk deploy $STACK_NAME --strictPair --strict with cdk-nag to catch both CDK warnings and compliance violations.
cdk-nag in CI
cdk-nag MUST be enforced in CI pipelines. Because rule packs are applied as Aspects, cdk synth will fail if any violations are found (when using --strict), blocking the deployment.
cdk-nag scans for:
- Over-permissive IAM policies
- Open security groups
- Unencrypted resources
- Missing logging
Drift in CI
Automate drift detection in CI with the --fail flag:
cdk drift --failThis exits with a non-zero code when drift is detected, failing the pipeline.
Security Scanning Layers
1. Wire cdk-nag first as the primary compliance layer. 2. Add Checkov as a second scanning layer for additional coverage.
Strict Mode Rollout
To adopt --strict incrementally on an existing project:
1. Collect current warnings with cdk synth. 2. Triage each warning — determine if it is a real issue or acceptable. 3. Fix genuine issues; suppress accepted findings with NagSuppressions. 4. Enable --strict in CI once all warnings are resolved or suppressed.
Construct Patterns
Table of Contents
- Overview
- Scope and Construct IDs
- Choosing Construct Levels
- Mixing L1 and L2
- Cross-Stack References
- Creating Custom Constructs
- Testing CDK Infrastructure
---
Overview
This reference covers construct selection, composition, cross-stack wiring, and testing patterns. It provides decision frameworks for choosing construct levels, mixing them safely, passing references across stacks, building custom constructs, and verifying infrastructure with assertions.
---
Scope and Construct IDs
Every construct is created with new SomeConstruct(scope, id, props?). The first two arguments are not interchangeable boilerplate — misusing them is a common review finding.
Scope (first argument)
Inside a construct's constructor, you MUST pass this as the scope of child constructs — NOT the incoming scope argument:
// ❌ INCORRECT — child parented to the wrong node
export class MyConstruct extends Construct {
constructor(scope: Construct, id: string) {
super(scope, id);
new ChildConstruct(scope, 'Child'); // wrong: uses 'scope'
}
}
// ✅ CORRECT
export class MyConstruct extends Construct {
constructor(scope: Construct, id: string) {
super(scope, id);
new ChildConstruct(this, 'Child'); // 'this' is the parent
}
}Passing this makes the child a child of the current construct, which is almost always the intent. A scope other than this is legitimate only when it comes from a function parameter or a local variable used to group constructs (e.g. in App or helper/test stacks).
Construct ID (second argument)
The construct ID is the locally unique identifier within a scope. The IDs between a CloudFormation resource and its containing Stack are concatenated into the resource's logical ID — and changing a logical ID replaces the resource.
- Construct IDs SHOULD be short and to the point.
- You SHOULD NOT interpolate variables into a construct ID: if the variable's value changes, the logical ID changes and the resource is replaced. Legitimate exceptions (constructs created in a loop; an intentional, conditional replacement) MUST be annotated with a comment explaining why.
- Construct IDs only need to be unique within their scope. They SHOULD NOT repeat project, region, or stage names already present higher in the construct tree.
---
Choosing Construct Levels
You SHOULD prefer L2 constructs as the default choice. They provide sensible defaults, grant methods, and metric helpers.
Decision tree
| Need | Construct type |
|---|---|
| Pure logic, no AWS resource | Plain TypeScript/Python class |
| Single resource with stricter defaults | Extend the L2 class (is-a) |
| Composition of multiple resources | Extend Construct (has-a) — this is an L3 |
| Organization-wide policy enforcement | Aspect |
When L1 is viable
L1 (Cfn*) constructs are acceptable when no L2 exists or when you need a property the L2 does not expose. You SHOULD combine L1 with Mixins, Facades, or I<Resource>Ref interfaces to retain type safety and grant support.
When using L3
L3 constructs provision multiple resources behind a single API. You MUST read what they provision (check the source or cdk synth output) before using them in production. Hidden resources may have cost, security, or operational implications.
When an L2 doesn't expose a property
Use this escalation ladder — prefer the first option that works:
1. Cfn<Resource>PropsMixin (preferred) — type-safe, applied via .with():
import { CfnBucketPropsMixin } from '@aws-cdk/cfn-property-mixins/aws-s3';
new s3.Bucket(this, 'Bucket').with(new CfnBucketPropsMixin({
analyticsConfigurations: [{ id: 'full', prefix: '' }],
}));2. `addPropertyOverride` — untyped, string-keyed last resort:
const cfnBucket = bucket.node.defaultChild as s3.CfnBucket;
cfnBucket.addPropertyOverride('AnalyticsConfigurations', [{ Id: 'full', Prefix: '' }]);---
Mixing L1 and L2
When a stack contains both L1 and L2 constructs, you MUST use I<Resource>Ref interfaces and Facades to bridge them — do not pass L1 property types to L2 props or vice versa, as their types are not interchangeable.
I<Resource>Ref interfaces (e.g. IBucketRef)
Both L1 and L2 constructs implement I<Resource>Ref-style interfaces (e.g., IBucketRef, IFunctionRef). Use these interfaces as prop types to accept either level:
interface MyProps {
readonly bucket: s3.IBucketRef;
}Facades for L1 grants
L1 constructs lack grant*() methods. You SHOULD wrap them with fromCfn<Resource>() or from<Resource>Attributes() to get an L2 interface:
const cfnTable = new dynamodb.CfnTable(this, 'Table', { /* ... */ });
const table = dynamodb.Table.fromTableArn(this, 'TableRef', cfnTable.attrArn);
table.grantReadData(myFunction);Escalation ladder
When you need to customize a resource, follow this order (least invasive first):
1. L2 prop — use the built-in property if available. 2. Mixin — add behavior via a helper function. 3. Cfn<Resource>PropsMixin — type-safe L1 prop injection. 4. node.defaultChild — access the underlying L1 construct. 5. addPropertyOverride — override arbitrary CloudFormation properties.
You SHOULD exhaust each level before moving to the next.
---
Cross-Stack References
Same app, same region
Pass construct references via stack props. CDK automatically creates CloudFormation exports and imports:
interface ConsumerProps extends cdk.StackProps {
readonly bucket: s3.IBucket;
}
class ConsumerStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: ConsumerProps) {
super(scope, id, props);
props.bucket.grantRead(myFunction);
}
}Cross-region or cross-account (same app)
Enable crossRegionReferences: true on the consuming stack and use explicit physical names on shared resources:
new ConsumerStack(app, 'Consumer', {
env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
crossRegionReferences: true,
bucket: producerStack.bucket,
});Different apps
When stacks are in different CDK apps, automatic exports do not work. You MUST use one of:
CfnOutput+Fn.importValue:
// Producer app
new cdk.CfnOutput(this, 'BucketArn', { value: bucket.bucketArn, exportName: '$EXPORT_NAME' });
// Consumer app
const arn = cdk.Fn.importValue('$EXPORT_NAME');- SSM Parameter Store for decoupled lookups.
Fixing cycles
If cross-stack references create a dependency cycle, you MUST extract the shared resource into a third stack so that dependencies flow one way.
---
Creating Custom Constructs
Extend L2 (is-a)
Use when you want a single resource with stricter defaults:
export class SecureBucket extends s3.Bucket {
constructor(scope: Construct, id: string, props?: s3.BucketProps) {
super(scope, id, {
encryption: s3.BucketEncryption.S3_MANAGED,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
enforceSSL: true,
...props,
});
}
}Compose L3 (has-a)
Use when you combine multiple resources behind a single API:
export class ApiWithQueue extends Construct {
public readonly queue: sqs.Queue;
constructor(scope: Construct, id: string) {
super(scope, id);
this.queue = new sqs.Queue(this, 'Queue');
// ... additional resources
}
}Stable logical IDs
The default child ID determines the CloudFormation logical ID. You MUST NOT change construct IDs after deployment — this causes resource replacement. Use the Default child ID convention for the primary resource in an L3.
Escape via defaultChild
When extending an L2, you can access the underlying CFN resource:
const cfn = this.node.defaultChild as s3.CfnBucket;
cfn.addPropertyOverride('$PROPERTY_PATH', '$VALUE');---
Testing CDK Infrastructure
Fine-grained assertions
Use Template.fromStack() to assert on specific resources:
const template = Template.fromStack(myStack);
template.hasResourceProperties('AWS::SQS::Queue', {
VisibilityTimeout: 300,
});Partial matching
Use Match.* helpers for flexible assertions:
template.hasResourceProperties('AWS::Lambda::Function', {
Runtime: Match.stringLikeRegexp('nodejs'),
Environment: Match.objectLike({
Variables: Match.objectLike({
TABLE_NAME: Match.anyValue(),
}),
}),
});Snapshot tests
Capture the full template and compare against a stored baseline:
expect(template.toJSON()).toMatchSnapshot();You SHOULD use snapshot tests to detect unintended drift but MUST NOT rely on them as the sole testing strategy — they are brittle and hard to review.
Logical ID stability tests
Assert that critical resource logical IDs remain stable to prevent accidental replacement:
template.hasResource('AWS::DynamoDB::Table', {
// Verifying the resource exists with this logical ID
});Integration tests
Use @aws-cdk/integ-tests-alpha for tests that deploy real infrastructure:
const integ = new IntegTest(app, 'MyIntegTest', {
testCases: [myStack],
});
integ.assertions
.awsApiCall('DynamoDB', 'DescribeTable', { TableName: '$TABLE_NAME' })
.assertAtPath('Table.TableStatus', ExpectedResult.stringLikeRegexp('ACTIVE'));Integration tests SHOULD be run in a dedicated test account. They MUST NOT run against production.
Application best practices
- Make decisions at synth time — use explicit
envto enable synth-time logic. - Use generated physical names (CDK default) unless cross-stack or cross-app references require explicit names.
- Set explicit
removalPolicyandlogRetentionon every resource. - Separate stateful resources (databases, buckets) into their own stack.
- Commit
cdk.context.jsonto version control for reproducible synth. - Use
grant*()methods for IAM instead of hand-written policy statements.
Import and Migrate Reference
Table of Contents
- Overview
- Read-Only References (from* Methods)
- Full Resource Adoption (cdk import)
- CI-Friendly Import (--import-existing-resources)
- Migrating with cdk migrate
- From an Existing Stack
- From a Template File
- From a Live Account Scan
- Migration Constraints
- First Deploy After Migration
- Incremental Refactoring
- Post-Import Verification
---
Overview
CDK provides three mechanisms for referencing or adopting existing AWS resources, plus a migration tool for converting existing CloudFormation stacks or live infrastructure into CDK code. The right mechanism depends on whether you need read-only access or full lifecycle management.
| Mechanism | Use Case | Lifecycle Control |
|---|---|---|
from* methods | Reference existing resources | Read-only |
cdk import | Adopt resources into a stack | Full (interactive) |
--import-existing-resources | Adopt resources in CI | Full (automated) |
cdk migrate | Convert stacks/infra to CDK code | Full |
---
Read-Only References (from* Methods)
Use from* static methods (e.g., Bucket.fromBucketName(), Vpc.fromLookup()) to reference existing resources without managing their lifecycle:
const bucket = s3.Bucket.fromBucketName(this, 'ImportedBucket', '$BUCKET_NAME');
const vpc = ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: '$VPC_ID' });Constraints:
from*references are read-only — CDK MUST NOT attempt to modify or
delete these resources.
fromLookupmethods require theenvproperty (account and region) to be
set on the stack. They perform API calls at synth time and cache results in cdk.context.json.
cdk.context.jsonSHOULD be committed to version control so that synth is
reproducible without network access.
---
Full Resource Adoption (cdk import)
cdk import adopts existing resources into a CDK stack so CDK fully manages their lifecycle.
Interactive (default):
cdk import $STACK_NAMEThe CLI prompts for each resource's physical identifier (bucket name, table name, etc.).
Non-interactive (CI-friendly):
# First, generate a mapping template:
cdk import $STACK_NAME --record-resource-mapping mapping.json
# Fill in the physical resource IDs, then import:
cdk import $STACK_NAME --resource-mapping mapping.jsonWorkflow:
1. Add the construct to your CDK code matching the existing resource's properties 2. Run cdk import $STACK_NAME (interactive) or with --resource-mapping (CI) 3. CloudFormation executes an import change set — no resource is created
Constraints:
- Not all CloudFormation resource types support import
- Resources that depend on each other MUST be imported together or in the correct order
- The only allowed changes during import are additions of the imported resources
---
CI-Friendly Import (--import-existing-resources)
For non-interactive, CI-friendly imports, use the --import-existing-resources flag during a normal deploy:
cdk deploy $STACK_NAME --import-existing-resourcesThe CLI matches resources in the synthesized template against existing unmanaged resources in the account by their custom physical name (e.g., explicit bucketName, tableName, roleName). Matches are imported instead of created.
Constraints:
- You MUST set explicit physical names on resources you want to import — auto-generated names cannot be matched
- The resource MUST be unmanaged (not already part of another CloudFormation stack)
- Not every resource type supports CloudFormation import
- Supports mixed operations — you can add new resources AND import existing ones in the same deploy
When to prefer over `cdk import`:
- CI/CD pipelines where interactive prompts are not possible
- Rolling out a new stack that overlaps with existing resources
- Mixed operations (new + imported resources in one change set)
---
Migrating with cdk migrate
cdk migrate generates CDK code from existing CloudFormation stacks, template files, or live account scans.
From an Existing Stack
cdk migrate --from-stack --stack-name $STACK_NAMEFrom a Template File
cdk migrate --from-path $TEMPLATE_FILE_PATH --stack-name $STACK_NAMEFrom a Live Account Scan
cdk migrate --from-scan --stack-name $STACK_NAMEMigration Constraints
- Output is L1 constructs only (
Cfn*classes). Higher-level L2/L3
constructs are NOT generated.
- Only a single stack can be migrated per invocation.
- Assets are not migrated — inline code, S3 references, and Docker images
MUST be handled manually after migration.
First Deploy After Migration
A migrate.json file is generated alongside the CDK code. This file is REQUIRED for the first deployment after migration — it tells CloudFormation to import the existing resources rather than creating new ones.
cdk deploy $STACK_NAMEThe migrate.json file is consumed automatically on the first deploy and MAY be removed afterward.
Incremental Refactoring
After migration, incrementally refactor L1 constructs to L2/L3 constructs:
1. Replace one Cfn* resource at a time with its L2/L3 equivalent. 2. Run cdk diff after each change to verify no unintended replacements. 3. Deploy incrementally to validate each refactoring step.
---
Post-Import Verification
After importing or migrating resources, the following steps MUST be performed:
1. Verify drift — Run cdk drift $STACK_NAME to confirm the imported resource state matches the CDK definition. 2. Protect logical IDs — Logical ID changes after import will cause resource replacement. Lock logical IDs with unit tests or use overrideLogicalId() where necessary. 3. Run `cdk diff` — Confirm no unexpected changes are pending before the next deployment.
Refactor and Prevent Replacement Reference
Table of Contents
- Overview
- Detecting Replacement
- Common Causes
- Using cdk refactor
- Workflow
- Resolving Ambiguity
- Constraints
- Prevention Techniques
- Do Not Hardcode Physical Names
- Use Default as Child ID
- Use cdk refactor for Moves and Renames
- Use overrideLogicalId
- Lock Logical IDs with Unit Tests
- Isolate Stateful Resources with RETAIN
- Protecting Stateful Resources
---
Overview
Resource replacement occurs when CloudFormation determines it must delete and recreate a resource instead of updating it in place. For stateful resources (databases, S3 buckets, encryption keys), replacement causes data loss. This reference covers detection, common causes, and prevention techniques.
---
Detecting Replacement
Use cdk diff to detect pending replacements before deploying:
cdk diff $STACK_NAMEIn the output, look for:
[-]markers indicating resource deletion.[~]markers with "requires replacement" annotations on specific properties.
Any resource showing "requires replacement" MUST be investigated before deploying. MUST NOT deploy when cdk diff shows replacement of stateful resources unless the replacement is intentional and data has been backed up.
---
Common Causes
Resource replacement is typically caused by:
1. Construct ID changes — Renaming a construct or moving it to a different scope changes its CloudFormation logical ID, which CloudFormation treats as a delete + create.
2. Immutable CloudFormation properties — Certain resource properties cannot be updated in place (e.g., DynamoDB table name, RDS engine). Changing these forces replacement.
3. Hardcoded physical names — If a resource has a hardcoded physical name and the logical ID changes, CloudFormation cannot create the new resource because the name is already taken, causing a deployment failure.
---
Using cdk refactor
cdk refactor safely moves or renames constructs without triggering resource replacement. It is currently an unstable feature.
Workflow
1. Deploy a baseline — Ensure the current state is deployed and clean.
cdk deploy $STACK_NAME2. Edit the code — Perform moves and renames only. MUST NOT change resource properties in the same step.
3. Run refactor — Generate the resource mapping:
cdk refactor --unstable=refactor4. Confirm the mapping — Review the proposed logical ID mappings.
5. Deploy — Apply the refactoring:
cdk deploy $STACK_NAME6. Deploy property changes separately — Any property changes MUST be made and deployed in a subsequent step, after the refactor deploy succeeds.
Resolving Ambiguity
When cdk refactor cannot determine the mapping (e.g., multiple resources of the same type were moved), provide an override JSON file to resolve the ambiguity.
Constraints
- Refactoring MUST stay within the same environment (account + region).
- Only moves and renames are supported — property changes MUST NOT be combined
with refactoring in the same deployment.
---
Prevention Techniques
Do Not Hardcode Physical Names
Physical resource names (bucket names, table names, queue names) SHOULD NOT be hardcoded. Let CloudFormation generate unique names. Hardcoded names prevent CloudFormation from performing replacement when needed and cause name-collision failures.
Use Default as Child ID
When extracting inline resources into a separate construct, use 'Default' as the child construct ID to preserve the original logical ID:
// Before: resource defined directly in the stack
new s3.Bucket(this, 'MyBucket', { ... });
// After: extracted into a construct — use 'Default' to keep the same logical ID
class MyConstruct extends Construct {
constructor(scope: Construct, id: string) {
super(scope, id);
new s3.Bucket(this, 'Default', { ... });
}
}
new MyConstruct(this, 'MyBucket');Use cdk refactor for Moves and Renames
When moving or renaming constructs, use cdk refactor --unstable=refactor instead of manually tracking logical IDs. See Using cdk refactor.
Use overrideLogicalId
As an alternative to cdk refactor, explicitly set the logical ID to preserve it across code changes:
const bucket = new s3.Bucket(this, 'NewId', { ... });
(bucket.node.defaultChild as s3.CfnBucket).overrideLogicalId('$ORIGINAL_LOGICAL_ID');This approach SHOULD be used sparingly — it creates a maintenance burden and bypasses CDK's automatic ID generation.
Lock Logical IDs with Unit Tests
Write unit tests that assert the logical IDs of stateful resources. This prevents accidental ID changes from reaching deployment:
test('stateful resource logical IDs are stable', () => {
const template = Template.fromStack($STACK);
const tables = template.findResources('AWS::DynamoDB::Table');
expect(Object.keys(tables)).toContain('$EXPECTED_LOGICAL_ID');
});Isolate Stateful Resources with RETAIN
Place stateful resources in a dedicated stack with the RETAIN removal policy. This ensures that even if the stack is deleted, the resources are preserved:
new s3.Bucket(this, 'DataBucket', {
removalPolicy: RemovalPolicy.RETAIN,
});---
Protecting Stateful Resources
A defense-in-depth approach SHOULD be used for stateful resources:
1. RETAIN removal policy — Prevents data loss on stack deletion. 2. Dedicated stack — Isolates stateful resources from frequently changing application stacks. 3. Logical ID unit tests — Catches accidental renames before deployment. 4. `cdk diff` review — MUST be reviewed before every production deployment. 5. No hardcoded physical names — Avoids name-collision failures during replacement.
Troubleshooting: Credentials and Environment
Table of Contents
- Troubleshooting: Credentials and Environment
- Table of Contents
- Overview
- NoCredentials / ExpiredToken / AssumeRoleFailed
- Error variants
- Diagnosis
- Common causes and fixes
- Bootstrap Version Validation
- Error variants
- Fixes
- Unresolved Account
- Fix — set explicit environment
- Fix — commit context
- Alternatives to context providers
- Account/Region Tokens
- Problem
- Fix
---
Overview
This reference covers authentication, authorization, and environment-resolution errors. These failures occur when the CDK CLI cannot determine who you are, what account/region to target, or whether the bootstrap stack is compatible.
---
NoCredentials / ExpiredToken / AssumeRoleFailed
Error variants
| Error | Meaning |
|---|---|
NoCredentials | No AWS credentials found in the environment |
ExpiredToken | Credentials exist but the session has expired |
AssumeRoleFailed | CLI found credentials but cannot assume the CDK bootstrap role |
AssumeRoleExpiredToken | Token expired during a role assumption chain |
Diagnosis
You MUST run these commands first:
aws sts get-caller-identity
cdk doctorIf get-caller-identity fails, the problem is with your base credentials, not CDK.
Common causes and fixes
No CLI credentials configured:
You MUST configure credentials via one of: ~/.aws/credentials, environment variables (AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY), or SSO.
Wrong profile:
cdk deploy $STACK --profile $PROFILEOr set the environment variable:
export AWS_PROFILE=$PROFILEExpired SSO session:
aws sso login --profile $PROFILEMissing `sts:AssumeRole` on bootstrap roles:
The CDK CLI assumes roles created by cdk bootstrap. If the calling principal lacks sts:AssumeRole permission on those roles, deployment fails. You MUST verify the trust policy on the bootstrap roles allows your identity.
---
Bootstrap Version Validation
Error variants
BootstrapVersionValidation— the deployed bootstrap stack version is too old for the constructs being deployed.SSM parameter /cdk-bootstrap/$QUALIFIER/version not found— the bootstrap stack does not exist in the target account/region, or the qualifier does not match.Cloud assembly schema version mismatch— the CLI version is incompatible with the cloud assembly produced by the CDK library.
Fixes
Re-bootstrap the target environment:
cdk bootstrap aws://$ACCOUNT/$REGIONMatch the qualifier if you use a custom one:
cdk bootstrap aws://$ACCOUNT/$REGION --qualifier $QUALIFIERGrant SSM read access:
The CDK CLI reads the bootstrap version from SSM Parameter Store. The deploying role MUST have ssm:GetParameter permission on /cdk-bootstrap/$QUALIFIER/version.
CLI version mismatch:
You SHOULD pin aws-cdk as a dev dependency to keep the CLI version aligned with the library:
npm install --save-dev aws-cdk@$VERSION
npx cdk deploy $STACKThis prevents drift between the globally installed CLI and the library version used in your project.
---
Unresolved Account
Cannot determine account/region; context providers need concrete valuesContext providers (e.g., Vpc.fromLookup) make API calls at synth time and MUST know the target account and region. Env-agnostic stacks (no explicit env) cannot use context providers.
Fix — set explicit environment
new MyStack(app, 'MyStack', {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
},
});CDK_DEFAULT_ACCOUNT and CDK_DEFAULT_REGION are set automatically by the CDK CLI from your current credentials.
Fix — commit context
You MUST commit cdk.context.json to version control. This file caches the results of context provider lookups so that synth is reproducible without live API calls.
Alternatives to context providers
If you cannot set an explicit environment, you SHOULD use one of:
ec2.Vpc.fromVpcAttributes()— provide VPC ID, AZs, and subnet IDs directly.- SSM Parameter Store lookups at deploy time — store infrastructure values in SSM and read them with
ssm.StringParameter.valueForStringParameter().
---
Account/Region Tokens
stack.account and stack.region return Tokens (lazy placeholders), not real values, when the stack is env-agnostic.
Problem
if (stack.region === 'us-east-1') {
// This NEVER matches — stack.region is a Token string like ${Token[AWS.Region.1234]}
}Tokens are resolved by CloudFormation at deploy time, not at synth time. You MUST NOT use them in synth-time conditional logic.
Fix
Set an explicit environment on the stack so that stack.account and stack.region resolve to real values at synth time:
new MyStack(app, 'MyStack', {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: 'us-east-1',
},
});With an explicit env, synth-time conditionals work as expected. Without it, you MUST use CfnCondition for deploy-time branching instead of TypeScript if statements.
Troubleshooting: Deployment Failures
Table of Contents
- Overview
- Deploy Failure Root Cause Analysis
- Deadly Embrace (Cross-Stack Reference Deadlock)
- UPDATE_ROLLBACK_FAILED Recovery
- Non-Empty Bucket Deletion
---
Overview
This reference covers deployment-time failures — errors that occur after cdk synth succeeds and CloudFormation begins creating or updating resources. The CDK CLI error message is almost never the root cause; you MUST inspect CloudFormation stack events to find the actual failure.
Three error categories exist:
| Category | Meaning |
|---|---|
DeployFailed | CloudFormation resource-level failure |
DeploymentError | Asset publishing or IAM permission failure before CFN executes |
EarlyValidationFailure | Pre-deploy check failed (e.g., bootstrap version mismatch) |
---
Deploy Failure Root Cause Analysis
The CDK CLI surfaces only a terse summary; the real cause is in the failed deployment, not the CLI output. You MUST work through these steps in order.
Step 1: Re-run with --verbose
cdk deploy $STACK --verbosePrints every AWS API call, the change-set diff, and a fuller stack trace (-vv / -vvv for more).
Step 2: cdk diagnose (preferred, CDK CLI ≥ 2.1120.0)
cdk --unstable=diagnose diagnose $STACKInspects the failed deployment and prints the root cause with pointers back to the CDK source that caused it. It runs after the fact, so it also works for diagnosing CI/CD pipeline failures. Requires the --unstable=diagnose flag.
Step 3: CloudFormation events (fallback)
If cdk diagnose is unavailable (older CLI) or you need the raw stream:
aws cloudformation describe-events --stack-name $STACK --filters FailedEvents=truedescribe-events groups events by operation ID and surfaces validation, provisioning, and hook-invocation errors — it supersedes describe-stack-events. The FIRST event in the output is the real root cause; later failures are rollback cascade.
Step 4: Read the ResourceStatusReason
| Reason | Likely cause → fix |
|---|---|
... already exists | Physical-name collision — remove bucketName/tableName/roleName and let CDK auto-generate. |
resource creation cancelled | Not the root — another resource failed first; find that event. |
... in the WAITING state for approximately ... seconds | Stabilization timeout (RDS, ASG signals, long-running Lambda). |
Export X cannot be deleted as it is in use by Stack Y | Cross-stack deadlock — see Deadly Embrace. |
is not authorized to perform ... | The default CDK bootstrap grants AdministratorAccess to the execution role — this error means you're using a customized bootstrap with a restricted execution role, a permissions boundary, or an SCP. Check which specific action/resource is denied, then add only that permission to your custom execution role or permissions boundary. Do NOT widen to * — grant the minimum action on the minimum resource ARN. |
Step 5: Service logs for Lambda / API Gateway / custom resources
CloudFormation only reports that a resource failed. The actual reason (e.g. a custom-resource Lambda threw) is in CloudWatch Logs:
- Lambda:
/aws/lambda/<function-name> - CodeBuild-in-pipeline:
/aws/codebuild/<project> - CloudFormation custom resources: the backing Lambda's log group.
EarlyValidationFailure specifically
Fails BEFORE the change set is submitted — a construct's validate() returned errors, a synth-time assertion tripped, or an addError annotation fired. The message names the exact property and constraint; fix it before redeploying.
If you have the awslabsaws-iac-mcp-server, itstroubleshoot_cloudformation_deploymenttool matches the failure event stream against 30+ known patterns and returns CloudTrail deep links — use it to shortcut Steps 2–4.
---
Deadly Embrace (Cross-Stack Reference Deadlock)
A deadly embrace occurs when Stack A exports a value that Stack B imports, and you then try to remove the export (or the resource behind it). CloudFormation refuses:
Export Stack1:ExportsOutputFnGetAtt-XXXX cannot be deleted as it is in use by Stack2
The deadlock is structural: a safe removal needs B deployed first (so it stops importing), but CDK orders A before B because of the dependency.
Every cross-stack reference has a strength:
- Strong (default) — uses
Fn::ImportValue. CloudFormation blocks the producer from removing the export while any consumer still imports it. - Weak — uses
Fn::GetStackOutput. No coupling; the producer can be changed or deleted independently. - Both — transitional state for migrating strong → weak.
Cross-account references are always weak (strong is unsupported cross-account).
Fix — reference strength (recommended)
CDK supports weakening the reference before removing the resource, with no manual exportValue hacks. You MUST do this as a three-deploy migration.
Weaken all references to a resource — CrossStackReferences.of(resource).produce():
import { CrossStackReferences, ReferenceStrength } from 'aws-cdk-lib';
// Deploy 1 — consumers move to Fn::GetStackOutput; the strong export stays
CrossStackReferences.of(bucket).produce(ReferenceStrength.BOTH);
// Deploy 2 — drop the strong export now that no consumer uses Fn::ImportValue
CrossStackReferences.of(bucket).produce(ReferenceStrength.WEAK);
// Deploy 3 — remove the resource or the reference entirelyWeaken a single reference — Stack.consumeReference():
import { Stack, ReferenceStrength } from 'aws-cdk-lib';
// Deploy 1 — wrap with consumeReference (defaults to BOTH)
new CfnOutput(consumer, 'BucketArn', { value: Stack.consumeReference(bucket.bucketArn) });
// Deploy 2 — switch to WEAK
new CfnOutput(consumer, 'BucketArn', {
value: Stack.consumeReference(bucket.bucketArn, ReferenceStrength.WEAK),
});
// Deploy 3 — remove the resource or reference(Use Stack.consumeListReference() for string-list references.)
Fix — legacy two-deploy (exportValue)
Use this only on CDK versions that lack ReferenceStrength. It MUST be done in exactly two deployments:
Deploy 1 — decouple the consumer, keep the export alive:
1. In consumer Stack B, remove the cross-stack reference (replace with a hardcoded value, SSM lookup, etc.). 2. In producer Stack A, add this.exportValue(resource.attribute) to keep the export alive during the transition. 3. Deploy both.
Deploy 2 — remove the export:
1. In Stack A, remove the this.exportValue() call (and the underlying resource if desired). 2. Deploy again.
You MUST NOT attempt to remove the export and the import in a single deployment.
Manual deploy ordering (cdk deploy -e)
If the consumer already stopped using the value and you control ordering yourself:
cdk deploy -e $CONSUMER_STACK # deploy consumer first (drops the import)
cdk deploy -e $PRODUCER_STACK # then producer, removing the export-e / --exclusively deploys only the named stack and skips dependency reconciliation.
Prevention
- Default cross-stack references to weak for resources you expect to remove or replace. Set app-wide in
cdk.json:
{ "context": { "@aws-cdk/core:defaultCrossStackReferences": "weak" } }- Keep stateful, long-lived resources in their own stack, separate from consumers.
- Use SSM Parameter Store as indirection (producer writes a parameter, consumer reads it) — no CFN export, no embrace.
---
UPDATE_ROLLBACK_FAILED Recovery
A stack enters UPDATE_ROLLBACK_FAILED when CloudFormation cannot roll back a failed update. The stack is wedged and MUST be recovered before any further operations.
Root causes
- Resource deleted out-of-band (e.g., manually deleted in the console).
- Insufficient IAM permissions for the rollback operation.
- Service quota exceeded.
- Resource operation timed out.
Recovery options
Option 1 — Standard rollback:
cdk rollback $STACKOption 2 — Orphan stuck resources:
If a specific resource cannot be rolled back (e.g., it was deleted out-of-band), skip it:
cdk rollback $STACK --orphan $LOGICAL_IDThe resource is removed from the stack's state without attempting to delete or update it.
Option 3 — Force rollback:
cdk rollback $STACK --forcePost-recovery steps
After the stack returns to a stable state, you MUST:
1. Run cdk diff $STACK to understand the current drift. 2. Fix the root cause (restore deleted resources, fix IAM, request quota increase). 3. Redeploy: cdk deploy $STACK.
You SHOULD NOT leave a stack in a recovered-but-drifted state.
---
Non-Empty Bucket Deletion
Setting removalPolicy: cdk.RemovalPolicy.DESTROY alone MUST NOT be expected to delete an S3 bucket that contains objects. CloudFormation cannot empty a bucket during deletion. Versioned buckets are worse — delete markers and non-current object versions persist even after apparent object deletion, so the bucket can appear empty yet still fail to delete.
Fix
You MUST add autoDeleteObjects: true alongside the removal policy:
new s3.Bucket(this, 'MyBucket', {
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
});autoDeleteObjects installs a custom resource Lambda that deletes all object versions and delete markers before CloudFormation attempts to delete the bucket.
You SHOULD only use this pattern in development or test stacks. Production buckets SHOULD retain the default removalPolicy: RETAIN.
---
Lambda Cannot Find Module at Runtime
These errors occur at Lambda invoke time, not during cdk synth. The function deploys successfully but fails when invoked.
Symptom
Cannot find module 'index'
Cannot find module 'aws-sdk'
Runtime.ImportModuleError: No module named 'requests'Cause
- Wrong
handlervalue (e.g.,handler: 'handler'instead ofhandler: 'index.handler') aws-sdkv2 was removed from Node.js 18+ Lambda runtimes — code still imports it- Python dependencies not bundled —
Code.fromAsset()zips the directory without runningpip install
Fix
- Fix handler to match your file and export:
handler: 'index.handler' - Migrate from AWS SDK v2 to v3:
import { S3Client } from '@aws-sdk/client-s3' - Remove
externalModules: ['aws-sdk']from bundling options if present - For Python: use
PythonFunctionfrom@aws-cdk/aws-lambda-python-alphawhich bundles pip dependencies automatically
---
API Gateway Multi-Stage
This is a construct design issue that manifests at deploy time, not a synth failure.
Symptom
Creating a RestApi produces only one stage. Adding extra Stage objects causes conflicts or duplicate deployments.
Cause
RestApi creates a Deployment and a default Stage automatically. Creating additional Stage objects without disabling the default causes conflicts.
Fix
Set deploy: false on the RestApi, then create Deployment and Stage objects explicitly:
const api = new apigateway.RestApi(this, 'Api', { deploy: false });
// ... define resources and methods ...
const deployment = new apigateway.Deployment(this, 'Deployment', { api });
new apigateway.Stage(this, 'Dev', { deployment, stageName: 'dev' });
new apigateway.Stage(this, 'Prod', { deployment, stageName: 'prod' });Troubleshooting: Synth Failures
Table of Contents
- Overview
- Cannot Find Module (Synth Time)
- Asset Errors
- App Required
- Annotation Errors
- Concurrent Lock
- Dependency Cycle
- No Stacks Matched
---
Overview
This reference covers errors that occur during cdk synth — before any CloudFormation deployment begins. These failures prevent the cloud assembly from being produced. Each section maps a specific error class to its root cause and fix.
---
Cannot Find Module (Synth Time)
cdk synth fails with Cannot find module (TS) or ModuleNotFoundError (Python) before producing a template. The error occurs at synth time, not deploy time.
For Cannot find module '@aws-cdk/aws-*' (v1→v2 migration) → see v1-to-v2-migration.For Cannot find module at Lambda runtime → see troubleshooting-deployment.TypeScript — diagnostic flow
Step 1: Run `npx tsc --noEmit`.
- tsc fails → problem is in your TS project. Check: missing
npm ci, wrongtsconfig.jsonpaths/rootDir/typeRoots, duplicateaws-cdk-lib(npm ls aws-cdk-lib), stalenode_modules(rm -rf node_modules && npm ci). - tsc succeeds → problem is in how CDK runs your app. Go to Step 2.
Step 2: Check how `cdk.json` runs your app.
The app field in cdk.json determines the execution mode. The failure causes differ:
If `cdk.json` uses compiled JS (e.g., "app": "node bin/app.js"):
| Cause | Symptom | Fix |
|---|---|---|
outDir mismatch with cdk.json | Cannot find module 'bin/app.js' | Ensure tsconfig.json outDir aligns with the path in cdk.json. If outDir: "dist", then "app": "node dist/bin/app.js" |
Stale compiled .js files | Module existed before but was renamed/deleted in TS | rm -rf cdk.out dist && npm run build && cdk synth |
| Never compiled | .js files don't exist | Run npx tsc or npm run build before cdk synth |
If `cdk.json` uses direct TS execution (e.g., "app": "npx tsx bin/app.ts"):
| Cause | Symptom | Fix |
|---|---|---|
| Path aliases not resolved by ts-node | Cannot find module 'lib/MyStack' | Switch to tsx ("app": "npx tsx bin/my-app.ts"), or register tsconfig-paths with ts-node ("app": "npx ts-node -r tsconfig-paths/register --prefer-ts-exts bin/my-app.ts") |
Monorepo — wrong node_modules | Cannot find module 'typescript' | Verify hoisting: npm ls typescript. Point cdk.json at correct binary. pnpm: shamefully-hoist=true. |
npm link / symlinked packages | Cannot find module '@my/shared-constructs' | Install peer deps explicitly, or NODE_OPTIONS=--preserve-symlinks. Long-term: publish to registry. |
| Wrong working directory | cdk.json not found | cd to directory containing cdk.json |
Python — diagnostic flow
Step 1: Check which Python is running — which python vs the interpreter in cdk.json.
Step 2: Test import — python -c "import aws_cdk; print(aws_cdk.__version__)".
| Cause | Symptom | Fix |
|---|---|---|
| Virtualenv not activated | No module named 'aws_cdk' | source .venv/bin/activate && pip install -r requirements.txt |
Missing pip install | No module named 'my_constructs' | pip install -r requirements.txt |
| CI — venv not activated | Module errors in pipeline | Activate in script, or set "app": ".venv/bin/python app.py" in cdk.json |
| Poetry / Pipenv | CDK runs outside managed env | "app": "poetry run python app.py" or "app": "pipenv run python app.py" |
cannot import name 'core' from 'aws_cdk' | v1→v2 API change | Replace from aws_cdk import core with import aws_cdk as cdk. See v1-to-v2-migration. |
Prevention
- You SHOULD use
tsxinstead ofts-node— native path alias support, faster - You SHOULD run
npm ci(TS) orpip install -r requirements.txt(Python) as the first CI step - You SHOULD install
aws-cdkCLI as a pinned dev dependency and invoke vianpx cdk
---
Asset Errors
Asset errors occur when CDK cannot locate, bundle, or publish file or Docker image assets.
CannotFindAsset
The asset path does not exist at synth time.
Fix: You MUST use path.join(__dirname, ...) to build asset paths relative to the source file, not the working directory:
new lambda.Function(this, 'Fn', {
code: lambda.Code.fromAsset(path.join(__dirname, '../lambda')),
// ...
});FailedToBundleAsset
The bundling command failed. Common cause: Docker is not running.
Fix: You MUST ensure Docker is running before synth. For Lambda bundling with esbuild, you SHOULD install esbuild locally to avoid the Docker fallback:
npm install --save-dev esbuildAssetBuildFailed
esbuild or Docker build returned a non-zero exit code.
Fix: Run the bundling command manually outside CDK to see the full error output. Check for missing dependencies, syntax errors, or incompatible platform targets.
AssetPublishFailed
The asset was built successfully but upload to the bootstrap S3 bucket or ECR repository failed.
Fix: You MUST verify that the CDK publishing role has permission to write to the bootstrap bucket and ECR repository. Re-bootstrap if necessary:
cdk bootstrap aws://$ACCOUNT/$REGION---
App Required
--app is required either in command-line, in cdk.json, or in ~/.cdk.jsonThe CDK CLI cannot find the app entry point.
Fix: You MUST add the app key to cdk.json:
{
"app": "npx tsx bin/$APP_NAME.ts"
}You SHOULD verify the path points to the file containing your new App() call.
---
Annotation Errors
An Aspect or construct called Annotations.of(node).addError(), which causes synth to fail. This covers:
- cdk-nag errors — security/compliance rule violations.
- Custom Aspect errors — organization-wide policy checks.
- Built-in CDK warnings promoted to errors by the
--strictflag.
Diagnosis
You MUST fix the underlying issue flagged by the annotation. Read the error message to identify which construct and which rule triggered it.
Suppression (last resort)
You SHOULD only suppress annotations when the flagged pattern is intentional and justified. Suppression patterns for cdk-nag:
Per-resource:
NagSuppressions.addResourceSuppressions(myBucket, [
{ id: '$RULE_ID', reason: '$JUSTIFICATION' },
]);Per-stack:
NagSuppressions.addStackSuppressions(myStack, [
{ id: '$RULE_ID', reason: '$JUSTIFICATION' },
]);By path:
NagSuppressions.addResourceSuppressionsByPath(stack, '/$STACK/$CONSTRUCT_PATH', [
{ id: '$RULE_ID', reason: '$JUSTIFICATION' },
]);You MUST NOT suppress annotations without providing a reason.
---
Concurrent Lock
Cannot lock cdk.out: file is locked by another processA file lock on the cdk.out directory prevents synth. This happens when a previous synth crashed or when multiple synth processes target the same output directory.
Fix — single build
rm -rf cdk.outFix — parallel CI
You MUST use a unique output directory per build to avoid lock contention:
cdk synth --output ./cdk.out.$BUILD_ID---
Dependency Cycle
Error: 'StackA' depends on 'StackB' depends on 'StackA'A circular reference exists between two or more stacks.
Fixes
1. Extract shared resource into a third stack. The shared resource lives in its own stack, and both consumers depend on it (one-way).
2. Use SSM for late-binding. The producer writes a value to SSM Parameter Store; the consumer reads it at deploy time. This breaks the synth-time dependency:
// Producer stack
new ssm.StringParameter(this, 'Param', {
parameterName: '/$APP/$RESOURCE_ARN',
stringValue: resource.resourceArn,
});
// Consumer stack
const arn = ssm.StringParameter.valueForStringParameter(this, '/$APP/$RESOURCE_ARN');3. Pass raw ARN strings instead of construct references when the full construct object is not needed.
Prevention
You SHOULD design stack dependencies as one-way: props flow from producer to consumer. You MUST NOT create reverse references from a producer back to its consumer.
---
No Stacks Matched
No stacks match the name(s) $STACK_NAMECDK selects stacks by their logical ID (the second argument to the Stack constructor), not by the CloudFormation stack name.
Diagnosis
List all stack IDs in the app:
cdk listDeploy options
# Exact logical ID
cdk deploy $STACK_ID
# Wildcard
cdk deploy "$PATTERN*"
# All stacks
cdk deploy --allYou MUST use the logical ID as shown by cdk list, not the CloudFormation stack name visible in the AWS console.
CDK v1 to v2 Migration
Table of Contents
---
Overview
CDK v2 consolidated all @aws-cdk/* packages into a single aws-cdk-lib package and moved Construct to the standalone constructs package. These changes cause three common error patterns when migrating from v1 or when mixing v1/v2 code.
---
V1 Import Paths
Symptom
Cannot find module '@aws-cdk/aws-ec2'
Cannot find module '@aws-cdk/aws-s3'
Cannot find module '@aws-cdk/core'Cause
CDK v2 consolidated all @aws-cdk/* packages into aws-cdk-lib. Old v1 package names no longer resolve.
Fix
Replace v1 imports with v2 equivalents:
// Wrong (v1)
import * as ec2 from '@aws-cdk/aws-ec2';
import * as s3 from '@aws-cdk/aws-s3';
import { Construct } from '@aws-cdk/core';
// Correct (v2)
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as s3 from 'aws-cdk-lib/aws-s3';
import { Construct } from 'constructs';You MUST also remove all @aws-cdk/* packages from package.json dependencies and replace with a single aws-cdk-lib dependency.
---
Wrong Construct Import
Symptom
Argument of type 'this' is not assignable to parameter of type 'Construct'This error appears even though the code looks correct — the types have the same name but come from different packages.
Cause
Construct was imported from @aws-cdk/core or aws-cdk-lib instead of the standalone constructs package. In CDK v2, all constructs MUST extend Construct from the constructs package.
Fix
// Wrong
import { Construct } from 'aws-cdk-lib';
import { Construct } from '@aws-cdk/core';
// Correct
import { Construct } from 'constructs';You MUST ensure constructs is listed as a dependency in package.json.
---
Duplicate aws-cdk-lib
Symptom
Argument of type 'Function' is not assignable to parameter of type 'IFunction'
Argument of type 'Bucket' is not assignable to parameter of type 'IBucket'TypeScript uses structural typing, but CDK classes contain private members, which causes TypeScript to treat them nominally. When two copies of aws-cdk-lib exist, the private members originate from different class declarations, making types like Function and IFunction from different copies incompatible.
Cause
Multiple copies of aws-cdk-lib exist in the module graph. Common causes:
- Monorepo with improperly hoisted dependencies
- Shared construct library declares
aws-cdk-libas a regular dependency instead of a peer dependency npm linkorfile:protocol pulling in a second copy
Diagnosis
npm ls aws-cdk-libIf more than one version appears, you have duplicates.
Fix
1. You MUST make aws-cdk-lib and constructs peer dependencies in shared construct libraries 2. Run npm dedupe to collapse duplicates 3. In monorepos, hoist aws-cdk-lib to the root workspace 4. Verify with npm ls aws-cdk-lib — only one copy SHOULD appear
If npm dedupe alone does not resolve it, reset the install:
rm -rf node_modules && npm ci && npm dedupeRelated skills
How it compares
Use for CDK construct work; defer raw CloudFormation, SAM, Terraform, or broad CI/CD to other tooling.
FAQ
What causes deadly embrace in CDK?
Removing a cross-stack export while another stack still imports it deadlocks deployment; weaken references across three deploys first.
Why must I run cdk diff before prod deploy?
Renaming or moving constructs changes logical IDs and CloudFormation may replace stateful resources causing data loss.
How do I fix UPDATE_ROLLBACK_FAILED?
Use cdk rollback STACK or cdk rollback STACK --orphan LogicalId per the troubleshooting deployment reference.
Is Aws Cdk safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.