CloudFormation Stack Policies are the silent guardians of your infrastructure, preventing accidental, destructive updates to critical resources.

Imagine you have a production database instance that absolutely cannot be interrupted. You’ve painstakingly configured it, and a stray cfn-update or a misclicked button could bring it down. Stack Policies are your safety net. They declare which resources are protected and under what conditions they can be modified.

Here’s a simple Stack Policy JSON:

{
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "Update:*",
      "Principal": "*",
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "cloudformation:ResourceTag/Environment": "Development"
        }
      }
    },
    {
      "Effect": "Deny",
      "Action": "Update:*",
      "Principal": "*",
      "Resource": "*",
      "Condition": {
        "StringNotEquals": {
          "cloudformation:ResourceTag/Environment": "Development"
        }
      }
    }
  ]
}

This policy says: "Allow any update action (Update:*) to any resource (Resource: "*") if the resource has a tag Environment with the value Development. Deny all other update actions to all other resources."

This policy is applied at the stack level. When you create or update a CloudFormation stack, you can specify a Stack Policy. If you don’t, CloudFormation uses a default policy that allows all updates.

Let’s say you have a CloudFormation template like this:

Resources:
  MyProductionDB:
    Type: AWS::RDS::DBInstance
    Properties:
      DBInstanceIdentifier: prod-db-001
      DBInstanceClass: db.r5.large
      AllocatedStorage: 100
      Tags:
        - Key: Environment
          Value: Production
        - Key: Criticality
          Value: High

  MyDevWebServer:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-0abcdef1234567890
      InstanceType: t3.micro
      Tags:
        - Key: Environment
          Value: Development

If you apply the Stack Policy above to the stack containing these resources, and then attempt to update MyProductionDB (e.g., change its instance class), CloudFormation will deny the update because the Environment tag is Production, not Development. However, updating MyDevWebServer would be allowed.

The power comes from the Condition block and its cloudformation:ResourceTag operator. You can use any tag to control access. Common strategies include tagging resources by environment (Development, Staging, Production), criticality (Low, Medium, High), or by team ownership.

Here’s another example, protecting a specific RDS instance:

{
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "Update:*",
      "Principal": "*",
      "Resource": "LogicalResourceId/MyProductionDB",
      "Condition": {
        "StringEquals": {
          "cloudformation:UpdateAction": "Update"
        }
      }
    },
    {
      "Effect": "Deny",
      "Action": "Update:*",
      "Principal": "*",
      "Resource": "LogicalResourceId/MyProductionDB",
      "Condition": {
        "StringNotEquals": {
          "cloudformation:UpdateAction": "Update"
        }
      }
    }
  ]
}

This policy is more granular. It targets a specific resource by its LogicalResourceId (MyProductionDB). It allows only Update actions (e.g., changing properties) but denies other actions like Delete or Replace for that specific resource. This is useful when you want to allow minor configuration changes but prevent complete replacement of a resource.

You can also specify resource types:

{
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "Update:Replace",
      "Principal": "*",
      "Resource": "ResourceType/AWS::RDS::DBInstance",
      "Condition": {
        "StringEquals": {
          "cloudformation:ResourceTag/Environment": "Development"
        }
      }
    },
    {
      "Effect": "Deny",
      "Action": "Update:Replace",
      "Principal": "*",
      "Resource": "ResourceType/AWS::RDS::DBInstance",
      "Condition": {
        "StringNotEquals": {
          "cloudformation:ResourceTag/Environment": "Development"
        }
      }
    }
  ]
}

This policy specifically protects AWS::RDS::DBInstance resources from being replaced if they are not tagged with Environment: Development. Replacing a resource often means CloudFormation deletes the old one and creates a new one, which can cause downtime.

The most surprising thing is how often people don’t use Stack Policies for critical resources, leading to avoidable outages. The cloudformation:ResourceTag condition is incredibly powerful, and you can combine multiple tags for very fine-grained control. For instance, you could allow updates only if a resource has both Environment: Production and ApprovalRequired: False.

To apply a Stack Policy, you use the --stack-policy-body or --stack-policy-url options with the aws cloudformation create-stack or aws cloudformation update-stack commands.

aws cloudformation create-stack \
  --stack-name my-production-stack \
  --template-body file://my-template.yaml \
  --stack-policy-body file://my-stack-policy.json

When you need to temporarily bypass a Stack Policy to perform a critical update, you can use the --force-rollback option with aws cloudformation update-stack. However, this is generally discouraged for routine operations.

The next thing you’ll likely encounter is managing drift detection for resources protected by stack policies, as drift can occur independently of CloudFormation updates.

Want structured learning?

Take the full Cloudformation course →