CDK Bootstrap Errors: SSM Parameter Not Found and More

Logeshwaran
—

If cdk bootstrap or cdk deploy is failing, the fix is nearly always one command: run cdk bootstrap aws://ACCOUNT-ID/REGION against the exact account and Region the error names, then match any leftover message below. The counterintuitive part: the scary "invalid principals" error on a pipeline deploy usually means the account you are deploying into was never bootstrapped, not that anything is broken in the account where the error appears.

⚡ Quick Answer

• "SSM parameter ... version not found" → that account and Region were never bootstrapped: cdk bootstrap aws://ACCOUNT-ID/REGION

• "already exists" on the S3 bucket → the bucket name is taken, usually by you, in another Region. Find it before you rename anything.

• "Invalid principals" or "AssumeRole" on a pipeline → bootstrap the target account with --trust pointing at the pipeline's account

• Bootstrap stack too old for your CDK → run cdk bootstrap again; it upgrades an existing stack in place

Not sure which one you have? Start with the two-minute triage, then use the section that matches your error text.

Jake's message landed at 9:40 on a Tuesday morning: "Deploy just died. Something about a toolkit and a version number. I promised this customer their inventory app by lunch." He had inherited a half-finished AWS CDK project from a contractor who had stopped answering messages, and a $400 setup job was now hostage to one command he had never heard of.

That is how bootstrap errors usually arrive: mid-deploy, under a deadline, worded for someone who already knows what a "toolkit stack" is. This post is written for the person who doesn't, and it covers every error you can hit, plus the cases nobody spells out.

What bootstrapping is, in plain English

The AWS Cloud Development Kit, or CDK, lets you describe your cloud setup in a normal programming language instead of clicking through the AWS console. When you run cdk deploy, the CDK turns your code into an AWS CloudFormation template, which is a text file (JSON or YAML) listing every resource you want. CloudFormation is the AWS service that reads that file and actually creates the resources.

Before any of that can happen, the CDK needs somewhere to park the things your app depends on: the zipped code for a Lambda function, a Docker image, other file assets. Bootstrapping is the setup step that creates that parking area. It provisions three kinds of resources in your account:

  • An Amazon S3 bucket. S3 is Amazon's file storage service, and a bucket is a named container in it. This one holds your Lambda code and other assets.
  • An Amazon ECR repository. ECR is the Elastic Container Registry, which is basically a private shelf for Docker images.
  • IAM roles. IAM is Identity and Access Management, and a role is a bundle of permissions that a person, service, or tool can borrow temporarily. These roles are what give the CDK and CloudFormation the permission to do the deploying.

The template behind all this is written and maintained by the CDK team. When you run cdk bootstrap, the CDK CLI (the command-line program you type into) fetches that template and deploys it to CloudFormation as a stack, which is just a named group of resources that CloudFormation tracks together. The default name is CDKToolkit, and it shows up in the CloudFormation console once it succeeds.

Ethan explained it to Jake with something from Jake's own world. "Before a shipment of phone cases arrives, you clear a shelf in the back room, right? You don't want the delivery guy stacking boxes in the doorway. Bootstrap is clearing the shelf. CloudFormation is the delivery. If the shelf isn't there, the delivery has nowhere to go, and it doesn't matter how good your product catalog is."

Jake grumbled that he'd had exactly that Monday last month, three boxes of screen protectors and nowhere to put them. Ethan said that was the point.

An "environment" means one account in one Region

Here is the fact behind most bootstrap errors. The CDK's word for a target is an environment, meaning one AWS account paired with one AWS Region (a Region is a geographic AWS location such as us-east-1). Environments are independent, and each one has to be bootstrapped before you deploy into it. Bootstrapping us-east-1 does nothing for us-west-2 in the same account, and it does nothing for a second AWS account you also own.

The resources get predictable names in the format cdk-<qualifier>-<description>-<account-ID>-<Region>. The qualifier is a nine-character string that defaults to hnb659fds, and the actual value has no significance. An example bucket name is cdk-hnb659fds-assets-012345678910-us-west-1. You will see that hnb659fds string in error messages constantly, and it is not a typo or a leaked secret.

🙋‍♂️ Jake's Reality Check

"So I have to do this for every account and every Region I ever touch? Nobody told me that."

The straight answer. Yes. I recommend bootstrapping every environment you plan to use before you need it. Doing it early also stops surprises like a bucket-name clash later, which we'll get to.

One more thing to know up front: bootstrapping can add AWS charges, because the resources it creates cost money when they are used. The bucket, repository, and roles are small, but they are real. The section on what bootstrap leaves behind covers the one item that has historically caught people out.

🧭 NEW HERE? READ THESE FIRST

New to the CDK? These explain what bootstrap is actually setting up for you:

⚡ Start here if you have never run cdk bootstrap on purpose.

