terraform destroy removes all infrastructure resources managed by your Terraform configuration. It is the opposite of terraform apply.
terraform destroyTerraform reads your configuration and state file, calculates a destroy plan (everything that exists gets removed), shows you the plan, and waits for approval before deleting anything.
Warning:
terraform destroyis permanent. Deleted cloud resources cannot be recovered unless you have backups (e.g., RDS snapshots, EBS snapshots). Always review the destroy plan carefully.
Before destroying anything, Terraform shows you exactly what will be deleted:
Terraform will destroy all your managed infrastructure, as shown below.
These resources will be destroyed in the proper reverse order.
# aws_instance.web will be destroyed
- resource "aws_instance" "web" {
- ami = "ami-0c55b159cbfafe1f0" -> null
- instance_type = "t3.micro" -> null
- id = "i-0abc12345def67890" -> null
- public_ip = "54.123.45.67" -> null
}
# aws_security_group.web will be destroyed
- resource "aws_security_group" "web" { ... }
# aws_subnet.public will be destroyed
- resource "aws_subnet" "public" { ... }
# aws_vpc.main will be destroyed
- resource "aws_vpc" "main" { ... }
Plan: 0 to add, 0 to change, 4 to destroy.
Do you really want to destroy all resources?
Terraform will destroy all your managed infrastructure.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value: yes
Notice the reverse dependency order: Terraform destroys in the reverse of the creation order. The EC2 instance is destroyed before the security group, which is destroyed before the subnet, which is destroyed before the VPC. This prevents dependency errors (e.g., you cannot delete a VPC that still has running instances).
| Use Case | Appropriate? |
|---|---|
| Tearing down a dev/test environment | â Perfect use case |
| Cleaning up after a tutorial or experiment | â Perfect use case |
| Removing a feature branch environment | â Standard practice |
| Decommissioning part of production | â ï¸ Use with extreme caution + backup strategy |
| "I'll just recreate it" in production | â Almost always wrong |
terraform destroy is most commonly used for ephemeral environments, dev environments, feature branch previews, CI/CD testing environments, and tutorial cleanup.
To destroy a single resource (or module) without touching the rest:
# Destroy a single resource
terraform destroy -target=aws_instance.web
# Destroy an entire module
terraform destroy -target=module.databaseTerraform will show a destroy plan for only the targeted resource:
# aws_instance.web will be destroyed
- resource "aws_instance" "web" {
- id = "i-0abc12345def67890" -> null
}
Plan: 0 to add, 0 to change, 1 to destroy.
Caution:
-targetcan leave your state inconsistent. If other resources depended on the destroyed resource, they still exist in state but their dependency is gone. Always run a fullplanafter a targeted destroy to check for consistency.
Like apply, you can skip the confirmation with -auto-approve:
terraform destroy -auto-approveThis is commonly used in CI/CD pipelines to automatically tear down ephemeral environments:
# GitLab CI, destroy on branch delete
teardown:
stage: destroy
script:
- terraform init -input=false
- terraform destroy -input=false -auto-approve
when: manualTo see the destroy plan without any risk, use terraform plan -destroy:
terraform plan -destroyThis is functionally equivalent to terraform destroy without the actual deletion, great for reviewing what would be destroyed before committing.
If you want certain resources to be retained even when the configuration is removed, use the prevent_destroy lifecycle argument:
resource "aws_s3_bucket" "critical_data" {
bucket = "my-production-data"
lifecycle {
prevent_destroy = true
}
}If you try to destroy this resource (or remove it from your config), Terraform will error:
â·
â Error: Instance cannot be destroyed
â Resource aws_s3_bucket.critical_data has lifecycle.prevent_destroy set to true.
â To allow this object to be destroyed, remove the lifecycle block or set
â prevent_destroy = false.
âµ
Use prevent_destroy for critical resources like production databases, S3 buckets with important data, or any resource where accidental deletion would be catastrophic.
While you have your terminal open, terraform fmt is a command you will use constantly:
terraform fmtIt automatically formats all .tf files in the current directory to match the Terraform style guide, consistent indentation, aligned = signs, and proper spacing.
# Format and show which files were changed
terraform fmt -diff
# Format all files recursively (including modules)
terraform fmt -recursive
# Check formatting without making changes (useful in CI)
terraform fmt -checkExample, before formatting:
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = {
Name = "web-server"
}
}After terraform fmt:
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = {
Name = "web-server"
}
}Pro tip: Add
terraform fmt -checkto your CI pipeline. If anyone submits unformatted code, the pipeline fails, this enforces consistent style across the team without manual code review comments.
terraform validate checks your configuration for syntax errors and internal consistency:
terraform validateSuccessful output:
Success! The configuration is valid.
Error example:
â·
â Error: Unsupported argument
â on main.tf line 8
â An argument named "instanse_type" is not expected here.
â Did you mean "instance_type"?
âµ
validate catches:
What validate does NOT check:
# Typical workflow before pushing code:
terraform fmt -recursive
terraform validate
terraform plan<MCQExercise exercise={{ id: 'tf-destroy-q1', title: 'Question 1: Destroy Order', description: 'You have a VPC, a subnet inside the VPC, and an EC2 instance inside the subnet. When you run terraform destroy, in what order does Terraform destroy these resources?', options: [ { id: 'a', text: 'VPC â Subnet â EC2 instance (creation order)' }, { id: 'b', text: 'EC2 instance â Subnet â VPC (reverse creation order)' }, { id: 'c', text: 'Alphabetical order: EC2 instance, Subnet, VPC' }, { id: 'd', text: 'All three simultaneously in parallel' }, ], correctId: 'b', explanation: 'Terraform destroys resources in reverse dependency order. The EC2 instance depends on the subnet, which depends on the VPC. So the correct destroy order is EC2 â Subnet â VPC. If Terraform tried to delete the VPC first while the EC2 instance and subnet still existed, the cloud API would return an error (you cannot delete a VPC with active resources).', wrongExplanations: { a: 'Creation order would cause dependency errors. Terraform uses reverse order to avoid these.', c: 'Terraform uses dependency order, not alphabetical.', d: 'Parallel destruction is only done for resources with no dependencies between them. These resources have a dependency chain.', }, }} />
<MCQExercise exercise={{ id: 'tf-destroy-q2', title: 'Question 2: prevent_destroy', description: 'A developer accidentally removes the aws_rds_cluster.production resource block from the Terraform config. What happens when they run terraform apply?', options: [ { id: 'a', text: 'Nothing, Terraform ignores resources that are removed from config' }, { id: 'b', text: 'Terraform shows a plan to delete the RDS cluster and asks for confirmation' }, { id: 'c', text: 'Terraform errors because the config is incomplete' }, { id: 'd', text: 'Terraform moves the RDS cluster to an orphaned state but keeps it running' }, ], correctId: 'b', explanation: 'When a resource is removed from the Terraform configuration, Terraform plans to destroy it (since it is no longer declared). This is why the prevent_destroy lifecycle hook is so valuable for critical resources, it prevents accidental deletion by causing an error when Terraform tries to destroy the resource. Without prevent_destroy, removing the resource block from your config and running apply would delete your production database.', wrongExplanations: { a: 'Terraform does not ignore resources that disappear from config. It plans to destroy them.', c: 'Terraform does not error for removed resources. It treats removal as an intent to destroy.', d: 'There is no "orphaned" state concept in Terraform. Resources are either managed (in state) or not.', }, }} />
<MCQExercise exercise={{ id: 'tf-destroy-q3', title: 'Question 3: terraform fmt', description: 'Your CI/CD pipeline includes terraform fmt -check. A developer submits a merge request with an unformatted .tf file. What happens?', options: [ { id: 'a', text: 'The pipeline auto-formats the file and commits the changes' }, { id: 'b', text: 'The pipeline fails, blocking the merge request until the developer fixes formatting' }, { id: 'c', text: 'The pipeline warns but continues, formatting is optional' }, { id: 'd', text: 'The pipeline skips the formatting check for merge requests' }, ], correctId: 'b', explanation: 'terraform fmt -check exits with a non-zero exit code if any files need formatting (without making changes). In CI/CD, a non-zero exit code fails the pipeline step. This blocks the merge request, enforcing that all committed code must be properly formatted. The developer needs to run terraform fmt locally and push the fixed files.', wrongExplanations: { a: 'The -check flag only checks, it never modifies files. You need terraform fmt (no flag) to actually format.', c: 'The -check flag causes a non-zero exit code on unformatted files, which fails the CI step. It is not advisory.', d: 'CI pipelines do not automatically skip steps for MRs. The same pipeline runs regardless of trigger type unless explicitly configured.', }, }} />
terraform plan -destroy produces the destroy plan without executing it, and -out saves it for review. This is the safe way to answer "what would go away", particularly before tearing down anything shared. In CI, generating a destroy plan and posting it for approval is the equivalent of the normal plan-then-apply flow.
Add lifecycle { prevent_destroy = true } to anything holding data. Terraform then refuses any plan that would destroy it, including a full terraform destroy, and you must remove the flag deliberately to proceed. Combine that with provider-level protection, RDS deletion protection, S3 bucket versioning and MFA delete, so a mistake has to get past more than one gate. Note prevent_destroy cannot stop a destroy triggered by the resource being removed from configuration entirely.
Yes, with terraform destroy -target=aws_instance.example, but treat it as a recovery tool rather than routine practice. Targeting operates on a subset, so the rest of your configuration is not reconciled and dependent resources may be left referencing something gone. A cleaner approach for permanent removal is to delete the resource from the configuration and run a normal apply, which Terraform plans with full dependency awareness.
terraform destroy removes the resources and clears them from state, leaving everything consistent. Deleting in the console removes the resources but leaves state believing they exist, so the next plan shows Terraform intending to recreate them, which is drift, and at best confusing, at worst a surprise recreation of something expensive. If resources were removed outside Terraform, use terraform state rm to bring state back in line.
Only for genuinely ephemeral environments. A per-pull-request preview stack that is torn down on merge, or a nightly cleanup of a sandbox account. Even then, scope it tightly with a separate state file and credentials that cannot reach production, so an error in the pipeline cannot destroy the wrong thing. For anything long-lived, destruction should require a human.
See how data flows through Terraform: input variables, locals and outputs, where each belongs, how variable precedence works and how to handle secrets safely.
Declare Terraform input variables with types, defaults and validation, then set them via tfvars files, CLI flags and environment variables, including secrets.
Learn how Terraform local values work, when to choose locals over input variables or data sources, and how a locals block removes repeated expressions.