Skip to content

Fix: AWS CDK Not Working — Bootstrap Error, ROLLBACK_COMPLETE, and Deploy Failures

FixDevs · (Updated: )

Part of:  JavaScript & TypeScript Errors

Quick Answer

How to fix AWS CDK errors — cdk bootstrap required, stack in ROLLBACK_COMPLETE, asset bundling failed, CLI/library version mismatch, VPC lookup failing, and cross-stack export conflicts.

The Error

You run cdk deploy and it fails immediately:

❌ Deployment failed: Error: This stack uses assets, so the toolkit stack must be deployed
to the environment (account 123456789012/us-east-1)

Or your stack is stuck and every deploy attempt fails:

❌  MyStack failed: Error [ValidationError]: Stack:arn:aws:cloudformation:us-east-1:123456789012:stack/MyStack/xxx
is in ROLLBACK_COMPLETE state and can not be updated.

Or bundling silently fails:

❌ Bundling did not produce any output. Check that content is written to /asset-output.

Or your CDK v1 code imports break after upgrading:

Cannot find module '@aws-cdk/aws-s3' or its corresponding type declarations.

Each of these has a specific fix. None requires rewriting your infrastructure code.

Why This Happens

CDK works in two phases: synthesis (cdk synth) converts your TypeScript/Python code into CloudFormation templates, then deployment (cdk deploy) uploads those templates and assets to AWS. Most errors fall into one of three categories:

  • Environment not ready: AWS account/region hasn’t been bootstrapped, stack is in a bad state
  • Asset pipeline broken: Docker isn’t running, versions are mismatched, or paths are wrong
  • CDK model error: circular dependencies, cross-stack reference conflicts, lookup failures

Understanding which phase failed narrows the fix significantly. Synthesis runs entirely on your machine: it walks your construct tree, resolves tokens, and writes JSON into cdk.out/. Nothing touches AWS yet. Deployment then talks to CloudFormation, which compares the new template against the existing stack and issues change-set operations. The vast majority of “CDK isn’t working” reports turn out to be CloudFormation isn’t working — CDK was just the messenger that surfaced an underlying CFN limitation, IAM denial, or quota issue.

The other piece of context worth carrying: CDK is a code generator on top of CloudFormation. It doesn’t have its own runtime in AWS. Every construct ultimately becomes one or more CloudFormation resources, and every error message that contains AWS::Something::Something is a CloudFormation error, not a CDK error. When in doubt, read the CloudFormation events tab in the AWS console for the exact resource that failed and the exact reason — that’s the ground truth CDK is reflecting back to your terminal.

Fix 1: “Toolkit Stack Must Be Deployed” — Run cdk bootstrap

CDK needs a set of AWS resources in each account/region before it can deploy stacks that use assets (Lambda code, Docker images, bundled files). These resources — an S3 bucket, ECR repository, and IAM roles — live in a CloudFormation stack called CDKToolkit.

The first time you deploy to any account or region, run bootstrap:

# Bootstrap the current account/region
cdk bootstrap

# Bootstrap a specific account and region
cdk bootstrap 123456789012/us-east-1

# Bootstrap with explicit credentials
cdk bootstrap --profile my-aws-profile

When you need to re-bootstrap:

  • First deployment to a new AWS account
  • First deployment to a new region
  • After a major CDK version upgrade that requires updated bootstrap resources
  • When the CDKToolkit stack was deleted

Check bootstrap status: Open the CloudFormation console and look for a stack named CDKToolkit. If it’s there and in CREATE_COMPLETE or UPDATE_COMPLETE state, bootstrap is done.

Permissions required to bootstrap: The IAM user or role running cdk bootstrap needs cloudformation:*, s3:*, ecr:*, iam:*, and ssm:*. In practice, AdministratorAccess is common for bootstrapping, though you can scope it down once the CDKToolkit stack exists.

Fix 2: Stack in ROLLBACK_COMPLETE State

CloudFormation puts a stack into ROLLBACK_COMPLETE when the initial creation failed and rolled back. The stack exists but contains no resources — and CloudFormation refuses to update it. You must delete it first:

# Delete the broken stack, then redeploy
cdk destroy MyStack
cdk deploy MyStack

