Skip to main content
RunBook Academy

TerraformV · The Terraform WorkflowDestroy

terraform destroy: The Most Dangerous Operation

Advanced⏱ ~18 min🧪 Lab requiredbashterraform

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

Not yet marked complete on this device.

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:

  1. Reads the configuration and state.
  2. Computes the destroy plan (essentially, the inverse of the apply plan).
  3. Prompts for approval (unless -auto-approve is set).
  4. Destroys resources in reverse dependency order.
  5. Removes the resource entries from state.
  6. 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-approve does 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_destroy does 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:

  1. Plan the destroy. terraform plan -destroy produces the destroy plan. Review every resource it proposes to destroy.
  2. Save the plan. terraform plan -destroy -out=destroy.tfplan.
  3. Have a second engineer review. The plan is the unit of review.
  4. Back up the state. The state may be needed for recovery. cp terraform.tfstate terraform.tfstate.pre-destroy.
  5. Verify the targets. Confirm that the resources being destroyed are the ones intended.
  6. Apply the saved plan. terraform apply destroy.tfplan.
  7. Verify the destroy. After the destroy, confirm the resources are gone in the providers console.
  8. 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 destroy is 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

  1. Q1. What is the role of terraform fmt?

  2. Q2. What is the role of terraform validate?

  3. Q3. What is the role of terraform init?

  4. Q4. terraform plan modifies the real world.

  5. Q5. Which of the following are part of the safe workflow? (Select all that apply.)

  6. Q6. What does terraform show do?

  7. 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.