How to set a Deletion Policy on a Resource in AWS CDK

avatar

Borislav Hadzhiev

Sat Apr 24 20214 min read

banner

Photo by Hakan Nural

Updated on Sat Apr 24 2021

The Deletion Policy from CloudFormation is called Removal Policy in AWS CDK and can be applied to stateful resources to prevent accidental deletion.

Deletion Policy and Removal Policy #

A deletion policy in CloudFormation enables us to specify what should happen to stateful resources (databases, S3 buckets) when a stack gets deleted.

The specified deletion policy also applies in case we delete the resource from our CloudFormation / CDK code.

The Deletion Policy from CloudFormation is called Removal Policy in CDK.

The default behavior for CloudFormation, is that if we don't specify aDeletion Policy and delete the stack - the resources are deleted.
However, the default behavior in CDK is - if we don't specify a Removal Policy and delete the stack the resources are retained, but orphaned from the stack.

Setting a Removal Policy in CDK #

In order to set a removalPolicy in cdk we have to pass the removalPolicy prop to the construct of the stateful resource, for example an S3 bucket or Dynamodb table.

The code for this article is available on GitHub

I'll cdk deploy a small application, consisting of an S3 bucket and a Dynamodb table in order to demo the behavior:

lib/cdk-starter-stack.ts
import * as dynamodb from '@aws-cdk/aws-dynamodb';
import * as s3 from '@aws-cdk/aws-s3';
import * as cdk from '@aws-cdk/core';

export class MyCdkStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props: cdk.StackProps) {
    super(scope, id, props);

    const s3Bucket = new s3.Bucket(this, id, {
      // ๐Ÿ‘‡ set a removal policy of DESTROY
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });

    const table = new dynamodb.Table(this, 'my-table', {
      partitionKey: {name: 'todoId', type: dynamodb.AttributeType.NUMBER},
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      // ๐Ÿ‘‡ set a removal policy of RETAIN
      removalPolicy: cdk.RemovalPolicy.RETAIN,
    });
  }
}

In the code snippet:

  1. I've defined an S3 bucket and set the removalPolicy to DESTROY. Note that only empty S3 buckets get deleted with a removal policy set to DESTROY. If we want to also delete a bucket that contains objects we have to also set the autoDeleteObjects property to true:
lib/cdk-starter-stack.ts
const s3Bucket = new s3.Bucket(this, id, {
  removalPolicy: cdk.RemovalPolicy.DESTROY,
  autoDeleteObjects: true,
});
  1. I've defined a dynamodb table and set its removalPolicy prop to RETAIN. This causes the resources to be retained in the account but orphaned from the stack.
There is a third option for a removal policy - RemovalPolicy.SNAPSHOT. This option deletes the stateful resource, but saves a snapshot of the state right before deletion, so the resource can be re-created later. The SNAPSHOT policy is only available for some stateful resources like relational databases and EFS volumes.

At this point I've deployed a CloudFormation template, consisting of an S3 bucket with a RemovalPolicy.DESTROY and a Dynamodb table with a RemovalPolicy.RETAIN:

cloudformation stack

Let's test what happens if I comment out the lines that provision the Dynamodb table in my CDK code:

lib/cdk-starter-stack.ts
import * as dynamodb from '@aws-cdk/aws-dynamodb';
import * as s3 from '@aws-cdk/aws-s3';
import * as cdk from '@aws-cdk/core';

export class MyCdkStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props: cdk.StackProps) {
    super(scope, id, props);

    // ...rest

    // ๐Ÿ‘‡ I've commented our the table with policy RETAIN

    // const table = new dynamodb.Table(this, 'my-table', {
    //   partitionKey: {name: 'todoId', type: dynamodb.AttributeType.NUMBER},
    //   billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
    //   removalPolicy: cdk.RemovalPolicy.RETAIN,
    // });
  }
}

If I now running the diff command:

shell
npx cdk diff

I can see that if I execute a deploy at this point, the Dynamodb table will become orphaned:

orphaned table

The orphan status means that the table will remain in the account, but it will be detached from the stack.

Next, we'll test what happens if we execute an update that requires resource replacement.

I'll uncomment the code that provisions the Dynamodb table and I'll change the name of the partition key:

lib/cdk-starter-stack.ts
const table = new dynamodb.Table(this, 'my-table', {
-  partitionKey: {name: 'todoId', type: dynamodb.AttributeType.NUMBER},
+  partitionKey: {name: 'another-one', type: dynamodb.AttributeType.NUMBER},
  billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
  removalPolicy: cdk.RemovalPolicy.RETAIN,
});

Let's run the diff command again:

shell
npx cdk diff

The output looks like:

replace resource

We can see that if we deploy our stack at the current state, the Dynamodb table will be replaced.

Since we've set the removal policy of the table to RETAIN, let's test if the old table is retained or deleted after an update that requires replacement.

I'll run the deploy command:

shell
npx cdk deploy

If I open the Dynamodb console, we can see that both my-cdk-stack-* tables remain in the account:

tables after update

However, the table with Partition key of todoId is orphaned and detached from the CloudFormation stack.

Discussion #

If we don't set a removal policy - the default behavior in CDK is to retain, but detach the resources from the CloudFormation stack.

This differs from the default behavior for the deletion policy in CloudFormation, which is to delete all resources that don't specify a deletion policy.

A very common footgun in CDK, is that changing the id parameter of a construct or refactoring it to a different scope changes the resource's CloudFormation logical ID. This causes the old resource to be deleted and a new resource with the new logical ID to be created, in other words this causes resource replacement.

If you're interested to read more about Identifiers in CDK I've written another article on the topic - What is an identifier in AWS CDK

To be on the safe side of things, it's always a best practice to run the cdk diff command before deploying, especially when updating stateful resources like databases:

shell
npx cdk diff

Further Reading #

Join my newsletter

I'll send you 1 email a week with links to all of the articles I've written that week

Buy Me A Coffee