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.

What Happens During terraform destroy #

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
StepWhat It DoesOutputImportant Notes
1. Read stateIdentify all managed resourcesResource listOnly resources in state get deleted
2. Reverse planThe opposite of apply — everything marked for destroyDeletion planSame as plan -destroy
3. ConfirmationUser types yesSkipped with -auto-approve
4. Delete in orderThe most dependent resources are deleted firstResources deletedOrder is automatic from the dependency graph
5. Empty stateThe state file is emptiedEmpty stateNo 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:#c62828

Running 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
CommandScopeConfirmationUse For
terraform destroyAll resourcesType yesEnvironment teardown
terraform destroy -auto-approveAll resourcesNoCI/CD teardown
terraform destroy -target=xSpecific resourcesType yesPartial deletion
terraform apply -destroyAll resourcesType yesSame as destroy
Remove block from .tf + applySpecific resourcesType yesMost 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
MechanismLevelWhat It DoesDrawback
prevent_destroy = trueTerraformErrors when a destroy is plannedMust be removed manually if you truly want to destroy
ignore_changesTerraformIgnores attribute changesNot a direct destroy protection
terraform state rmTerraformDetaches from managementResource orphaned, managed by no one
deletion_protectionProvider/CloudPrevents deletion at the API levelLimited 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"]
ScenarioUse 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❌ NoRemove blocks from .tf, then apply
Production database without a backupNEVERBack up first, then consider
Unsure what will be deletedNEVERRun 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:#c62828

Alternative 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
AlternativeCommandOther ResourcesData Safe?Best For
Remove block + applyEdit .tf + applyUnaffected✅ YesDeleting specific resources
Targeted destroydestroy -target=xUnaffected✅ YesDeleting a specific module/resource
Unmanagestate rmUnaffected✅ YesOwnership transitions
Full destroydestroyEverything deleted❌ NoEnvironment 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 PatternCommandAudit TrailRisk
Direct destroydestroy -auto-approve❌ NoneHigh
Saved destroy planplan -destroy -out=x + apply x✅ YesLow

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 →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact