Sign in
Menu
Sign up
Docs
Operational rulesRule catalog

Prevent destroy data

For platform engineers deciding whether to apply lifecycle_prevent_destroy_data: what it changes, how to enable it, and how to check it worked.

Adds prevent_destroy = true to data-bearing resources. Blocks accidental deletion.

What this rule does

Adds a lifecycle { prevent_destroy = true } block to each matching resource. Terraform and OpenTofu will refuse to destroy these resources during terraform destroy or resource replacement.

Affected resources

ResourceServiceWhy
aws_s3_bucketAmazon S3Object storage for logs, backups, application data
aws_db_instanceAmazon RDSRelational databases with primary application data
aws_rds_clusterAmazon AuroraAurora clusters holding primary application data
aws_dynamodb_tableAmazon DynamoDBNoSQL tables for application state and session data
aws_efs_file_systemAmazon EFSShared file storage mounted by compute workloads
aws_elasticache_replication_groupAmazon ElastiCacheReplication groups whose data a workload treats as durable

Before and after

Before (a module without the change):

resource "aws_s3_bucket" "this" {
  # ... resource configuration ...

  tags = var.tags
}

After (with Prevent destroy data applied):

resource "aws_s3_bucket" "this" {
  # ... resource configuration ...

  tags = var.tags

  lifecycle {
    prevent_destroy = true
  }
}

The only change is the rule transformation. All existing arguments, outputs, and module behavior remain identical.

The example above is illustrative. To see this rule run for real, open it in the Rules Playground: it applies lifecycle_prevent_destroy_data to a reviewed upstream module that declares the resources this rule targets, and shows the diff the registry would serve. No account needed.

Why this rule exists

Accidentally destroying an S3 bucket, RDS instance, DynamoDB table, or EFS file system can cause data loss that is difficult or impossible to recover from. A single terraform destroy or a careless resource rename can trigger deletion of production data.

Terraform's prevent_destroy lifecycle argument is the built-in safeguard. But it cannot be passed as a variable into a module. Every team using upstream terraform-aws-modules must either fork the module to add it, or rely on review checklists.

RepositoryIssueTitle
hashicorp/terraform#3116Cannot use interpolations in lifecycle attributes
hashicorp/terraform#18367Feature request: support prevent_destroy for modules
hashicorp/terraform#27360A method to override configuration and meta arguments within a module

Limitations this rule answers

Per-module pages under Workarounds where this rule is the answer, each with the native workaround first and the diff the rule makes:

Default configuration

These are the values the rule ships with. Parameters never travel in a module source URL, so a rule named there runs with exactly these defaults. Your organization sets them per rule, in its Baseline or in a ruleset.

ParameterTypeDefaultDescription
resource_typeslist(string)["aws_s3_bucket", "aws_db_instance", "aws_rds_cluster", "aws_dynamodb_table", "aws_efs_file_system", "aws_elasticache_replication_group"]Data-storage resource types to protect

The HCL this rule is written in, with its parameters and the transformers it runs, is published at lifecycle_prevent_destroy_data.

How to enable

On a framework host, add the rule on top of your organization's Baseline with ?add_rules= in the HTTPS module source:

module "s3_bucket" {
  source = "https://soc2.compliance.tf/terraform-aws-modules/s3-bucket/aws?version=x.y.z&add_rules=lifecycle_prevent_destroy_data"
}

On your organization's host, an org admin adds the rule to the Baseline, which reaches every module served from that host, or to a named ruleset that a module selects with ?ruleset=:

module "s3_bucket" {
  source = "https://bobthecorp.compliance.tf/terraform-aws-modules/s3-bucket/aws?version=x.y.z&ruleset=<ruleset-name>"
}

Replace x.y.z with the release you use and bobthecorp with your organization's alias. Which parameters each host accepts, and which it refuses with a 400, is on Rulesets in the console.

How to check it worked

Read compliancetf-manifest.json in the module you received. Its entry for this rule carries an outcome: enforced means the change was made, not_applicable means nothing matched - no resource of a targeted type, or the module already carried the identical change. Do not infer enforcement from a successful terraform init; a rule that changes nothing also inits cleanly.

Known limits

  • Does not cover aws_redshift_cluster, aws_elasticache_cluster, aws_neptune_cluster, or any other stateful resource not listed in affected resources. aws_rds_cluster and aws_elasticache_replication_group are in the defaults and are covered.
  • Does not prevent deletion of S3 objects inside the bucket, only the bucket resource itself.
  • For prevent_destroy, add means ensure this value, not add it if absent. A resource that explicitly declares prevent_destroy = false is rewritten to true; older engine releases left it unprotected while the run still reported the rule as applied. A value that is not a true/false literal is left alone and warned about instead of being rewritten.
  • Only .tf and .tofu files are read. Modules written in JSON syntax (.tf.json, .tofu.json) are left untouched.
  • Only the module's own directory and its first-level modules/* submodules are processed. Modules that this module calls by source URL are separate downloads and are handled on their own request.
  • Works with Terraform 1.x and OpenTofu 1.6 and later. The rule reads ordinary HCL, and anything it changes stays ordinary HCL.

Compliance framework support

This rule is not a compliance control. It supports these framework objectives as an operational safeguard:

FrameworkControlsRole
NIST 800-53SI-12Supports retaining information by blocking a destroy of the resource that holds it

Break-glass path

On a framework host, add ?rules=-lifecycle_prevent_destroy_data to the module source URL and run terraform init; the source changed, so Terraform installs a fresh copy without this rule. When you are done, remove the override and run terraform init again to restore it. On your organization's host the parameter is refused with a 400 rather than ignored. The rule comes from the organization's configuration: a Baseline rule applies to every module served from the bare organization form, so an admin disables it for the duration of the operation, or keeps it out of the Baseline and in a ruleset that only the modules needing protection select. On a project environment the posture is the bound snapshot and its waivers. See Baseline rules and rulesets.

Failure modes

ScenarioResult
Rule targets a module with no matching resource typesNo-op. Module is delivered unchanged. No error.
Developer runs terraform destroy on a protected resourceTerraform exits with error: "Instance cannot be destroyed because of prevent_destroy."
Resource rename causes replacement planTerraform will error on destroy of the old resource. Use terraform state mv to rename without destroy.

On this page

Ask AI about this

Help improve this page