TerraformV · The Terraform WorkflowDestroy
terraform destroy: The Most Dangerous Operation
What you'll learn
- Recognise destruction as a production-dangerous operation
- Apply the same plan-review workflow to destroy as to apply
- Use lifecycle.prevent_destroy to protect critical resources
- Distinguish targeted destroy from full destroy
Prerequisites
Verified against Terraform CLI 1.9.x · OpenTofu 1.7.x · HCL 2.0 · bpg/proxmox provider 0.66+ · hashicorp/local provider 2.5+ · hashicorp/null provider 3.2+ · hashicorp/random provider 3.6+ · hashicorp/http provider 3.4+ · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · 2026-08-12
terraform destroy is the most dangerous operation in Terraform.
It is the only command that exists for the sole purpose of
irreversibly removing infrastructure. The course treats it with
the same seriousness as rm -rf / on a production server.
What destroy does
The destroy command:
- Reads the configuration and state.
- Computes the destroy plan (essentially, the inverse of the apply plan).
- Prompts for approval (unless
-auto-approveis set). - Destroys resources in reverse dependency order.
- Removes the resource entries from state.
- Writes the empty state to the backend.
The default destroy plan is “destroy everything in the state file”. A targeted destroy can be limited to a subset of resources, but the default is the entire estate.
Why destroy is dangerous
A few reasons the default is alarming:
Confirmation is not a safety net. The approval prompt is a
single yes. There is no second confirmation. There is no
typed-out-of-the-name-of-the-resource-to-be-destroyed step. There
is no “destroy only if the change window is open” check.
The destroy plan is read at destroy time. Like apply, the plan is computed at destroy time. If the state has changed since the last review, the destroy plan may include resources the engineer did not intend to destroy.
The destroy is irreversible. A successful destroy removes real infrastructure. Recreating it requires the same configuration and the same state (or rebuilt state). If the state was destroyed alongside the resources, there is no recovery.
Data is destroyed before the engineer can react. By the time
the destroy output is on screen, the underlying real-world
resources are gone. The --target flag does not help here: a
targeted destroy on a database is still destructive.
The lifecycle.prevent_destroy guard
The prevent_destroy lifecycle setting is a guardrail that
prevents a class of accidental destruction:
resource "aws_db_instance" "primary" {
# ...
lifecycle {
prevent_destroy = true
}
}
When prevent_destroy = true is set:
- A plan that does not affect the resource succeeds normally.
- A plan that would destroy the resource fails with an error:
Error: Instance cannot be destroyed. - The destroy error is unconditional — even
-auto-approvedoes not override it.
prevent_destroy is a circuit breaker, not a security
control. It prevents the configuration from proposing to destroy
the resource. It does not prevent:
- Manual destruction of the resource outside Terraform.
- State manipulation that removes the resource from state (which would then allow the next plan to recreate it).
- Resource replacement (the resource is destroyed and recreated,
which
prevent_destroydoes NOT prevent).
For production-critical resources, prevent_destroy is a
mandatory setting. The course has a dedicated lab in Part LXX
that reviews protection patterns for stateful resources.
Targeted destroy
A targeted destroy limits the operation to a subset of resources:
terraform destroy -target=aws_instance.web
The plan shows only the targeted resources. The apply destroys only the targeted resources. The untargeted resources are untouched.
Targeted destroy is appropriate for:
- Removing a single resource that is no longer needed.
- Recovering from a failed apply by re-destroying a partially created resource.
- Removing a misconfigured resource as part of a more complex change.
Targeted destroy is not appropriate as a workflow tool:
- A targeted destroy leaves the other resources in state. The next plan may propose unexpected changes to the omitted resources.
- A targeted destroy does not reduce the need for plan review.
- A targeted destroy does not change the irreversibility of the operation.
The course returns to targeting in Part LXXIV.
Approved-destroy workflows
A production destroy should follow the same review workflow as an apply:
- Plan the destroy.
terraform plan -destroyproduces the destroy plan. Review every resource it proposes to destroy. - Save the plan.
terraform plan -destroy -out=destroy.tfplan. - Have a second engineer review. The plan is the unit of review.
- Back up the state. The state may be needed for recovery.
cp terraform.tfstate terraform.tfstate.pre-destroy. - Verify the targets. Confirm that the resources being destroyed are the ones intended.
- Apply the saved plan.
terraform apply destroy.tfplan. - Verify the destroy. After the destroy, confirm the resources are gone in the providers console.
- Document the destroy. The destroy is a change. It should be archived in the change log.
Most production destroy workflows require two engineers to approve: the engineer who is destroying and an engineer who has verified the targets.
When destroy is reversible
A few cases where destroy is reversible:
- The state is preserved. A destroy that succeeds leaves the state empty. The previous state, if backed up, can be restored. The configuration can be re-applied to recreate the resources. The recreate may produce a slightly different real-world state (new IPs, new ARNs, new resource IDs), but the topology is recovered.
- The destroy is logical, not physical. Some resources (e.g. most AWS APIs) allow a “soft delete” that can be recovered within a window. Terraform destroy is typically a hard delete; check the providers documentation.
- The destroy is part of a recreate.
terraform destroyis often used to reset a development environment. The plan includes creating the resources again from the configuration.
The course has a dedicated lab for the “destroy + recreate” workflow in Part LXXV.
The destroy-time refresh
By default, the destroy plan does not refresh state. The plan reads the state as it was at the last apply. If the real world has changed since the apply, the destroy plan may:
- Propose to destroy resources that no longer exist (an error).
- Propose to destroy resources that have been modified since the apply (which is fine, but the destroy may behave differently than expected).
- Miss real-world changes that should have been considered.
The course recommends running terraform plan (a non-destroy
plan) immediately before the destroy to verify the state is
fresh:
terraform plan -refresh-only # refresh state without changing anything
terraform plan -destroy -out=destroy.tfplan
The refresh-only plan is read-only. It updates state to reflect the current real world. The next plan (the destroy) is computed against the refreshed state.
A destroy plan review
A typical destroy plan:
aws_security_group.alb will be destroyed
- resource "aws_security_group" "alb" {
- id = "sg-0abc123def456789" -> null
- name = "alb-sg" -> null
- description = "ALB SG" -> null
- (4 other attributes hidden)
}
aws_lb_target_group.api will be destroyed
- resource "aws_lb_target_group" "api" {
- id = "arn:aws:elasticloadbalancing:..." -> null
- (10 other attributes hidden)
}
Plan: 0 to add, 0 to change, 2 to destroy.
The review asks:
- Are these the resources intended to be destroyed?
- Are there any resources that the plan expected to destroy that are not listed?
- Are there any resources that the plan did not expect to destroy that are listed?
- What is the recovery procedure if the destroy is wrong?
A destroy plan that names “0 to destroy” should never be applied — it is a no-op. A destroy plan that names “the whole estate” should have a recovery plan in writing.
The destroy command in CI
In CI, the destroy is usually a separate workflow from the apply:
destroy-job:
if: github.event.label == 'destroy-staging'
steps:
- uses: actions/checkout@v4
- run: terraform init
- run: terraform plan -destroy -out=destroy.tfplan
- run: terraform show -json destroy.tfplan > destroy.json
- uses: actions/upload-artifact@v4
with:
name: destroy-plan
path: destroy.tfplan
- run: terraform apply destroy.tfplan
The destroy is gated by a label, requires a second engineer to approve, and uploads the destroy plan as an artifact for audit.
What comes next
The next lesson is HCL fundamentals. The state, plan, apply, and destroy lessons are the most production-relevant context for the configuration language lessons that follow.
Knowledge check · 7 questions
Q1. What is the role of terraform fmt?
Q2. What is the role of terraform validate?
Q3. What is the role of terraform init?
Q4. terraform plan modifies the real world.
Q5. Which of the following are part of the safe workflow? (Select all that apply.)
Q6. What does terraform show do?
Q7. A team runs terraform plan and sees no changes. The apply also shows no changes. The real world has drifted. What should they do?
Passing score: 75%. Answers are checked in this browser.