Which of these is happening to you? A two-minute triage

Before you change anything, spend two minutes finding out what your terminal is actually pointed at. A surprising share of "bootstrap is broken" problems are really "my terminal is logged into a different account than I thought."

  1. Run aws sts get-caller-identity. It prints the account number your credentials belong to. If you use named profiles, add --profile your-profile-name.
  2. Run aws configure get region to see your default Region. If your CDK stack sets an explicit env property, that account and Region win over your defaults, so read it too.
  3. Run cdk --version. If it looks old, update it with npm install -g aws-cdk and retry. The AWS troubleshooting guide asks you to do this before any other bootstrap troubleshooting.
  4. Open the CloudFormation console for that account and Region and look for a stack named CDKToolkit. If it is there, this environment has been bootstrapped at least once. If it is missing, you have found your problem.
  5. If the stack exists, find its BootstrapVersion output. That number tells you how old the bootstrap setup is, which matters for the version errors further down.

Now match your symptom:

What you see What it usually means Go to
SSM parameter /cdk-bootstrap/hnb659fds/version not found This account and Region were never bootstrapped Never bootstrapped
Bootstrap cannot tell which account or Region to use Missing credentials, a wrong profile, or no environment given Credentials and environment
CREATE_FAILED on an S3 bucket that "already exists" The default bucket name is taken, usually by an earlier bootstrap of yours Bucket already exists
"Policy contains a statement with one or more invalid principals" The target account of a cross-account deploy is not bootstrapped Cross-account errors
Not authorized to perform sts:AssumeRole on a lookup role Missing bootstrap, missing trust, or missing permission on the caller Lookup role
A complaint that the bootstrap stack is too old The CDK version you run expects a newer bootstrap template Version mismatch

Ethan's advice to Jake before he touched a single keystroke: "Read the error twice. The first read is panic. On the second read you'll notice it usually names the account, the Region, or the role. It's rarely as vague as it feels."

"SSM parameter ... version not found": the environment was never bootstrapped

This is the most common bootstrap error, and once you know the vocabulary it is refreshingly literal. It looks like this:

Deployment failed: Error: BootstrapExampleStack: SSM parameter /cdk-bootstrap/hnb659fds/version not found. Has the environment been bootstrapped? Please run 'cdk bootstrap'

An SSM parameter is a small named value stored in AWS Systems Manager. Bootstrapping creates one that records which version of the bootstrap template is installed, and your deploy looks for it first. If it isn't there, the CDK concludes, correctly, that nobody has ever bootstrapped this account and Region. The deploy stops before CloudFormation even starts.

Can you skip bootstrapping in some cases? Barely. In an environment that is not bootstrapped, only stacks without assets, and with synthesized templates under 51,200 bytes, will deploy. A stack that holds a Lambda function's code, a Docker image, or a big template needs the bucket or repository, so it needs bootstrapping.

The fix, step by step

  1. Get your account number with aws sts get-caller-identity. The AWS docs also note you can find it in the AWS Management Console.
  2. Decide the Region. Use the Region named in your error or your stack's env, not just your default.
  3. Run cdk bootstrap aws://123456789012/us-east-1, substituting your own account number and Region. The aws:// prefix is optional, so cdk bootstrap 123456789012/us-east-1 works too.
  4. Wait for it to finish. It deploys a CloudFormation stack, so the first run takes a little while.
  5. Run your cdk deploy again.

If you are running the command from the folder that holds your CDK project's cdk.json file and you give no environment, the CDK bootstraps every environment referenced in your app, or works out one from default sources. Those sources include a --profile option, environment variables, and your default AWS CLI settings. That convenience is also a trap, which the next subsection explains.

To bootstrap several environments at once, list them: cdk bootstrap aws://123456789012/us-east-1 aws://123456789012/us-east-2.

When bootstrap can't tell which account you mean

Two rules in the CLI reference explain most "it bootstrapped the wrong thing" and "it can't find an account" reports. First, if your app contains environment-agnostic stacks (stacks that don't say which account and Region they belong to), the CLI bootstraps your default account and Region, or the one from --profile. Second, outside an app, you must state the environment explicitly. Either way, credentials must be configured for that account and Region, for example in ~/.aws/credentials.

So if the CLI complains that it can't work out an account, or your credentials aren't found, do these in order: run aws sts get-caller-identity to see whether you have working credentials at all; if you use named profiles, run it with --profile and pass the same profile to cdk bootstrap; and if you are outside a project folder, type the full aws://ACCOUNT-ID/REGION.

If aws sts get-caller-identity itself fails, bootstrap was never going to succeed. That is a credentials problem to solve first, and no CDK setting will fix it.

"But I already bootstrapped this account"

You probably did, in a different Region. Because each environment is separate, a stack whose env says eu-west-1 gets nothing from a bootstrap you ran in us-east-1. Check the Region in the error message against the Region where you actually see the CDKToolkit stack in the console. Jake's contractor had bootstrapped one Region and then deployed to another, and the failure looked like a broken account when it was a wrong Region.

CREATE_FAILED: the S3 bucket "already exists"

This one appears during the bootstrap itself, not during a later deploy:

CREATE_FAILED | AWS::S3::Bucket | <BucketName> already exists

S3 bucket names are global. Each one must be unique across all AWS accounts in all Regions within a partition (a partition is a separate AWS "universe," like the standard commercial one). The other bootstrap resources are scoped to your account, but the bucket name is shared with the whole partition. The default name includes your account ID and Region, so a collision with a stranger is unlikely though possible. Far more often, the bucket that already exists is yours, created by an earlier bootstrap.

Here is a detail almost nobody knows. The Region written inside a bucket's name does not necessarily mean the bucket lives in that Region. So a bucket named for us-west-1 might be sitting in a different Region, and an "is it in this Region?" check on the console alone can mislead you.

Jake said it felt like finding his lunch in the office fridge with his name on it, just on the wrong floor. Ethan said that was a fair description, and also the reason to search every floor before buying a new lunch.

Work through it in this order

  1. Update the CLI first: cdk --version, then npm install -g aws-cdk if it is behind. Retry the bootstrap. AWS asks you to do this before troubleshooting further.
  2. List your buckets with aws s3 ls, or look in the S3 console for the name in your error. Check more than one Region.
  3. If you find it, check whether it belongs to a bootstrap stack. For each Region you might have used, run aws cloudformation describe-stack-resources --stack-name CDKToolkit --region <Region>, or open the CDKToolkit stack in the CloudFormation console and look at the Resources tab.
  4. Act on what you found, using the table below.
What you found What it means What to do
Bucket belongs to a CDKToolkit stack in the Region you are bootstrapping You are already bootstrapped Stop. Start using cdk deploy.
Bucket belongs to a CDKToolkit stack in a different Region Two bootstraps are fighting over one name Rename or delete the old bucket if unused, or give the new bootstrap a different bucket name
Bucket exists in your account, not tied to any bootstrap stack A leftover, or something you made by hand If unused, consider deleting it and bootstrapping again; if used, pick a new name
No matching bucket anywhere in your account It probably belongs to another AWS account Customize the names: a new bucket name, or a new qualifier

Renaming only the bucket

Use cdk bootstrap --bootstrap-bucket-name 'my-new-bucket-name'. Read the reference note carefully: the CLI creates this bucket, and it "must not currently exist." So this option can't point bootstrap at a bucket you already own. It only names a new one.

Then comes the part people skip. Your app has to be told the new name, or it will keep looking for the default one. You do that in the stack's synthesizer (the piece that decides which bootstrap resources your generated templates refer to). AWS's example in TypeScript is synthesizer: new DefaultStackSynthesizer({ fileAssetsBucketName: 'my-new-bucket-name' }), and in Python it is synthesizer=DefaultStackSynthesizer(file_assets_bucket_name='my-new-bucket-name').

✅ Why this is the one to use

Rename only the bucket, not the whole setup. The alternative, --qualifier, renames every bootstrap resource, and there is usually no need to change the qualifier. Ethan's view: "A new bucket name fixes a naming clash. A new qualifier fixes a problem you don't have, and adds one you didn't."

How to stop this happening again

My advice is to bootstrap each environment you plan to use ahead of time. That creates the bucket in each environment and holds the name, so nobody else can take it. It costs nothing but a minute per environment.

"Invalid principals" and the other cross-account errors

This is the reveal from the top of the post, in full. A CDK Pipeline is an automated delivery line: code changes in one account flow through a pipeline that deploys into other accounts, often separate development, staging, and production accounts. When such a deploy fails with this message, you see it on a KMS key (KMS is the AWS key management service, used for encryption):

CREATE_FAILED | AWS::KMS::Key | Pipeline/Pipeline/ArtifactsBucketEncryptionKey
Policy contains a statement with one or more invalid principals.

A "principal" in IAM is whoever a permission is granted to, such as an account or a role. The message means CloudFormation tried to grant access to a role in the other account, and that role does not exist. The most likely cause is that the target environment has not been bootstrapped. So the failure lands on an encryption key, which looks like an encryption problem, when the missing piece is upstream in an account you haven't touched yet.

The fix: bootstrap the target and trust the pipeline's account

Here is the pattern for bootstrapping the target account, where 111111111111 is the pipeline's account and 222222222222 is the target:

npx cdk bootstrap --cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess --trust 111111111111 aws://222222222222/us-east-2

You would run that with credentials for the target account, using --profile if you keep separate profiles. Three details matter here.

  1. --trust lists extra accounts allowed to deploy into the environment. The account doing the bootstrapping is always trusted already.
  2. When you use --trust, you must also pass --cloudformation-execution-policies, which names the permissions CloudFormation will deploy under in that account.
  3. To trust two accounts, repeat the flag: --trust 234567890123 --trust 987654321098. The CLI prints a "Trusted accounts for deployment" line so you can read back what you set.

⚠️ What this actually breaks

If you add a trusted account to an existing bootstrap, you must list every account you want trusted, including the earlier ones. Provide only the new account and the previously trusted accounts are removed. A pipeline that worked yesterday can start failing today because someone "just added one account."

Read the CLI reference's warning before you copy the command above unchanged. The modern template effectively grants the permissions implied by --cloudformation-execution-policies to any AWS account on the trust list, and by default that means read and write access to any resource in the bootstrapped account. For a throwaway sandbox that may be fine. For production you should choose the policies and the trusted accounts deliberately, as the permissions section explains.

"Not authorized to perform sts:AssumeRole" on a lookup role

This shows up in the pipeline's Synth step and looks like this:

Could not assume role in target account using current credentials ... is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::222222222222:role/cdk-hnb659fds-lookup-role-222222222222-us-east-1

A "lookup" is when your CDK code asks AWS a question while generating templates, such as "which VPC is the default?" The lookup role is the read-only role that answers. "Assume a role" just means "borrow its permissions," and sts:AssumeRole is the permission to do that borrowing. The AWS pipeline guide lists exactly three causes:

  • The target environment has not been bootstrapped.
  • The target environment was bootstrapped without the right --trust relationship.
  • The build environment's execution role (for example, a CodeBuild role) does not have permission to call sts:AssumeRole.

Check them in that order. Notice that the role name carries the qualifier, the target account, and the Region. If your app looks for us-east-1 and you bootstrapped us-east-2, the role it wants does not exist.

Two more options. You can avoid lookups in the pipeline altogether by running them on your own machine and committing the resulting cdk.context.json file to source control. Or you can use --trust-for-lookup at bootstrap time to let an account look up information without giving it permission to deploy. That second option is handy when a build server should read but never change.

When your bootstrap stack is older than your CDK

The bootstrap template is versioned and changes over time as the CDK itself changes. Apps and CDK versions can require a minimum version, and an account bootstrapped long ago may sit below it. Your first job is to read the number that is there. The bootstrap stack keeps its version in the SSM parameter /cdk-bootstrap/hnb659fds/version (with your qualifier in place of the default, if you changed it) and in a CloudFormation output named BootstrapVersion.

For the fix, run cdk bootstrap again against the same environment. It is fine to bootstrap an environment more than once: an existing bootstrap stack is upgraded if needed, and otherwise nothing happens. You are not rebuilding anything, and you are not touching your applications.

Why the template keeps changing

Every version in the AWS history table exists because something real changed. Here are the entries most likely to explain why a deploy that worked last year suddenly wants a newer bootstrap:

Template version First in CDK What changed
92.1.0S3 asset uploads no longer rejected by a commonly used encryption service control policy
132.25.0Container images in bootstrap-created ECR repositories became immutable
142.34.0ECR image scanning turned off at the repository level by default, so Regions without scanning can be bootstrapped
222.160.0sts:TagSession added to the trust policy of the bootstrap roles
24 and 252.165.0Noncurrent bucket objects kept 30 days instead of 365; incomplete multipart uploads removed after 1 day
292.1026.0AssumeRole calls that supply an ExternalId are rejected by default, unless disabled
322.1120.0Latest listed in the AWS docs when this was written; several deploy-role permissions folded into an AWS-managed read-only policy

🕐 What changed between versions

  • Before: earlier bootstrap templates created an AWS KMS key in every bootstrapped environment by default.
  • Now: the current default creates no KMS key. If your older bootstrap still has one and you don't want the charge, re-bootstrap with --no-bootstrap-customer-key.
  • Also now: since template version 29, AssumeRole calls that provide an ExternalId are rejected by default. If a cross-account automation of yours depends on an ExternalId, check it before you upgrade a production bootstrap.

If you customized the template, the upgrade may refuse

If you deployed your own modified template, AWS suggests changing its BootstrapVariant parameter so that someone running the default cdk bootstrap can't overwrite your changes by accident. The CLI then only allows overwriting a stack with a template that has the same BootstrapVariant and an equal or higher version. So a bootstrap that refuses to run on a customized stack is behaving as designed. Update your own template from the current default, keep your variant name, and deploy it with cdk bootstrap --template your-template.yaml.

Permissions: who can bootstrap, and what the roles can do

Sometimes bootstrap fails with a plain "access denied" that has nothing to do with the CDK's own errors. The person running cdk bootstrap needs, at a minimum, broad permissions on five services: CloudFormation (cloudformation:*), ECR (ecr:*), SSM (ssm:*), S3 (s3:*), and IAM (iam:*). IAM is on the list because bootstrapping creates roles. AWS also warns that the bootstrap stack changes over time, so the permissions it needs may change too.

That is a strong set of permissions, and it is meant for whoever bootstraps, not for everyone who deploys afterward. The practical pattern is that an administrator bootstraps each environment, and developers deploy through the roles that bootstrap creates. Those roles are worth knowing by name, because their names appear in errors:

Role What it does Shows up when
CloudFormationExecutionRoleA service role CloudFormation uses to make the AWS calls that deploy your stacksA deploy is denied creating one of your resources
DeploymentActionRoleAssumed by the CDK CLI to deploy; its trust controls who can deploy in, including from other accountsCross-account deploys fail
FilePublishingRoleUploads and deletes assets in the bootstrap S3 bucketA file asset can't be uploaded
ImagePublishingRoleWorks with the bootstrap ECR repositoryA Docker image can't be pushed
LookupRoleRead-only role for context lookups such as VPCsSynth fails on a lookup

If you replace any of these with your own roles, the synthesizer needs the ARNs of the roles you chose, because the CDK expects to find all five.

🙋‍♂️ Jake's Reality Check

"You said CloudFormation deploys with full administrator rights. Isn't that insane?"

The straight answer. By default, yes, stacks deploy with the AdministratorAccess policy. That's convenient, and AWS lets you change it with --cloudformation-execution-policies. Just remember that narrowing it too far makes deployments fail, because the policies must cover everything you deploy there.

Narrowing the execution permissions

Pass the ARNs of the managed policies you want as one comma-separated string, for example cdk bootstrap --cloudformation-execution-policies "arn:aws:iam::aws:policy/AWSLambda_FullAccess,arn:aws:iam::aws:policy/AWSCodeDeployFullAccess". That is the standard example.

Permissions boundaries

A permissions boundary is an IAM feature that sets the maximum permissions an identity can ever have. It doesn't grant anything by itself. It only caps what other policies can grant. To apply one at bootstrap time, use cdk bootstrap --custom-permissions-boundary your-boundary-name, giving the name of a policy that already exists. There is also --example-permissions-boundary, which uses an example boundary that the CDK supplies. The two options can't be used together. The boundary is attached to the CloudFormationExecutionRole, which sets the ceiling for what your developers can grant through the CDK.

✅ Why this is the one to use

For a personal sandbox, take the defaults and move on. For anything with real customer data, choose narrower execution policies, a short trust list, and a permissions boundary. Ethan puts it plainly: "Full admin is a fine default for a sandbox. It is a bad default for a shop's customer records."

Customizing bootstrap without breaking the next deploy

Most people never need to. When you do, the useful rule is the "bootstrap contract": whatever you change in bootstrap, your app's synthesizer has to agree, because when the CDK generates templates it doesn't know how your environment was bootstrapped. It only knows what the synthesizer tells it.

Option What it does Watch out for
--bootstrap-bucket-nameNames the S3 bucketBucket must not exist yet; tell your synthesizer the new name
--qualifierChanges the hnb659fds part of every resource nameYour app must pass the same qualifier
--bootstrap-kms-key-id or --bootstrap-customer-keyUses your own KMS key, or creates a customer-managed one, for bucket encryptionThe two are not compatible with each other; a key you create is billed
--cloudformation-execution-policiesSets what CloudFormation deploys withToo narrow breaks deploys
--trust and --trust-for-lookupAllows other accounts to deploy or to look upList every account each time
--tags KEY=VALUETags the bootstrap stackCan be repeated
--toolkit-stack-nameRenames the CDKToolkit stackUse the new name in later checks
--termination-protectionBlocks accidental deletion of the stackWorks on new and existing stacks

About the qualifier

People change the qualifier because they hope it will isolate two teams from each other. Not so: changing it is intended for name separation between automated tests of the CDK itself, and unless you can scope the CloudFormation execution role's permissions very precisely, two bootstrap stacks in one account give you no permission isolation. If you do change it, pass the value in your app, either in the stack's synthesizer or by setting "@aws-cdk/core:bootstrapQualifier" under context in cdk.json.

Turn on termination protection

Run cdk bootstrap --termination-protection. Then confirm it with aws cloudformation describe-stacks --stack-name CDKToolkit --query "Stacks[0].EnableTerminationProtection", which should print true. If you renamed the stack, use your name. AWS is blunt about why: deleting a bootstrap stack deletes the resources it created, and for CDK Pipelines there is no general recovery.

Bootstrapping without the CDK command

You don't have to use the CDK CLI. You can copy the template from the aws-cdk-cli GitHub repository or print it with cdk bootstrap --show-template, then deploy it with any CloudFormation tool. That includes CloudFormation StackSets or AWS Control Tower, which suits large numbers of accounts, and the console or the AWS CLI. The AWS CLI equivalent is aws cloudformation create-stack --stack-name CDKToolkit --template-body file://bootstrap-template.yaml --capabilities CAPABILITY_NAMED_IAM --region us-west-1. Copy that --capabilities flag as shown.

Windows users, take note. On Windows you must use PowerShell to save the template, to preserve its encoding: powershell "cdk bootstrap --show-template | Out-File -encoding utf8 bootstrap-template.yaml". In a Windows command prompt, AWS's multi-line example uses ^ at the end of a line instead of \. And if CDK notices leak into your template output, add --no-notices.

If you bootstrap from a CodeCatalyst workflow, note that the bootstrap action deploys the modern template and updates an existing bootstrap stack if needed. It does not support a custom template, and it isn't for you if you want an existing stack left alone. CodeCatalyst is no longer open to new customers.

Special situations: profiles, other partitions, strict organizations, and CI/CD

Most guides assume one person, one laptop, and one account. Real setups are messier, and each of these has its own way of going wrong.

Named profiles and single sign-on

If your credentials live in named profiles, pass the profile to both the AWS CLI and the CDK, or the two can point at different accounts. My tip: run aws sts get-caller-identity --profile prod to see the account behind a profile, and then run cdk bootstrap --profile prod so the CDK uses the same one. If the two commands point at different accounts, you can bootstrap one account and then deploy into another, and the second one will report that it was never bootstrapped.

Other AWS partitions

The bucket-name rule is "unique within a partition," not "unique in the world." AWS partitions are separate AWS "universes," and the standard commercial one is only one of them. If you work in a different partition, a name that is free in the commercial one may still be taken in yours, and the reverse. The advice for a collision is the same either way: locate the bucket, and rename yours if you must.

Regions and organizations with strict rules

Two entries in the template history exist for exactly these people. Template version 14 turned off ECR image scanning at the repository level by default, so that you can bootstrap Regions that don't support image scanning. Template version 9 fixed S3 asset uploads being rejected by a commonly used encryption service control policy (an organization-wide rule set in AWS Organizations). If you work under such rules and your bootstrap is old, check the BootstrapVersion against that table before you spend an afternoon on the rule itself, then re-run cdk bootstrap to bring the stack up to date.

CI/CD systems

A build server is a poor place to discover a missing bootstrap. I recommend bootstrapping each environment ahead of time. Doing that as a deliberate one-off step, by a person with the right permissions, means the pipeline only has to deploy. If a pipeline does bootstrap, remember that the identity it runs as needs the permissions from the earlier list, and that trust must include the pipeline's account for any target account.

Legacy vs. modern bootstrap, for inherited projects

Old CDK projects can carry an old bootstrap. CDK version 1 supported a legacy and a modern template, and CDK version 2 supports only the modern one. CDK v1 entered maintenance on June 1, 2022 and ended support on June 1, 2023, so a project still on it needs attention for more than bootstrap reasons.

Feature Legacy (v1 only) Modern (v2)
Cross-account deploysNot allowedAllowed
Deploy permissionsThe current user's own credentialsPermissions chosen when the stack was bootstrapped
VersioningOne versionVersioned; apps can require a minimum
Resource namesAutomatically generatedDeterministic

To move an environment from legacy to modern, re-bootstrap it. AWS's advice is to re-deploy all your CDK applications in that environment at least once before you delete the legacy bucket. Ethan's version: "Don't demolish the old loading dock until the last truck has used the new one."

If AWS Security Hub flags your bootstrap stack

Security Hub may report finding KMS.2 on the DeploymentActionRole, because a policy statement there uses a wildcard resource for KMS decryption. AWS reviewed this configuration and says it doesn't constitute a security problem. The statement is limited by a condition to requests from S3 in the same Region, and the wildcard exists because a pipeline's KMS key doesn't exist yet when you bootstrap. If you haven't made that role more permissive, there's nothing to fix.

If an audit requires you to close the finding anyway, there are two routes. If you use CDK Pipelines for cross-account deploys, put your pipeline's real KMS key ARN into the template's PipelineCrossAccountArtifactsKey statement and deploy it with cdk bootstrap aws://ACCOUNT-ID/REGION --template bootstrap-template.yaml. If you don't use cross-account pipelines, delete the PipelineCrossAccountArtifactsBucket and PipelineCrossAccountArtifactsKey statements and deploy the same way.

What bootstrap costs and what it leaves behind

Because bootstrapping creates resources, you may incur charges when they are used with the CDK. I'm not quoting prices here because they belong on AWS's pricing pages, but you should know where the money can hide. The KMS key is the famous one: earlier bootstrap templates created it by default, and keys cost money. The current default doesn't create one, and if you want to control encryption yourself, --bootstrap-customer-key creates a key and the CLI reference says you will be charged for it.

Storage is the other item. The bootstrap bucket holds your uploaded assets. Since template version 24, noncurrent objects in it are kept for 30 days rather than 365, and version 25 removes incomplete multipart uploads after 1 day. Version 24 also came with the cdk gc command, which can delete objects in the bootstrap bucket, so understand exactly what it deletes before you use it on a shared account.

Five beliefs about bootstrapping that cause most of the trouble

Each of these sounds reasonable, and each one is wrong.

  1. "Bootstrap once and I'm finished." Once per environment, yes, and there are two follow-ups. The CDK team periodically updates the template, and you should update your bootstrap stack when it does. And a brand-new Region or account is a brand-new environment that needs its own bootstrap.
  2. "It only matters if I use Lambda or Docker." Technically, a small stack with no assets and a template under 51,200 bytes can deploy without it. In practice stacks grow, and the day yours picks up a Lambda function is the day it fails. I recommend bootstrapping every environment you plan to use, up front.
  3. "A different qualifier will keep my teams separate." A second bootstrap stack in the same account gives no permission isolation unless you scope the execution role's permissions very precisely. The qualifier is for name separation, not security.
  4. "I can point --bootstrap-bucket-name at a bucket I already have." The CLI reference says the bucket "must not currently exist," because the bootstrap creates it. Reusing an existing bucket is a customization job, not a flag.
  5. "When in doubt, delete the CDKToolkit stack and redo it." Deleting removes the resources it created, and for pipelines there is no general recovery. Re-running cdk bootstrap is the safe way to upgrade.

Jake read the list and admitted he had believed three of the five. Ethan said that was a good score. "Most people believe the delete-and-redo one, because it's the one that works on a laptop."

After bootstrap succeeds: how to know it worked and what to do next

A success message from the CLI is nice, and a second look costs almost nothing. Here is a short routine for confirming the environment is ready:

  1. Open the CloudFormation console in the same account and Region, and find the CDKToolkit stack (or your custom stack name). Its Resources tab should list the bucket, repository, and roles.
  2. Check its BootstrapVersion output, and compare that number with the template history in the AWS docs.
  3. If you customized anything, such as the qualifier or the bucket name, confirm that your app's synthesizer or your cdk.json uses exactly the same values.
  4. If this environment matters, turn on termination protection and read it back with aws cloudformation describe-stacks --stack-name CDKToolkit --query "Stacks[0].EnableTerminationProtection".
  5. Run cdk deploy.

If you look after many accounts, it is worth deciding early whether to use the CDK command or a template-based tool. I recommend the cdk bootstrap command when you don't need significant changes. The template route is more flexible and suits large-scale rollouts, since you can push the same template through StackSets or Control Tower. And if you keep your own modified template, keep it in step with the canonical one, because a template that has fallen behind can quietly stop supporting newer CDK features.

Jake's shop now has a habit he wrote on a sticky note above the register: new AWS account, bootstrap it before lunch, add termination protection, and go back to selling phones.

When the fix doesn't work

Work through the list in order, cheapest first. Confirm your identity with aws sts get-caller-identity. Confirm the Region in the error against the Region of your CDKToolkit stack. Update the CLI. Re-run cdk bootstrap. Then look at trust, permissions, and customization. Most problems fall before that last step.

Here is what a failed fix looks like, and what it means:

  • The same "not found" error after bootstrapping. You bootstrapped a different account or Region than the one your deploy uses. Compare the two.
  • A role name in the error you don't recognize. Look at the qualifier and Region inside it. A custom qualifier in bootstrap that isn't set in your app produces this exact confusion.
  • Access denied while bootstrapping. Your identity lacks the permissions listed earlier. No CDK setting fixes that.
  • A pipeline that suddenly fails after someone edited trust. Check that the trust list still contains every account you need.

Some things can't be fixed from here. If you don't hold administrator rights, you need someone who does. If a bootstrap stack was deleted while a pipeline depended on it, there is no general recovery, and you're re-bootstrapping and re-publishing assets, not restoring anything. If the bucket name belongs to a stranger's account, no amount of retrying will change it. Only a new name will.

Popular forum advice says to delete the CDKToolkit stack and start over. That is the wrong first move. It removes the bucket, repository, and roles that your existing deployments rely on, and re-running cdk bootstrap already upgrades the stack in place. Deleting is the drastic option, and the right move is to update the stack rather than delete and recreate it unless you are intentionally doing so.

Jake's problem turned out to be a Region mismatch, exactly the kind of thing the two-minute triage exists to catch. He now bootstraps new accounts the day he creates them. Ethan calls that the most boring habit in cloud work, and the one that saves the most Tuesdays.

Frequently asked questions

What is CDK bootstrapping in plain English?

It is the one-time setup that prepares an AWS account and Region for the CDK. It creates an S3 bucket for your assets, an ECR repository for Docker images, and IAM roles that let the CDK and CloudFormation deploy for you. You do it before your first deploy into each environment.

Do I need to bootstrap every AWS account and Region separately?

Yes. Each environment, meaning one account in one Region, is independent, and each must be bootstrapped before you deploy into it. Bootstrapping one Region does nothing for another Region or another account.

What does cdk bootstrap actually create in my account?

A CloudFormation stack named CDKToolkit by default. It holds an S3 bucket, an ECR repository, five IAM roles (CloudFormationExecutionRole, DeploymentActionRole, FilePublishingRole, ImagePublishingRole, and LookupRole), and an SSM parameter that records the bootstrap version.

Why do I get "SSM parameter /cdk-bootstrap/hnb659fds/version not found"?

Because the account and Region you are deploying into have never been bootstrapped. Run cdk bootstrap aws://ACCOUNT-ID/REGION for that exact environment, then deploy again.

Why does bootstrap fail with "already exists" for the S3 bucket?

S3 bucket names must be unique across every account and Region in a partition, and the default bootstrap name is predictable. Most often the bucket is yours from an earlier bootstrap. Find it, check whether it belongs to a CDKToolkit stack, and if you need a new name, use the bootstrap bucket name option and update your synthesizer to match.

What does "Policy contains a statement with one or more invalid principals" mean?

The IAM roles CloudFormation tried to reference do not exist in the other account, most likely because that target account was never bootstrapped. Bootstrap it with the trust option naming the pipeline's account, plus cloudformation-execution-policies.

Why am I not authorized to perform sts:AssumeRole on a lookup role?

There are three causes: the target environment is not bootstrapped, it was bootstrapped without the right trust relationship, or the build role lacks permission to call sts:AssumeRole. You can also avoid lookups in the pipeline by committing cdk.context.json to source control.

How do I check which bootstrap version my account is running?

Read the SSM parameter at /cdk-bootstrap/hnb659fds/version, using your own qualifier if you changed it, or read the BootstrapVersion output on the CDKToolkit stack. Compare it with the template version history in the AWS docs.

Is it safe to run cdk bootstrap more than once?

Yes. It is fine to bootstrap an environment more than once. An existing bootstrap stack is upgraded if necessary, and otherwise nothing happens.

What permissions do I need to run cdk bootstrap?

At a minimum, broad permissions on CloudFormation, ECR, SSM, S3, and IAM, because bootstrapping creates IAM roles. The required permissions may change as the bootstrap stack changes.

What is the difference between legacy and modern bootstrapping?

The legacy template, for CDK v1 only, deploys with the current user's credentials and does not allow cross-account deployments. The modern template, the only one CDK v2 supports, allows cross-account deploys, uses permissions set when the stack was bootstrapped, and is versioned.

Why is my bootstrap stack costing me money for a KMS key?

Earlier bootstrap templates created a KMS key by default, and the current default does not. If your environment still has the old key and you don't want it, re-bootstrap with the no-bootstrap-customer-key option.

How do I bootstrap an account that a CDK pipeline in another account will deploy into?

Bootstrap the target account with the trust option set to the pipeline's account ID, and pass cloudformation-execution-policies as well. If you add accounts later, list every account you want trusted, because previously trusted accounts you leave out are removed.

Can I delete the CDKToolkit stack?

You can, but AWS advises against deleting and recreating it, because deleting removes the resources it provisioned, and for CDK Pipelines there is no general recovery. Update the stack by re-running cdk bootstrap, and enable termination protection to prevent accidents.

Can I deploy without bootstrapping at all?

Only in a narrow case. In an environment that is not bootstrapped, only stacks without assets, and with synthesized templates under 51,200 bytes, will deploy. Anything with Lambda code, Docker images, or a larger template needs bootstrapping.

How do I rename the bootstrap bucket or change the qualifier?

Use the bootstrap bucket name option to name a new bucket, which must not already exist, and set fileAssetsBucketName in your synthesizer to match. Use the qualifier option to change the qualifier, and pass the same value to your app. There is usually no need to change the qualifier.

πŸ“– ALSO READ

Deploy going through? These fix the credential and role errors that show up next:

⚡ Bookmark these — they solve the errors that hide behind a CDK deploy.

Revision note. Written September 2026, covering AWS CDK v2 and the modern bootstrap template, with the bootstrap template version history current at the time of writing (latest: version 32). It will need updating when AWS publishes a new template version or changes the wording of these errors. If you've spent a morning staring at a deploy that won't go through, you are not slow, and you are almost certainly one command away from working.

Related