Quick Answer
# Temporarily set to false
lifecycle {
prevent_destroy = false # Was true
}
terraform apply # Applies the lifecycle change
terraform destroy -target=aws_db_instance.main
Then re-enable prevent_destroy = true.
The Error
Error: Instance cannot be destroyed
on rds.tf line 5:
5: resource "aws_db_instance" "main" {
Resource aws_db_instance.main has lifecycle.prevent_destroy set, but the
plan calls for this resource to be destroyed. To avoid this error and
continue with the plan, either disable lifecycle.prevent_destroy or
reduce the scope of the change.
What Causes This
prevent_destroy = true in the lifecycle block intentionally blocks any operation that would destroy the resource. This triggers when:
- Running
terraform destroy— directly deleting protected resources - Changing a force-replacement attribute — e.g., changing RDS
engineforces replacement (destroy + create) - Removing the resource from config — deleting the resource block from
.tffiles - Renaming the resource —
aws_instance.web→aws_instance.appwithoutmovedblock
Solution 1: Temporarily Disable
resource "aws_db_instance" "main" {
# ...
lifecycle {
prevent_destroy = false # Temporarily disabled
}
}
terraform apply # Apply the lifecycle change
terraform destroy -target=aws_db_instance.main # Now works
Remember to re-enable prevent_destroy = true for other environments!
Solution 2: Remove from State (Keep the Resource)
If you want to stop managing the resource without destroying it:
# Removes from Terraform state — the actual resource stays in AWS
terraform state rm aws_db_instance.main
Then remove the resource block from your .tf files. The database continues running, just no longer managed by Terraform.
Solution 3: Use moved Block for Renames
# This causes destroy + create (blocked by prevent_destroy)
# resource "aws_instance" "app" { ... } # renamed from "web"
# Instead, use moved block (no destroy):
moved {
from = aws_instance.web
to = aws_instance.app
}
resource "aws_instance" "app" {
# ...
lifecycle {
prevent_destroy = true
}
}
Solution 4: Reduce Scope of Change
If a force-replacement attribute triggers the error:
# ❌ This forces replacement (destroy + create) → blocked
resource "aws_db_instance" "main" {
engine = "mysql" # Was "postgres" — can't change in-place
lifecycle { prevent_destroy = true }
}
# ✅ Create new instance with different name, migrate data, then remove old
resource "aws_db_instance" "main_v2" {
engine = "mysql"
}
When to Use prevent_destroy
Use it on resources where accidental deletion is catastrophic:
# Databases — data loss
resource "aws_db_instance" "production" {
lifecycle { prevent_destroy = true }
}
resource "aws_rds_cluster" "production" {
lifecycle { prevent_destroy = true }
}
# S3 buckets with important data
resource "aws_s3_bucket" "backups" {
lifecycle { prevent_destroy = true }
}
# DNS zones
resource "aws_route53_zone" "primary" {
lifecycle { prevent_destroy = true }
}
# KMS keys (can't be recreated with same ID)
resource "aws_kms_key" "main" {
lifecycle { prevent_destroy = true }
}
Don’t use it on:
- EC2 instances (replaceable)
- Security groups (no data loss)
- IAM roles (recreatable)
Combining with AWS Protection
resource "aws_db_instance" "main" {
deletion_protection = true # AWS-level protection
lifecycle {
prevent_destroy = true # Terraform-level protection
}
}
Double protection: Terraform won’t plan the destroy, AND AWS won’t execute it even if Terraform tried.
Hands-On Courses
- Terraform for Beginners on CopyPasteLearn
- Terraform By Example — practical code examples
Conclusion
prevent_destroy = true is working as intended — it’s protecting your resource. Temporarily set it to false to destroy, use terraform state rm to decouple without destroying, or use moved blocks for renames. Apply it to databases, S3 buckets, and DNS zones — anything where data loss would be catastrophic.