For deeper detail on CloudFormation stack states and recovery, see AWS CloudFormation rollback complete. If cdk destroy fails (common when the stack has retained resources or deletion policies), delete it from the CloudFormation console directly:

  1. Go to the CloudFormation console
  2. Select the stuck stack
  3. Click Delete and wait for DELETE_COMPLETE
  4. Run cdk deploy again

For UPDATE_ROLLBACK_FAILED (stack update failed and the rollback also failed): CloudFormation has a recovery mechanism specifically for this. In the console, choose Continue Update Rollback from the stack actions menu. This attempts to complete the rollback so the stack returns to a stable state.

Why stacks get stuck: A resource creation takes too long, an IAM permission is missing for one specific resource, a naming conflict with an existing AWS resource, or a dependency isn’t ready yet. The CloudFormation events tab shows the exact resource that failed — always check it before redeploying.

Fix 3: “Bundling Did Not Produce Any Output” — Docker Not Running

CDK uses Docker to bundle certain Lambda functions in a Linux environment (regardless of your OS). This ensures the bundled output is compatible with the Lambda runtime. If Docker isn’t running, bundling fails:

❌ Bundling did not produce any output. Check that content is written to /asset-output.

Fix: Start Docker Desktop and retry cdk deploy. If Docker itself fails to start, see Docker daemon not running for platform-specific fixes.

CDK uses Docker for:

  • NodejsFunction with TypeScript source
  • PythonFunction from aws-lambda-python-alpha
  • Any construct with a custom bundling.image
  • Container image assets

Local bundling fallback — if you can’t run Docker (e.g., CI/CD with restricted permissions), configure local bundling:

import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';

new NodejsFunction(this, 'MyFunction', {
  entry: 'src/handler.ts',
  bundling: {
    forceDockerBundling: false, // Try local bundling first
  },
});

Local bundling uses whatever node, esbuild, or python is available on the host. It’s faster for development but requires the correct toolchain to be installed locally.

In CI/CD pipelines: If you’re running CDK inside a Docker container (Docker-in-Docker), mount the Docker socket explicitly. GitHub Actions with setup-node and Docker pre-installed works without extra configuration, but some CI systems need explicit Docker socket access.

Fix 4: CLI and Library Version Mismatch

CDK has two separate packages:

  • aws-cdk — the CLI (cdk deploy, cdk synth, cdk diff)
  • aws-cdk-lib — the construct library (the code you write)

If these are on incompatible versions, deployment fails with:

This CDK CLI is not compatible with the CDK library used by your application.
Please upgrade the CLI to the latest version.

Or, if the CLI is newer than the library:

Maximum schema version supported is 43.x.x, but found 44.0.0

Fix: Keep both at matching versions:

# Update the library
npm install aws-cdk-lib@latest

# Update the global CLI
npm install -g aws-cdk@latest

# Or use npx to avoid global version conflicts entirely
npx cdk deploy

Prefer npx cdk over cdk in project scripts. npx uses the locally installed version from node_modules/.bin/cdk, ensuring the CLI always matches the library version in your project.

// package.json — pin CLI to same version as library
{
  "dependencies": {
    "aws-cdk-lib": "2.150.0",
    "constructs": "^10.0.0"
  },
  "devDependencies": {
    "aws-cdk": "2.150.0"
  },
  "scripts": {
    "deploy": "npx cdk deploy"
  }
}

Common Mistake: Installing aws-cdk globally with npm install -g aws-cdk and then forgetting to update it when you bump aws-cdk-lib in your project. The global CLI and the local library fall out of sync silently — your package.json shows the right version but cdk --version reports something older. Always use npx cdk in project scripts to avoid this entirely.

Note: CDK v2 uses monotone versioning — all AWS service modules are in a single aws-cdk-lib package with one version number. You don’t need to sync dozens of @aws-cdk/aws-* packages like you did in v1.

Fix 5: CDK v1 Import Errors in v2

The single most common error when upgrading from CDK v1 to v2:

// CDK v1 imports — no longer work in v2
import * as s3 from '@aws-cdk/aws-s3';
import * as lambda from '@aws-cdk/aws-lambda';
import { Construct } from '@aws-cdk/core';

In CDK v2, all AWS service modules moved into aws-cdk-lib and Construct moved to the standalone constructs package:

// CDK v2 — correct imports
import { Construct } from 'constructs';          // Construct base class
import * as cdk from 'aws-cdk-lib';              // Core CDK classes
import * as s3 from 'aws-cdk-lib/aws-s3';       // S3 service
import * as lambda from 'aws-cdk-lib/aws-lambda'; // Lambda service

