| Tested on | Ubuntu 26.04 LTS (Resolute Raccoon) |
|---|---|
| Package | terraform 1.15.8-1kreuzwerker/docker 3.9.0 |
| Applies to | Any host with Terraform installed |
| Lab environment | Single Ubuntu VM with Terraform and Docker — Terraform lab environment on Ubuntu |
| Privilege | Normal user (Docker socket access required) |
| Scope | terraform destroy workflow — disposable Docker lab, plan -destroy preview, confirmation prompt, -auto-approve, -var and -var-file, -target with warnings, remove-from-config comparison, state after destroy, and brief prevent_destroy. Does not cover full lifecycle rules, state rm shortcuts, backend deletion, or cloud account teardown. |
| Related guides | Terraform lab environment on Ubuntu terraform plan command Terraform init command Terraform providers Terraform Associate certification course |
terraform destroy tears down infrastructure Terraform manages in the current working directory. This guide provisions disposable Docker resources, verifies them outside Terraform, previews deletion, walks through confirmation and automation flags, and shows what remains in state and on disk afterward.
~/terraform-labs/terraform-destroy/ so you do not collide with other lessons on the same VM.
What does terraform destroy do?
terraform destroy builds a destroy plan for every resource instance recorded in the current state, shows you the proposed deletions, and — after confirmation unless you pass -auto-approve — deprovisions those remote objects through provider APIs.
In current Terraform releases, terraform destroy is effectively destroy-mode apply. The same graph walk that creates and updates resources during apply is reused to delete them. You do not need a separate legacy destroy engine — one workflow handles the full lifecycle.
Destroy only affects objects managed by this configuration and state. It does not remove unrelated Docker containers, networks, or volumes you created manually outside Terraform.
Prepare disposable Terraform resources
Provision real objects first so destroy has something to remove and you can verify results with Docker commands.
Create the lab directory:
mkdir -p ~/terraform-labs/terraform-destroyMove into it — every command below assumes you are here:
cd ~/terraform-labs/terraform-destroyWrite a small stack with a network, image, container, and volume. This lab uses a cached ghcr.io/nginx/nginx-unprivileged:alpine image so you are not blocked when Docker Hub rate-limits anonymous pulls:
terraform {
required_version = ">= 1.12.0"
required_providers {
docker = {
source = "kreuzwerker/docker"
version = "~> 3.0"
}
}
}
provider "docker" {}
variable "environment" {
type = string
default = "lab"
}
resource "docker_network" "lab" {
name = "tf-destroy-lab"
}
resource "docker_image" "nginx" {
name = "ghcr.io/nginx/nginx-unprivileged:alpine"
keep_locally = true
}
resource "docker_container" "web" {
name = "tf-destroy-web"
image = docker_image.nginx.image_id
networks_advanced {
name = docker_network.lab.name
}
ports {
internal = 8080
external = 8092
}
labels {
label = "environment"
value = var.environment
}
}
resource "docker_volume" "data" {
name = "tf-destroy-data"
}Save that as main.tf, then initialize the working directory:
terraform initCreate the Docker resources Terraform will manage:
terraform applyType yes when prompted, or pass -auto-approve in the lab. Confirm the resources exist outside Terraform:
docker ps --filter name=tf-destroy-webSample output:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
05172779525b 334d92979f15 "/docker-entrypoint.…" 12 seconds ago Up 11 seconds 0.0.0.0:8092->8080/tcp tf-destroy-webThe container is running on port 8092. List the custom network next:
docker network ls --filter name=tf-destroy-labSample output:
NETWORK ID NAME DRIVER SCOPE
335e8514b8ac tf-destroy-lab bridge localConfirm the named volume exists as well:
docker volume ls --filter name=tf-destroy-dataSample output:
DRIVER VOLUME NAME
local tf-destroy-dataFour managed objects now exist in both Docker and Terraform state — a realistic starting point for teardown. After destroy, Terraform removes all four resource instances from state, but keep_locally = true on the image means Docker may still keep that image on disk even when docker_image.nginx is gone from state.
Preview destruction before running terraform destroy
Always review what Terraform will delete. terraform plan -destroy prints the same destroy diff without executing it — see terraform plan command for full plan-reading detail.
terraform plan -destroySample output (trimmed):
# docker_container.web will be destroyed
- resource "docker_container" "web" { ... }
# docker_image.nginx will be destroyed
- resource "docker_image" "nginx" { ... }
# docker_network.lab will be destroyed
- resource "docker_network" "lab" { ... }
# docker_volume.data will be destroyed
- resource "docker_volume" "data" { ... }
Plan: 0 to add, 0 to change, 4 to destroy.The - symbols and the summary line tell you Terraform intends to delete every managed instance. A speculative terraform plan -destroy is only a preview — run terraform destroy when you are ready to execute that teardown.
Destroy all managed resources
Run destroy when the preview matches what you expect:
terraform destroyTerraform prints the destroy plan again, then asks for explicit confirmation:
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, as shown above.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value:Decline once in the lab to see the safe default — type anything other than yes:
printf 'no\n' | terraform destroySample output:
Destroy cancelled.Nothing is deleted and state is unchanged. When you are ready to proceed, approve the run:
terraform destroy -auto-approveSample output (trimmed):
docker_container.web: Destroying...
docker_container.web: Destruction complete after 1s
docker_network.lab: Destroying...
docker_volume.data: Destroying...
docker_image.nginx: Destroying...
Destroy complete! Resources: 4 destroyed.Verify resources were destroyed
Terraform destroys four resource instances from state. On the host, only three Docker objects disappear — the container, network, and volume. The image stays in Docker's local image store because keep_locally = true on docker_image.nginx.
Check the container first:
docker ps -a --filter name=tf-destroy-webThe container name should no longer appear. Check the network next:
docker network ls --filter name=tf-destroy-labCheck the volume:
docker volume ls --filter name=tf-destroy-dataThose three commands should return no matching rows. Inspect the image Terraform tracked:
docker image inspect ghcr.io/nginx/nginx-unprivileged:alpineSample output (trimmed):
[
{
"Id": "sha256:...",
"RepoTags": [
"ghcr.io/nginx/nginx-unprivileged:alpine"
],
...
}
]Terraform removed docker_image.nginx from state, but the image remains in Docker because keep_locally = true. Destroying a Terraform resource does not always mean the provider deletes the underlying local artifact; resource-specific arguments can alter destroy behavior.
List what Terraform still tracks:
terraform state listWith every managed instance destroyed, the command prints nothing — state contains no resource addresses.
terraform destroy options
Skip the confirmation prompt with -auto-approve
-auto-approve runs the destroy plan without the interactive yes prompt. Recreate the lab stack first:
terraform apply -auto-approveUse non-interactive destroy only when a human or pipeline has already reviewed the plan:
terraform destroy -auto-approveReserve -auto-approve for disposable labs and controlled automation. Production teardown should keep the confirmation step or use a reviewed saved destroy plan workflow.
Pass variables with -var and -var-file
Terraform evaluates configuration during destroy, not only during apply. If your .tf files reference var.environment, Terraform needs those values even when every resource is being deleted.
Create a variable file for repeatable lab runs:
printf 'environment = "lab"\n' > test.tfvarsPass the same values you used at apply time:
terraform destroy -var='environment=lab' -auto-approveOr reference the file:
terraform destroy -var-file=test.tfvars -auto-approveTerraform still evaluates configuration while building a destroy plan, so required input variables may still need values. This is especially important when variables affect provider configuration, module inputs, or resource addressing. Use the same variable sources you normally use for that working directory.
Target specific resources
-target limits destruction to one resource address. Recreate the full stack, then destroy only the volume:
Recreate the full stack before you test targeting:
terraform apply -auto-approveDestroy only the volume address while the docker_volume block remains in configuration:
terraform destroy -target=docker_volume.data -auto-approveTerraform warns that targeting is exceptional:
Note that the -target option is not suitable for routine use, and is provided
only for exceptional situations such as recovering from errors or mistakes...After a targeted destroy, terraform state list still shows the container, network, and image — only the volume address is gone. Targeted destruction can leave configuration, state, and live infrastructure out of sync. Use it for recovery, not as the normal way to remove a resource from everyday workflows.
Destroy one resource vs remove it from configuration
These workflows look similar but serve different goals.
Scenario A — targeted destroy: run terraform destroy -target=docker_volume.data while the docker_volume block remains in main.tf. Terraform deletes the remote volume but still expects a volume because the block is present. The next terraform plan may propose recreating it.
Scenario B — remove from configuration: delete the docker_volume block from main.tf while the volume still exists, then run a normal plan:
terraform planSample output (trimmed):
# docker_volume.data will be destroyed
- resource "docker_volume" "data" { ... }
Plan: 0 to add, 0 to change, 1 to destroy.Apply that plan with terraform apply to remove only the orphaned volume while keeping the rest of the stack under management.
| Approach | Configuration block | Typical intent |
|---|---|---|
terraform destroy -target=... |
Still present | Exceptional recovery or emergency partial teardown |
Remove block + terraform plan / apply |
Removed | Permanent model change — resource no longer belongs in this module |
For day-to-day changes, remove the resource from configuration and let a normal plan drive deletion. Reserve -target for the exceptional cases Terraform documents in its warning text.
terraform destroy vs plan and apply destroy modes
| Command | Executes deletions? | Confirmation prompt? |
|---|---|---|
terraform plan -destroy |
No — preview only | No |
terraform destroy |
Yes | Yes, unless -auto-approve |
terraform apply -destroy |
Yes | Yes, unless -auto-approve |
terraform destroy and terraform apply -destroy are equivalent destroy-mode entry points in current Terraform. Pick whichever reads clearer in your runbook; you do not need separate examples for both in everyday use.
State and configuration after destroy
What happens to Terraform state?
After a full destroy, terraform state list returns no addresses — there are no managed instances left. The state file usually still exists on disk with an empty resource list (or metadata only). Terraform does not automatically delete terraform.tfstate just because the last resource was destroyed.
Inspect the file when you want to confirm:
wc -l terraform.tfstateYou should still see a JSON state file, not a missing path error.
Configuration files remain
Destroy removes remote objects, not your source code. After teardown:
ls *.tf .terraform.lock.hclSample output:
main.tf
.terraform.lock.hclYour .tf files and lock file stay in place so you can edit configuration and run terraform apply again. The .terraform/ directory with provider plugins also remains until you delete it.
Block destroy with prevent_destroy
Add a lifecycle guard when a resource must never be deleted accidentally:
resource "docker_container" "web" {
lifecycle {
prevent_destroy = true
}
name = "tf-destroy-web"
image = docker_image.nginx.image_id
...
}With the guard in place, a full destroy fails while that resource is still in scope:
terraform destroySample output:
Error: Instance cannot be destroyed
Resource docker_container.web has lifecycle.prevent_destroy set, but the plan
calls for this resource to be destroyed.To deliberately destroy that protected object, disable or remove prevent_destroy while keeping the resource configuration available, then review the resulting plan. Targeting can exclude the protected object from a broader operation, but it does not remove the protection from that object. Full prevent_destroy and related rules belong in Terraform lifecycle.
Recreate or remove the lab
After you finish the exercises, either keep ~/terraform-labs/terraform-destroy/ for another run or remove the directory entirely. If resources were destroyed cleanly, deleting the folder is safe.
Do not manually delete terraform.tfstate as a substitute for destroying resources — that orphans remote objects Terraform no longer tracks while they continue running on the host.
To run the walkthrough again:
terraform applyCommon terraform destroy problems
| Symptom | Likely cause | Fix |
|---|---|---|
Instance cannot be destroyed |
lifecycle { prevent_destroy = true } on a resource in the destroy plan |
Disable or remove prevent_destroy while the resource block remains, then review the plan |
Error acquiring the state lock |
Another Terraform process holds the lock | Wait for the other run to finish, or see Terraform state locking |
| Provider authentication or connection error | Docker daemon stopped or socket permissions missing | Confirm docker ps works on the host |
| Dependency error during destroy | Provider delete order or external dependency | Read the provider error; resolve the dependency or use documented recovery steps |
| Resource already deleted manually | State still tracks an address that no longer exists | Refresh or remove the stale binding — see terraform state commands |
| Target warning followed by unexpected recreate plan | -target left configuration and state misaligned |
Follow with a full terraform plan and normal apply, or fix configuration |
References
- terraform destroy command
- terraform plan command
- terraform apply command
- Lifecycle meta-arguments
- Docker provider documentation
Summary
terraform destroy proposes and executes deletion of every resource instance managed by the current configuration and state.
You provisioned a disposable Docker stack, previewed teardown with terraform plan -destroy, cancelled once at the confirmation prompt, then destroyed everything.
Terraform cleared four resource instances from state:
- The container, network, and volume disappeared from Docker
- The nginx image remained locally because
keep_locally = true
-auto-approve skips the prompt for labs and controlled automation only. -var and -var-file still matter because Terraform reads configuration during destroy.
Targeted destroy is an exceptional tool — removing a resource block and running a normal plan is the everyday way to drop something from your model.
Destroy clears managed infrastructure, not your .tf files or lock file, and usually leaves an empty state file on disk.
The mistake to avoid:
- Using
-targetfor routine cleanup - Deleting state by hand instead of letting Terraform deprovision resources it manages
Next in the Core Workflow track: deeper apply and lifecycle topics once your preview-and-destroy rhythm is solid.

