TutorialsTerraformCommands › terraform destroy

Terraform Commands · Command 6 of 8

terraform destroy

Removes every resource Terraform is tracking in state. It's the mirror image of terraform apply — instead of making reality match the configuration, it makes reality match an empty configuration.

Codey the Sr Developer, standing with a pointer, ready to walk through the guide
Touches real Azure resources → yes, deletes Reversible → no (data included) In this project → plan -destroy, then apply

What this command does in detail

Running terraform destroy directly is shorthand for two steps combined: it computes a plan where every resource in state is marked for deletion (the same as terraform plan -destroy), shows it to you, and — after you confirm — applies that plan, deleting each resource from Azure in an order that respects dependencies (in reverse of how they were created).

This is the one command on this page with real, permanent consequences beyond infrastructure: destroying database.tf's PostgreSQL server deletes every row of data inside it, with no built-in undo. Back up anything that matters before running this against a database you care about.

Where to run it from working directory

terraform -chdir=terraform destroy

Note that this only ever affects the resources tracked by that folder's state. Running it inside terraform/ never touches terraform/bootstrap/'s resources — the state storage account and the Terraform deployment identity — because they live in a completely separate state file. Destroying bootstrap requires deliberately running the same command inside that folder instead.

Precondition what must already be true

  • terraform init completed, connected to the same backend the resources were created through.
  • Azure credentials with delete permission for every resource being removed.
  • Any data you want to keep has already been backed up — destroy does not prompt you to export anything first.
  • Nothing outside Terraform's knowledge still depends on the resources being removed (for example, a manually-created resource pointing at one of them).

Postcondition what becomes true after it succeeds

  • Every resource that was tracked in this folder's state has been deleted from Azure.
  • State is updated to reflect that nothing remains — an empty (or near-empty) state file.
  • Any outputs that depended on the destroyed resources are no longer available.
  • Resources belonging to a different state (like bootstrap's storage account and identity) are completely untouched.

How this project uses it real examples

Not a literal “destroy” command.github/workflows/terraform.yml
args=()
if [[ "$OPERATION" == destroy ]]; then args+=(-destroy); fi
terraform -chdir=terraform plan -input=false -lock-timeout=5m "${args[@]}" -out workflow.tfplan
...
terraform -chdir=terraform apply -input=false -lock-timeout=5m workflow.tfplan

Selecting destroy in this project's GitHub Actions workflow never actually runs the bare terraform destroy command. Instead it runs terraform plan -destroy to produce a destroy-flavored plan, then terraform apply's that exact plan — the same reviewed-plan-then-apply pattern used for every other operation, so a destroy gets the identical safety guarantees (a plan captured in the job log, locking, no stale-plan risk) as a normal apply.

What survives a destroyby design

After the application infrastructure is destroyed, the bootstrap resource group, state storage account, and Terraform deployment identity are all still there — ready for the same project to be re-applied later without repeating the one-time bootstrap setup.

The manual equivalent of this command is az_delete_resources in the Manual Deployment tutorial — same outcome, one resource-group-wide Azure CLI call instead of a tracked, resource-by-resource plan.

Common errors and how to fix them

What you seeLikely causeHow to fix it
A resource fails to delete with a dependency conflictSomething outside Terraform's knowledge still references the resource (a manually created resource, or a lingering firewall rule).Remove the external dependency first, then re-run destroy.
A resource is “not found” during destroyIt was already deleted outside of Terraform (for example, manually in the Portal).Terraform generally treats this as already-satisfied and removes it from state; if it errors instead, run terraform state rm <address> to drop it from tracking manually.
“Error acquiring the state lock”Another run is using the same state concurrently.Wait for it to finish; this project's workflow concurrency group prevents overlapping infrastructure runs.
Data is gone after running this against the databaseThis is expected, correct behavior — destroy permanently deletes the PostgreSQL server and all its data.Restore from a backup made beforehand; there is no way to recover data after the fact through Terraform.
Codey giving a thumbs up

Torn down cleanly, bootstrap untouched — next, reading back what a run actually produced.