This is yet-another installment in series notes-to-self, inspired by theburningmonk.com and conversation in LinkedIn

By default Cloudformation will delete a resource when the stack is deleted or resource is removed from template. Resources can also be replaced, that is deleting existing resource after new is created during stack update. When you have persistent data stored into database or disk, you would want to retain rather then delete. Resource retention can be controlled with DeletionPolicy and UpdateReplacePolicy attributes.

What is DeletionPolicy?

DeletionPolicy is an optional attribute you can use to preserve a resource when it would otherwise be deleted due to stack deletion or update where resource is removed from the template. For EBS volumes, databases and Elastic caches, you can also take a final snapshot before resource deletion.

Use-cases for DeletionPolicy

The Main use-case for DeletionPolicy attribute is to protect stateful resources being deleted during stack update or deletion.

Secondary use-case is, if you are not planning to maintain resources via Cloudformation stacks, setting DeletionPolicy when creating stack and resources, with pre-defined standard architecture, and then delete the stack but retain resources it created.

Retail or RetainExceptOnCreate?

DeletionPolicy can have 4 different values, depending what you want to happen when resource is about to be deleted.

  • Delete is the default when you don’t specify ´DeletionPolicy` and resource will be deleted.
  • Retain will skip the deletion and resource will remain as-is, except it isn’t part of cloudformation stack any longer.
  • Snapshot will take final snapshot before deleting the resource. This option is available only for EBS volumes, Elastic caches and certain databases.
  • RetainExceptOnCreate is the latest addition that works the same way as Retain but won’t retain resource if initial stack creation or resource creation at stack update ends with rollback.

So, should you use Retain or RetainExceptOnCreate? I really don’t see any reasons why Retain would be better than RetainExceptOnCreate. Original Retain is preserved for backwards compatibility but RetainExceptOnCreate does cleanup after a failed stack creation or update automatically. Even when you are using stacks just to create resources, but not maintaining them through updates, this retains resources because stack deletion is separete operation after successful creation.

UpdateReplacePolicy

If you read above carefully, you noted I said “deleted”. But resources can also be replaced, i.e. creating a replacement and then deleting the original. During stack updates it can be difficult to see beforehand what changes will trigger resource replacement. Creating a change set will reveal this, so you can avoid doing it accidentially, but sometimes replacements just can not be avoided and then UpdateReplacePolicy is the way to retain your data.

UpdateReplacePolicy has the same options as DeletionPolicy, except you can not use RetainExceptOnCreate as it doesn’t make sense for resource updates.

How to apply DeletionPolicy (and UpdateReplacePolicy)?

The Most simple way, when you need just an on/off -switch, is using If with Condition. Ability to include functions in policies was introduced in AWS::LanguageExtensions transform, but it is now part of standard Cloudformation and transform is no longer required.

# Oct12th 2023, AWS::LanguageExtensions is no longer needed for
# intrinsic functions in DeletionPolicy or UpdateReplacePolicy.
# Transform: AWS::LanguageExtensions

Parameters:
  Environment:
    Type: String
    Default: dev
    AllowedValues:
      - dev
      - test
      - prod
    Description: Environment type

Conditions:
  IsProd: !Equals [ !Ref Environment, "prod" ]

Resources:
  MyDB:
    Type: AWS::RDS::DBInstance
    DeletionPolicy: !If [ "IsProd", "RetainExceptOnCreate", "Delete" ]
    UpdateReplacePolicy: !If [ "IsProd", "Retain", "Delete" ]
    Properties:
      ...

If you need more than on/off -switch, e.g. there are more than one environment where you would want to retain resources, it might be easier to create a map than maintain complex conditions clauses.

# Oct12th 2023, AWS::LanguageExtensions is required for FindInMap default value feature.
Transform: AWS::LanguageExtensions

Parameters:
  Environment:
    Type: String
    Default: dev
    Description: Environment type, e.g. dev, test, stage, prod

Mappings: 
  Policies: 
    stage: 
      Delete: RetainExceptOnCreate
      Update: Retain
    prod: 
      Delete: RetainExceptOnCreate
      Update: Retain

Resources:
  MyDB:
    Type: AWS::RDS::DBInstance
    DeletionPolicy: !FindInMap [ Policies, Environment, Delete, DefaultValue: Delete ]
    UpdateReplacePolicy: !FindInMap [ Policies, Environment, Update, DefaultValue: Delete ]
    Properties:
      ...

Above would allow you to use freeform environment names and retain resources when environment is stage or prod. In all other environments, resources will be deleted. Note that DefaultValue feature in FindInMap is also part of AWS::LanguageExtensions.

How to apply DeletionPolicy for all resources in template?

Problem with above solution, is you need to add policy attribute(s) for every resource in template individually. It would be nice if there was a switch in Cloudformation API you could turn on and set Retain(ExceptOnCreate) policy for every resource. Unfortunately there isn’t such a thing, but you could get close to this by building a custom transformation.

I will leave the implementation of such transform for reader as homework, but if you do this, please drop me a note where can I find your implementation ;-)