Destroy
#
terraform destroy is the command that demands the most care in all of Terraform. It permanently deletes every resource Terraform manages — no undo, no recycle bin. Understanding when destroy is appropriate, how to protect resources that must never be deleted, and the alternatives to a full destroy are essential skills for anyone managing infrastructure in production.
flowchart TD
A["terraform destroy"] --> B["1. Read the current state\nWhat does Terraform manage?"]
B --> C["2. Generate a 'reverse plan'\nAll resources marked for deletion"]
C --> D["3. Show the deletion plan\nAsk for 'yes' confirmation"]
D --> E["4. Delete resources in\nreverse dependency order"]
E --> F["5. State emptied\nAll resources deleted"]
E --> G["aws_instance.web\ndeleted first (most dependent)"]
G --> H["aws_security_group\ndeleted next"]
H --> I["aws_subnet.public\ndeleted next"]
I --> J["aws_vpc.main\ndeleted last (the foundation)"]
style A fill:#ffebee,stroke:#c62828
style F fill:#ffebee,stroke:#c62828
style G fill:#fff3e0,stroke:#e65100
style J fill:#e3f2fd,stroke:#1565c0| Step | What It Does | Output | Important Notes |
|---|
| 1. Read state | Identify all managed resources | Resource list | Only resources in state get deleted |
| 2. Reverse plan | The opposite of apply — everything marked for destroy | Deletion plan | Same as plan -destroy |
| 3. Confirmation | User types yes | — | Skipped with -auto-approve |
| 4. Delete in order | The most dependent resources are deleted first | Resources deleted | Order is automatic from the dependency graph |
| 5. Empty state | The state file is emptied | Empty state | No data left behind |
The deletion order is the reverse of the creation order — the “deepest” resources (those depending on the most other resources) are deleted first, then the resources that form their foundation.
flowchart LR
subgraph "Apply Order (left to right)"
A1["VPC"] --> A2["Subnet"] --> A3["SG"] --> A4["Instance"]
end
subgraph "Destroy Order (right to left)"
D4["Instance"] --> D3["SG"] --> D2["Subnet"] --> D1["VPC"]
end
style A1 fill:#e3f2fd,stroke:#1565c0
style A4 fill:#e8f5e9,stroke:#2e7d32
style D4 fill:#ffebee,stroke:#c62828
style D1 fill:#ffebee,stroke:#c62828Running Destroy
#
flowchart TD
A["Need a destroy?"] --> B{"What's the scope?"}
B -->|"All resources"| C{"Any\nproduction data?"}
C -->|"Yes"| D["❌ DON'T\nUse an alternative"]
C -->|"No"| E["terraform destroy"]
B -->|"Only some\nresources"| F["Remove the resource block\nfrom .tf, then apply"]
B -->|"A specific module"| G["terraform destroy\n-target=module.x"]
B -->|"One resource"| H["terraform destroy\n-target=resource.x"]
style D fill:#ffebee,stroke:#c62828
style E fill:#fff3e0,stroke:#e65100
style F fill:#e8f5e9,stroke:#2e7d32
style G fill:#e3f2fd,stroke:#1565c0
style H fill:#e3f2fd,stroke:#1565c0# Destroy all resources (asks for confirmation)
terraform destroy
# Confirmation output:
# Plan: 0 to add, 0 to change, 5 to destroy.
#
# Do you really want to destroy all resources?
# Terraform will destroy all your managed infrastructure, as shown above.
# There is no undo. Only 'yes' will be accepted to confirm.
#
# Enter a value: yes
# Destroy without confirmation (for CI/CD or automation)
terraform destroy -auto-approve
# An equivalent alternative — more explicit about intent
terraform apply -destroy
# Destroy only specific resources
terraform destroy -target=aws_instance.web
terraform destroy -target=module.staging
| Command | Scope | Confirmation | Use For |
|---|
terraform destroy | All resources | Type yes | Environment teardown |
terraform destroy -auto-approve | All resources | No | CI/CD teardown |
terraform destroy -target=x | Specific resources | Type yes | Partial deletion |
terraform apply -destroy | All resources | Type yes | Same as destroy |
Remove block from .tf + apply | Specific resources | Type yes | Most controlled |
Protecting Resources from Destroy
#
There are several mechanisms to protect resources that must never be accidentally deleted.
flowchart TD
A["Protecting resources\nfrom destroy"] --> B["prevent_destroy = true"]
A --> C["ignore_changes"]
A --> D["terraform state rm"]
A --> E["Deletion protection\nin the provider"]
B --> B1["Terraform ERRORS before\ndeleting the resource\nSafest for DBs"]
C --> C1["Ignore attribute changes\nthat could trigger a replace\nNot a direct destroy guard"]
D --> D1["Detach the resource from\nTerraform without deleting\nit from the cloud"]
E --> E1["Provider features like\nRDS deletion_protection\nAWS-level protection"]
style B1 fill:#e8f5e9,stroke:#2e7d32
style C1 fill:#e3f2fd,stroke:#1565c0
style D1 fill:#fff3e0,stroke:#e65100
style E1 fill:#e8f5e9,stroke:#2e7d32| Mechanism | Level | What It Does | Drawback |
|---|
prevent_destroy = true | Terraform | Errors when a destroy is planned | Must be removed manually if you truly want to destroy |
ignore_changes | Terraform | Ignores attribute changes | Not a direct destroy protection |
terraform state rm | Terraform | Detaches from management | Resource orphaned, managed by no one |
deletion_protection | Provider/Cloud | Prevents deletion at the API level | Limited to specific resources |
Mechanism 1: prevent_destroy
#
# lifecycle prevent_destroy
# Terraform will ERROR if anyone tries to delete this resource
resource "aws_rds_instance" "production_db" {
identifier = "production-database"
engine = "postgres"
instance_class = "db.t3.medium"
allocated_storage = 100
lifecycle {
prevent_destroy = true
# Error on destroy:
# Error: Instance cannot be destroyed
# Resource aws_rds_instance.production_db has lifecycle.prevent_destroy
# set to true.
}
}
Mechanism 2: ignore_changes
#
# ignore_changes for attributes managed outside Terraform
resource "aws_instance" "web" {
ami = var.ami_id
instance_type = var.instance_type
lifecycle {
ignore_changes = [
# Ignore changes to these attributes — they won't trigger destroy/recreate
ami,
user_data,
]
}
}
Mechanism 3: Unmanage the Resource
#
# Remove the resource from state without deleting it from the cloud
# The resource stays in AWS, but Terraform no longer manages it
terraform state rm aws_instance.web
# Now terraform destroy won't touch this resource
Mechanism 4: Provider-Level Protection
#
# Deletion protection at the provider level (not Terraform)
resource "aws_rds_instance" "db" {
identifier = "production-db"
engine = "postgres"
instance_class = "db.t3.medium"
deletion_protection = true # AWS-level: can't be deleted from Console/API
}
# S3 bucket: versioning + MFA delete
resource "aws_s3_bucket" "critical" {
bucket = "critical-data-bucket"
# Versioning ensures old data stays stored
# even if objects are deleted
}
When Destroy Is Appropriate
#
flowchart TD
A["Situation"] --> B{"Environment type?"}
B -->|"Development /\nfeature branch"| C["✅ Destroy is safe\nTemporary resources"]
B -->|"Staging /\nTesting"| D["✅ Destroy is safe\nCan be recreated"]
B -->|"Production"| E["❌ AVOID destroy\nUse an alternative"]
C --> C1["terraform destroy\nor -auto-approve in CI/CD"]
D --> D1["terraform destroy\nwith confirmation"]
E --> E1["Remove blocks from .tf\nthen apply\nOr state rm"]| Scenario | Use Destroy? | Alternative |
|---|
| Temporary environment (dev, feature branch) | ✅ Yes | — |
| Demo or experiment resources | ✅ Yes | — |
| Tearing down an unneeded environment | ✅ Yes | — |
| Migrating to a fresh configuration from scratch | ✅ Yes (careful) | Back up state first |
| Deleting some resources | ❌ No | Remove blocks from .tf, then apply |
| Production database without a backup | ❌ NEVER | Back up first, then consider |
| Unsure what will be deleted | ❌ NEVER | Run plan -destroy first |
Never run terraform destroy on production without a verified backup. Always run terraform plan -destroy and read its output carefully before confirming.
Alternatives to a Full Destroy
#
A full destroy is often not the only option. There are safer, more controlled approaches.
flowchart TD
A["Want to delete\nresources?"] --> B{"Which resources\ndo you want to delete?"}
B -->|"Only a few\nresources"| C["Remove the resource block\nfrom .tf, then apply"]
B -->|"One module"| D["terraform destroy\n-target=module.x"]
B -->|"Detach from\nTerraform"| E["terraform state rm\nThe resource stays in the cloud"]
B -->|"All resources"| F{"Any production\ndata?"}
F -->|"Yes"| G["❌ Don't destroy\nUse another alternative"]
F -->|"No"| H["terraform destroy"]
style C fill:#e8f5e9,stroke:#2e7d32
style D fill:#e3f2fd,stroke:#1565c0
style E fill:#fff3e0,stroke:#e65100
style G fill:#ffebee,stroke:#c62828
style H fill:#ffebee,stroke:#c62828Alternative 1: Remove the Resource Block from the Configuration
#
# Before:
resource "aws_instance" "staging" {
ami = var.ami_id
instance_type = "t3.micro"
}
# After (remove this block from .tf):
# (nothing left)
# Then run: terraform apply
# Terraform detects the resource is no longer in the configuration
# and deletes it from the cloud — only this resource, not the others
Alternative 2: Targeted Destroy
#
# Destroy only a specific module or resource
terraform destroy -target=module.staging
terraform destroy -target=aws_instance.old_server
Alternative 3: Unmanage the Resource
#
# Detach the resource from Terraform without deleting it from the cloud
terraform state rm aws_s3_bucket.old_bucket
# The resource stays in AWS, Terraform no longer manages it
# Useful for transitioning ownership to another team or tool
| Alternative | Command | Other Resources | Data Safe? | Best For |
|---|
| Remove block + apply | Edit .tf + apply | Unaffected | ✅ Yes | Deleting specific resources |
| Targeted destroy | destroy -target=x | Unaffected | ✅ Yes | Deleting a specific module/resource |
| Unmanage | state rm | Unaffected | ✅ Yes | Ownership transitions |
| Full destroy | destroy | Everything deleted | ❌ No | Environment teardown |
Destroy in CI/CD Pipelines
#
For environments destroyed automatically (for example, ephemeral environments per PR), there’s a safe pattern.
flowchart TD
A["PR merged /\nEnvironment done"] --> B["1. Generate a destroy plan\nterraform plan -destroy\n-out=destroy-plan"]
B --> C["2. Review the plan\nManual or automated check"]
C --> D["3. Approval received"]
D --> E["4. terraform apply destroy-plan\nExecute the destroy from the saved plan"]
E --> F["5. Delete the destroy-plan file"]
F --> G["✅ Environment cleaned up\nAudit trail saved"]
style A fill:#e3f2fd,stroke:#1565c0
style E fill:#ffebee,stroke:#c62828
style G fill:#e8f5e9,stroke:#2e7d32# The safe destroy pattern in CI/CD:
# 1. Generate a destroy plan first
terraform plan -destroy -out=destroy-plan
# 2. Review the plan (manual or automated check)
terraform show destroy-plan
# 3. Apply the destroy plan once verified
terraform apply destroy-plan
# Why not just -auto-approve?
# A saved destroy plan provides an audit trail
# and prevents surprises from conditions changing
# between initiating the destroy and executing it.
| CI/CD Pattern | Command | Audit Trail | Risk |
|---|
| Direct destroy | destroy -auto-approve | ❌ None | High |
| Saved destroy plan | plan -destroy -out=x + apply x | ✅ Yes | Low |
Summary
#
- Destroy can’t be undone — always run
terraform plan -destroy and read its output before confirming. - Deletion order is automatic — Terraform deletes resources in reverse dependency order, no need to specify it manually.
prevent_destroy = true for critical resources like production databases — Terraform will error before it can delete them.- Provider-level protection (
deletion_protection, versioning, MFA delete) provides additional protection beyond Terraform. - Remove the resource block from .tf then apply to delete only specific resources — more controlled than a full destroy.
terraform state rm to unmanage a resource without deleting it from the cloud — useful for ownership transitions.- In CI/CD, use a saved destroy plan (
plan -destroy -out=x) so there’s a clear audit trail of what will be deleted. - Never destroy production without a verified backup — there’s always a safer alternative.
← Previous: Apply
Next: Execution Plan →