Complete migration of a stack file:

// BEFORE (v1):
import * as cdk from '@aws-cdk/core';
import * as s3 from '@aws-cdk/aws-s3';
import * as lambda from '@aws-cdk/aws-lambda';
import { Construct } from '@aws-cdk/core';

export class MyStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    new s3.Bucket(this, 'MyBucket');
  }
}

// AFTER (v2):
import * as cdk from 'aws-cdk-lib';
import * as s3 from 'aws-cdk-lib/aws-s3';
import { Construct } from 'constructs';          // Different package

export class MyStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    new s3.Bucket(this, 'MyBucket');
  }
}

After updating imports, remove all @aws-cdk/* packages from package.json and install aws-cdk-lib and constructs:

npm uninstall @aws-cdk/core @aws-cdk/aws-s3 @aws-cdk/aws-lambda # etc.
npm install aws-cdk-lib constructs

Fix 6: Context Lookup Failures — VPC Not Found

Vpc.fromLookup() queries your AWS account at synthesis time to find an existing VPC. If CDK can’t find it (wrong account, wrong region, wrong filters), synthesis fails or returns dummy-value:

// This fails if CDK can't query your account or no VPC matches the filter
const vpc = ec2.Vpc.fromLookup(this, 'ImportedVpc', {
  vpcName: 'my-vpc'
});

Error 1: Stack environment not specified

fromLookup() needs to know which account and region to query. If your stack doesn’t specify an environment, CDK uses environment-agnostic mode and can’t perform lookups:

// Fix: Specify environment on the stack
new MyStack(app, 'MyStack', {
  env: {
    account: process.env.CDK_DEFAULT_ACCOUNT,
    region: process.env.CDK_DEFAULT_REGION
  }
});

Error 2: Stale cdk.context.json

CDK caches lookup results in cdk.context.json. If the VPC was modified or recreated after the cache was written, CDK uses the stale data:

# Clear context and re-run lookup
rm cdk.context.json
cdk synth

Or delete just the specific key from cdk.context.json and re-run cdk synth.

Error 3: “All arguments to Vpc.fromLookup must be concrete”

If you pass a CloudFormation token (a value that’s only known at deploy time) to fromLookup(), synthesis fails because lookups happen at synthesis time, before deploy-time values exist:

// WRONG — env var used at synth time might be a token
const vpcId = process.env.VPC_ID!; // This is fine if it's a real value
const vpc = ec2.Vpc.fromVpcAttributes(this, 'Vpc', {
  vpcId,
  availabilityZones: ['us-east-1a', 'us-east-1b']
});

// If VPC_ID isn't a concrete value, use fromLookup with known tags instead
const vpc = ec2.Vpc.fromLookup(this, 'Vpc', {
  tags: { 'environment': 'production' }
});

Pro Tip: Commit cdk.context.json to source control. This ensures CI/CD pipelines don’t need live AWS access to perform lookups — they use the cached values from the last developer run. Only delete it locally when you need to re-query.

Fix 7: “Export Cannot Be Deleted” — Cross-Stack Reference Conflict

When Stack A exports a value used by Stack B, CloudFormation tracks that dependency. If you remove the reference from Stack B’s code but don’t deploy Stack B first, Stack A can’t delete its export:

Export MyStack:ExportsOutputFnGetAttMyBucketBucketArnXXXXXX cannot be deleted as it is
in use by AnotherStack

This requires a two-step deploy. Don’t redeploy Stack A yet:

# Step 1: Deploy Stack B first (removes the import of Stack A's export)
cdk deploy AnotherStack

# Step 2: Now deploy Stack A (export is no longer in use, safe to remove)
cdk deploy MyStack

If you need to add a cross-stack reference and the export doesn’t exist yet, deploy Stack A first:

cdk deploy StackA  # Creates the export
cdk deploy StackB  # Imports the export

Avoid this class of problem entirely: For values that need to be shared across stacks, consider using SSM Parameter Store or Secrets Manager instead of CloudFormation exports. These don’t create hard deployment-order dependencies.

CDK vs Terraform, Pulumi, SST, Serverless Framework, CloudFormation

Infrastructure-as-code is a crowded category. The right tool depends on language preference, cloud spread, and whether you want to write code or DSL.

AWS CDK. TypeScript/Python/Java/Go/.NET that compiles to CloudFormation. AWS-only. Strongest fit when your stack is entirely AWS and you want full programming-language constructs (loops, functions, type checking) rather than HCL. Tied to CloudFormation’s pace and quirks — when a new AWS service ships, CDK gets coverage as soon as CloudFormation does. Pick CDK for AWS-only shops that want code, not DSL.

Terraform. HCL (its own DSL) over a provider model that covers AWS, GCP, Azure, Cloudflare, GitHub, Datadog, hundreds more. Doesn’t depend on CloudFormation, so it manages resources CFN never supported. State is stored separately (S3, Terraform Cloud, local). The community is huge; modules in the registry are battle-tested. Pick Terraform when you span multiple clouds or you want to manage GitHub/SaaS resources alongside cloud infrastructure. State locking issues are a Terraform-specific problem — see Fix: Terraform Error Locking State.

Pulumi. Like CDK in spirit — real programming languages (TypeScript, Python, Go, .NET, Java) — but supports every Terraform provider. Multi-cloud out of the box. Encryption-at-rest for state managed by Pulumi Cloud (or self-hosted). The trade-off: a paid service for the easy workflow; self-hosting works but requires more setup. Pick Pulumi when you want CDK-style code with Terraform-style cloud coverage.

SST (Serverless Stack). Built on top of AWS CDK with an extra developer-experience layer: live Lambda development, dev mode that proxies events to your laptop, opinionated constructs for typical web app patterns (Next.js, Astro, API, queues). Pick SST when you’re building a serverless web app on AWS and want the “Vercel-but-on-AWS” experience. Inherits all CDK errors and gotchas.

Serverless Framework. YAML-driven, older, focused on Lambda + API Gateway + DynamoDB style apps. Multi-cloud (AWS, Azure, GCP) via plugins. Lighter than CDK or Terraform; opinionated specifically for serverless workloads. Pick Serverless Framework for simple serverless apps where YAML is fine and you don’t need CDK’s flexibility.

CloudFormation (raw). JSON/YAML templates, the AWS-native option. No abstraction. You write AWS::Lambda::Function blocks directly. Pick raw CFN only when you need to debug a CDK-generated template, or in environments where installing CDK isn’t an option. CDK’s generated templates are typically 3–10x longer than hand-written equivalents because of safety defaults.

Quick decision table:

NeedPick
AWS-only, real code, TypeScriptCDK
Multi-cloud, DSL acceptableTerraform
Multi-cloud, real codePulumi
AWS serverless web app + DXSST
Pure serverless, YAML, multi-cloudServerless Framework
Direct CFN debugging or minimal toolsCloudFormation

The programming language vs DSL axis matters more than people expect. Loops and conditionals in HCL are awkward (for_each, count, dynamic blocks). The same logic in CDK is just JavaScript: a for loop creating constructs. For teams that already write TypeScript, CDK and Pulumi reduce the context switch. For teams that maintain modules across many projects, Terraform’s module registry and terraform-aws-modules/* ecosystem is unmatched.

The state model axis is the other split. CDK has no separate state — CloudFormation is the state. Terraform and Pulumi maintain their own state files that must be locked, versioned, and protected. State drift, lock contention, and tfstate corruption are real Terraform operational concerns that don’t exist in CDK at all. The opposite is true for CDK: you inherit CloudFormation’s quirks (ROLLBACK_COMPLETE, slow updates, no partial deploys without --hotswap).

If you’re migrating between IaC tools, the most common path is Terraform → CDK (or Terraform → Pulumi) when teams consolidate on AWS and want better language tooling. The migration is rarely worth doing wholesale — bridge stacks (importing CFN-managed resources into Terraform via data sources, or vice versa) work for years.

Still Not Working?

Circular Dependencies Between Stacks

If cdk synth fails with “A cyclic dependency between stacks detected”, two stacks are referencing each other. The fix is to move shared resources to a third stack that both import:

// Stack A references Stack B, Stack B references Stack A — circular
// Fix: Extract shared resources to SharedStack
class SharedStack extends cdk.Stack {
  public readonly bucket: s3.Bucket;
  constructor(scope: Construct, id: string) {
    super(scope, id);
    this.bucket = new s3.Bucket(this, 'Shared');
  }
}

class StackA extends cdk.Stack {
  constructor(scope: Construct, id: string, shared: SharedStack) {
    super(scope, id);
    // Reference shared.bucket — no circular dep
  }
}

Lambda Runtime.ImportModuleError After Deploy

If cdk deploy succeeds but Lambda fails with Runtime.ImportModuleError: Cannot find module 'xyz', the dependency wasn’t bundled. See AWS Lambda import module error for the full breakdown of this error class, including layer-based dependency issues.

new NodejsFunction(this, 'MyFunction', {
  entry: 'src/handler.ts',
  bundling: {
    // Explicitly bundle packages that aren't part of the Lambda runtime
    nodeModules: ['my-custom-package'],
    // Packages available in Lambda runtime (don't bundle these)
    externalModules: ['@aws-sdk/*']
  }
});

AWS SDK v3 (@aws-sdk/*) is available in Node.js 18+ Lambda runtimes and should be in externalModules. Third-party packages must be in nodeModules or they’ll be missing at runtime.

AccessDenied During Deploy

CDK deploy uses IAM roles created during bootstrap. If someone deleted those roles or your credentials don’t have permission to assume them:

# Check which identity you're deploying as
aws sts get-caller-identity

# Check the bootstrap roles exist in IAM
aws iam list-roles | grep cdk

The role name pattern is cdk-XXXXX-deploy-role-ACCOUNTID-REGION. If it doesn’t exist, re-run cdk bootstrap.

Use --hotswap for Faster Development Iterations

For Lambda code changes during development, --hotswap bypasses CloudFormation and updates the function directly — seconds instead of minutes:

cdk deploy --hotswap          # Direct update, skips CloudFormation for supported resources
cdk watch                     # Continuous: re-deploys with hotswap on file save

Supported: Lambda code/config, ECS container definitions, Step Functions state machines. Never use --hotswap in production — it creates drift between your CDK code and actual CloudFormation state.

Check CloudFormation Events for Specific Failures

When cdk deploy prints a generic failure, the actual cause is in the CloudFormation events. View them without leaving the terminal:

aws cloudformation describe-stack-events \
  --stack-name MyStack \
  --query 'StackEvents[?ResourceStatus==`CREATE_FAILED` || ResourceStatus==`UPDATE_FAILED`].[LogicalResourceId,ResourceStatusReason]' \
  --output table

For Terraform users moving to CDK, many Terraform state lock concepts apply similarly — CloudFormation has its own state management that can get stuck in the same ways.

Deployment failures that start succeeding in the AWS console but still fail via cdk deploy often indicate that the CDK bootstrap roles lack permission for a specific new resource type you added. The console uses your interactive credentials; CDK uses the bootstrap deploy/file-publishing roles. Inspect the trust policy and inline policy on cdk-*-deploy-role-* and cdk-*-cfn-exec-role-* to confirm they include the IAM actions for the new resource.

cdk diff Shows Resources Will Be Replaced That You Didn’t Touch

CloudFormation considers some property changes “replacing” rather than “updating” — changing the name of an S3 bucket, the partition key of a DynamoDB table, or the VPC ID of an RDS instance all force replacement. CDK exposes this in cdk diff with a yellow or red marker on the replaced resource. Before hitting deploy, scroll through cdk diff output and confirm no production resources are about to be recreated. If they are, the safer path is to import the new resource side-by-side, dual-write, then cut over — not let CloudFormation delete the existing one.

cdk deploy --hotswap Reports Success but Lambda Still Runs Old Code

--hotswap updates Lambda code directly via the Lambda API, bypassing CloudFormation. If the change you made was to environment variables or memory size, hotswap silently falls back to a real CloudFormation update — but if the change was to anything inside the bundling step (a new npm dependency in nodeModules, a TypeScript build flag), hotswap may report success while the deployed function is missing the dependency. Run cdk deploy (no --hotswap) once after dependency changes to force a full template update.

Asset Hashes Cause Constant Updates Even When Source Didn’t Change

CDK computes asset hashes from the contents of bundled output. If your bundler (esbuild, webpack, tsc) emits timestamps, the hash changes every build even when source didn’t. Each deploy then ships a new Lambda version. Fix by setting bundling.assetHashType: AssetHashType.SOURCE so the hash is computed from input rather than output, or pass --no-progress --color=false to your bundler so its output is byte-stable.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles