Skip to content
cdkd

Problems destroying a stack

These entries are for a cdkd destroy that stops on one resource, skips one, or finishes and still leaves something billed in the account.

On this page:

"bucket is not empty" / "still contains images" on destroy

Failed to delete S3 bucket MyBucket: bucket my-bucket is not empty. Matching
CloudFormation, cdkd does not delete a non-empty bucket unless it opted into
automatic emptying (CDK's autoDeleteObjects: true, i.e. the
'aws-cdk:auto-delete-objects' tag).
Failed to delete S3 Express Directory Bucket my-bucket--use1-az4--x-s3: bucket
my-bucket--use1-az4--x-s3 is not empty. Matching CloudFormation, cdkd does not
delete a non-empty directory bucket without an explicit opt-in.
Failed to delete ECR Repository MyRepo: repository my-repo still contains
images. Matching CloudFormation, cdkd does not force-delete an image-carrying
repository unless it opted in via EmptyOnDelete: true (CDK's emptyOnDelete) or
the 'aws-cdk:auto-delete-images' tag (CDK's autoDeleteImages).

Like CloudFormation, cdkd destroy does not delete a bucket that still holds objects or a repository that still holds images, unless the resource opted in to being emptied. You have two ways forward.

The first is to opt in from the CDK app, redeploy, and then destroy:

Resource Opt-in
S3 bucket autoDeleteObjects: true with removalPolicy: RemovalPolicy.DESTROY
S3 Express directory bucket The tag on the L1: tags: [{ key: 'aws-cdk:auto-delete-objects', value: 'true' }]
ECR repository emptyOnDelete: true, or the older autoDeleteImages: true

The second is to empty the bucket or repository by hand and re-run the destroy:

# a versioned bucket also needs its versions and delete markers removed
aws s3 rm s3://my-bucket --recursive
aws ecr batch-delete-image --repository-name my-repo \
  --image-ids "$(aws ecr list-images --repository-name my-repo \
    --query 'imageIds' --output json)"

Destroy flags & guards has the full rules.

"has DeletionPolicy: Snapshot, but ..." refusal on delete

MyDb (AWS::RDS::DBInstance) has DeletionPolicy: Snapshot, but the resource is
managed via the Cloud Control API route (provisionedBy: cc-api), which has no
final-snapshot delete parameter — deleting it now would destroy its data WITHOUT
the final snapshot the policy promises.

Before cdkd deletes a resource with DeletionPolicy: Snapshot, it takes a final snapshot, as CloudFormation does. It refuses the delete only when it cannot take one. That happens in two cases:

  • cdkd manages the resource through the Cloud Control API, whose delete has no final-snapshot parameter.
  • The template puts Snapshot on a type that does not support it.

Take the snapshot yourself, then do one of:

  • Re-run with --skip-final-snapshot, which deletes without a snapshot.

    aws rds create-db-snapshot --db-instance-identifier mydb \
      --db-snapshot-identifier mydb-final
    cdkd destroy MyStack --skip-final-snapshot
    
  • Change the policy to Retain and delete the resource by hand afterwards.

Destroy flags & guards has the per-type behaviour.

"Backing Lambda for custom resource X no longer exists" on destroy

Backing Lambda for custom resource X no longer exists (arn:aws:lambda:...), so its Delete handler cannot be invoked and cdkd cannot confirm the resource was deleted; skipping deletion — anything this custom resource manages may still be LIVE.

The resource's row in the destroy output reads skipped (backing Lambda function is gone — Delete handler not invoked). cdkd keeps the state record, and cdkd destroy exits 2.

A custom resource is deleted by invoking its handler, the Lambda function named by its ServiceToken. That function is gone, so the handler cannot run, and cdkd cannot tell whether the things the custom resource manages still exist. There are two usual ways to get here:

  • A shared provider stack was destroyed before the stacks that use it.
  • An earlier destroy skipped this resource and then deleted the function in the same run.

What to do depends on whether the function can be restored:

  • The function can come back at the same ARN (redeploy the shared provider stack): do that, then re-run cdkd destroy.

  • It cannot: confirm by hand that what the handler manages is gone, or tear it down. Then drop the stack's records:

    cdkd state orphan MyStack --stack-region us-east-1
    

    This drops every record for the stack in that region, not only this one.

The same situation during a deploy

When a cdkd deploy removes such a custom resource, cdkd only warns and drops the record. CloudFormation also ignores delete failures in the cleanup phase of an update.

destroy reports N skipped and exits 2

⚠ MyGlueTable (AWS::Glue::Table) skipped (malformed physicalId in state — no delete issued)
⚠ Stack MyStack partially destroyed (4 deleted, 1 skipped, 0 errors). cdkd did not confirm the skipped resource(s) were deleted, so they may still exist in AWS.

cdkd could not work out which AWS resource the state record points at, so it did not try to delete it.

A state record holds the resource's physical id. Some types need more than one value to find a resource, so cdkd stores them joined with |: <databaseName>|<tableName> for AWS::Glue::Table, and <apiId>|<typeName>|<fieldName> for AWS::AppSync::Resolver. A state.json edited by hand, or a record written by an older cdkd, can have the wrong number of parts.

cdkd made no AWS call for that resource, so the resource may still exist. cdkd keeps the state record, because it holds the only id you could delete the resource with. The warning for each resource names the form cdkd expected.

Inspect the record, then fix it one of two ways:

cdkd state show MyStack
  • Repair the id and re-run.

    aws s3 cp s3://cdkd-state-123456789012/cdkd/MyStack/us-east-1/state.json .
    # edit the record: "physicalId": "mydb|mytable"
    aws s3 cp state.json \
      s3://cdkd-state-123456789012/cdkd/MyStack/us-east-1/state.json
    cdkd destroy MyStack
    
  • Delete the resource by hand and drop the record.

    aws glue delete-table --database-name mydb --name mytable
    cdkd state orphan MyStack   # removes state only, never AWS resources
    

Do not remove the record while the resource is still in AWS. Nothing would track the resource any more.

Known Leftover: EFS Automatic Backups

A successful cdkd destroy of an AWS::EFS::FileSystem deletes the file system, but it does not delete the AWS Backup recovery points that were taken while the file system existed. If automatic backups were on (BackupPolicy: { Status: ENABLED }), the recovery points stay in the vault aws/efs/automatic-backup-vault for the default retention of 35 days. You can still restore from them, and you are billed for them.

List and delete them with aws backup list-recovery-points-by-backup-vault and aws backup delete-recovery-point. "EFS automatic backups survive destroy" in Supported Resources has the full commands.

Known Leftover: FSx Final Backups

A successful cdkd destroy of an AWS::FSx::FileSystem can leave a final backup that you are billed for. cdkd calls DeleteFileSystem with the API's defaults, as CloudFormation does. Those defaults take a final backup for Windows and ONTAP file systems, and this has been observed on OpenZFS too.

The backup is usually untagged, so find it by its FileSystem.FileSystemId with aws fsx describe-backups, and remove it with aws fsx delete-backup. "FSx final backup on destroy" in Supported Resources has the full commands.

  • Troubleshooting: the common problems and the list of every troubleshooting page

Last updated: