Cloud resources created using Terraform are maintained using Infrastructure as Code. Any changes, commissioning, and decommissioning of resources are supposed to be handled using IaC. Teams that have adapted Terraform for infrastructure management usually have strict compliance with manual changes via the web console.
In this post, you will learn how to manage the destruction of cloud infrastructure resources under Terraform management (resources created using Terraform IaC) and how to overcome some common pitfalls.
What we will cover:
Terraform destroy
command is a common way to destroy resources managed by Terraform. It terminates a complete set of cloud infrastructure or a targeted resource by deleting infrastructure resources present in the state file. When the destroy
command is executed, Terraform first validates the information contained in the state file by cross-checking with cloud provider APIs. Internally it builds a dependency graph to identify the sequence in which the resources are to be destroyed.
Before running terraform destroy
you should review the changes and verify the execution plan using terraform plan -destroy
. Once executed, terraform destroy
permanently deletes the targeted resources.
The state file is Terraform’s source of truth when performing any operation. If the state file is corrupted, Terraform can behave in unwarranted ways. If the state file does not mention a certain resource—but the resource exists in the real world—then running terraform destroy
will NOT destroy that resource.
When to use the destroy command?
The terraform destroy
command should be used mostly when you want to tear down your infrastructure resources. However, there are cases where you will use the target option to destroy only what you no longer need, or that is not working properly.
You can use terraform destroy
command with different options, for example:
1. terraform destroy -auto-approve
By default, whenever you run a terraform destroy
, you will first see a destroy plan and have to approve it manually. Taking advantage of the “-auto-approve” option lets you destroy all resources without the need of any manual approval. This can be useful in CI/CD pipelines, especially for ephemeral environments that you want to destroy on a schedule, or whenever an event occurs.
2. terraform destroy -refresh=false
By using the -refresh=false
option with terraform destroy
, you ensure that resources are destroyed based on the information Terraform has in the state file, without refreshing the state prior to running this operation.
3. terraform destroy -lock and -lock-timeout
The -lock
option controls whether or not Terraform will lock the state during the operation it provides (in our case destroy) and the -lock-timeout
refers to the duration of how long Terraform should wait to acquire the lock which can be useful in automated environments where you may expect brief delays in acquiring a lock and do not want an immediate failure if the state is locked.
Running terraform destroy
is not the only way to delete the infrastructure resources. Continuing on the importance of the state file – there are essentially two ways to destroy resources using Terraform.
1. Destroy resources with Terraform plan
Assuming you have a set of resources deployed using Terraform configurations, to destroy one or all of the resources in this configuration, simply remove/comment out the resource blocks and run terraform plan
to validate the desired action and then apply
.
During its validation phase, Terraform identifies gaps in configuration and state files. It also checks if real-world resources exist and generates a plan to remove any resources that are available in the real world but not in the configuration.
This also means that if we manually delete a particular resource, the next destroy cycle will consider it and only attempt to delete the existing resource.
Example: How to destroy an AWS instance using Terraform
The example code below (uncommented) will create two EC2 instances – demo_vm_1
and demo_vm_2
. To destroy one of the EC2 instances, comment out the corresponding resource block in the code – demo_vm_2
.
resource "aws_instance" "demo_vm_1" {
ami = "ami-07df274a488ca9195"
instance_type = "t2.micro"
tags = {
name = "Demo VM One"
}
}
/*resource "aws_instance" "demo_vm_2" {
ami = "ami-07df274a488ca9195"
instance_type = "t2.micro"
tags = {
name = "Demo VM Two"
}
}*/
Run terraform plan
and observe the output.
aws_instance.demo_vm_2: Refreshing state... [id=i-0b63433033e61d818]
aws_instance.demo_vm_1: Refreshing state... [id=i-0b46c3280f7d305df]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
- destroy
Terraform will perform the following actions:
# aws_instance.demo_vm_2 will be destroyed
- resource "aws_instance" "demo_vm_2" {
- ami = "ami-07df274a488ca9195" -> null
- arn = "arn:aws:ec2:eu-central-1:532199187081:instance/i-0b63433033e61d818" -> null
- associate_public_ip_address = true -> null
- availability_zone = "eu-central-1b" -> null
.
.
.
.
}
}
Plan: 0 to add, 0 to change, 1 to destroy.
Terraform identifies the absence of demo_vm_2
in the configuration, but notices that it exists in the state file and the real world. Thus, it generates a plan to destroy that EC2 instance. Running terraform apply
now would cause the destruction of this EC2 instance.
2. Destroy resources using terraform destroy
command
The terraform destroy
command is a more common option to terminate resources managed by your current Terraform project.
Example: Destroy a specific Terraform resource using the --target
flag
To destroy a specific EC2 instance (demo_vm_1
), the --target
argument can be supplied to the destroy
command with the resource path to identify the correct resource as shown below.
terraform destroy --target aws_instance.demo_vm_1
Terraform validates the state and its existence in the AWS EC2 console and triggers the deletion of the specified EC2 instance.
.
.
.
.
- throughput = 0 -> null
- volume_id = "vol-040c21ea887ddd779" -> null
- volume_size = 8 -> null
- volume_type = "gp2" -> null
}
}
Plan: 0 to add, 0 to change, 1 to destroy.
╷
│ Warning: Resource targeting is in effect
│
│ You are creating a plan with the -target option, which means that the result of this plan may not represent all of the changes requested by the current
│ configuration.
│
│ The -target option is not for routine use, and is provided only for exceptional situations such as recovering from errors or mistakes, or when
│ Terraform specifically suggests to use it as part of an error message.
╵
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
aws_instance.demo_vm_1: Destroying... [id=i-0435cfff2020ce5fd]
aws_instance.demo_vm_1: Still destroying... [id=i-0435cfff2020ce5fd, 10s elapsed]
aws_instance.demo_vm_1: Still destroying... [id=i-0435cfff2020ce5fd, 20s elapsed]
aws_instance.demo_vm_1: Still destroying... [id=i-0435cfff2020ce5fd, 30s elapsed]
aws_instance.demo_vm_1: Still destroying... [id=i-0435cfff2020ce5fd, 40s elapsed]
aws_instance.demo_vm_1: Destruction complete after 40s
╷
│ Warning: Applied changes may be incomplete
│
│ The plan was created with the -target option in effect, so some changes requested in the configuration may have been ignored and the output values may
│ not be fully updated. Run the following command to verify that no other changes are pending:
│ terraform plan
│
│ 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, or when Terraform specifically suggests to use it as part of an error message.
╵
Destroy complete! Resources: 1 destroyed.
Validate the same by logging into the AWS Console.
Example: Destroy all resources using terraform destroy
command
To destroy both EC2 instances simply run terraform destroy
without --target
argument. Terraform validates the state and existence of the EC2 instances and triggers the resource deletion. Try running terraform destroy
with our example now, and observe the output.
.
.
.
Plan: 0 to add, 0 to change, 2 to destroy.
Changes to Outputs:
- instance_id_1 = "i-0195745b98b21bec7" -> null
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
aws_instance.demo_vm_2: Destroying... [id=i-0cdf2d51800624a22]
aws_instance.demo_vm_1: Destroying... [id=i-0195745b98b21bec7]
aws_instance.demo_vm_2: Still destroying... [id=i-0cdf2d51800624a22, 10s elapsed]
aws_instance.demo_vm_1: Still destroying... [id=i-0195745b98b21bec7, 10s elapsed]
aws_instance.demo_vm_2: Still destroying... [id=i-0cdf2d51800624a22, 20s elapsed]
aws_instance.demo_vm_1: Still destroying... [id=i-0195745b98b21bec7, 20s elapsed]
aws_instance.demo_vm_1: Still destroying... [id=i-0195745b98b21bec7, 30s elapsed]
aws_instance.demo_vm_2: Still destroying... [id=i-0cdf2d51800624a22, 30s elapsed]
aws_instance.demo_vm_2: Still destroying... [id=i-0cdf2d51800624a22, 40s elapsed]
aws_instance.demo_vm_1: Still destroying... [id=i-0195745b98b21bec7, 40s elapsed]
aws_instance.demo_vm_1: Destruction complete after 41s
aws_instance.demo_vm_2: Still destroying... [id=i-0cdf2d51800624a22, 50s elapsed]
aws_instance.demo_vm_2: Still destroying... [id=i-0cdf2d51800624a22, 1m0s elapsed]
aws_instance.demo_vm_2: Destruction complete after 1m7s
Destroy complete! Resources: 2 destroyed.
3. Destroy protected resources
Let us recreate the infrastructure from the previous example, this time with a minor difference. Add disable_api_termination = true
attribute to demo_vm_2
. Try to destroy the same using terraform destroy
command again. Observe the output with error message.
As per the error message, we got OperationNotPermitted
error for one of the EC2 instances. The Terraform configuration responsible for creating this EC2 instance has disable_api_termination
set to true
. This is additional protection provided by the cloud provider to avoid accidental deletion of the EC2 instance. Because Terraform operations utilize cloud provider APIs to apply
or destroy
configurations, it cannot do anything disallowed by the provider’s API.
In this case it means that Terraform is not be able to delete demo_vm_2
. However, it was able to delete demo_vm_1
for which this property was not set at all. This tells us that it defaults to false
.
Log in to the AWS console, disable the termination property for this EC2 instance, and run the destroy command again. This time, it should succeed.
At this time, Terraform cannot override this behavior. However, this is a positive because it helps prevent any accidental deletion of critical resources.
4. Destroy all except a few
Sometimes, you will face scenarios where you would like to recreate most of the infrastructure components but keep a few critical ones in place. Currently, there is no way to specify the resources NOT to be destroyed using Terraform.
However, since the state plays a major role in Terraform’s decision making – an apparent workaround would be as follows:
- Remove the resource from the state file using
terraform state rm
command. - Run
terraform destroy
– without Terraform--target
argument. - After successful destruction,
import
the target resources back in the state file. This post describes how to import cloud resources under Terraform.
Terraform destroy
vs. terraform state rm
Unlike terraform destroy
, terraform state rm
doesn’t actually delete resources from the infrastructure. Running terraform state rm
lets Terraform know that some of the resources are no longer managed by Terraform. When destroy
is executed, Terraform does not touch those resources. When the cleanup of other resources is done, and the remaining resources are imported back, Terraform then manages the resources again.
There are many reasons why a Terraform destroy operation may fail.
RBAC | Terraform destroy will fail if the role/account you are using doesn’t have enough permissions to delete the resources. |
Locked state | When the state is locked, you won’t be able to run operations against your state, and you will need to wait for it to be available. |
Network issues | If you have any connectivity issues, your delete requests may not arrive properly. |
Syntax errors | If you have made changes to your code that have syntax errors, terraform destroy won’t run until you fix them. |
Resource dependencies | This was mostly happening in earlier versions of Terraform, but if you create explicit dependencies using “depends_on”, you would need to sometimes run terraform destroy multiple times to make it work. |
Terraform destroy is a very powerful command, but it will remove all infrastructure resources based on your Terraform configuration and state file, so it should be approached with caution.
Below is a list of best practices you should follow:
1. Review changes before destroying
You should first check the execution plan before destroying your resources to ensure that everything will happen exactly as you want.
2. Take advantage of version control
Ensure your code is kept in a VCS system because this will allow you to track changes to the codebase and roll back to previous configurations if the need arises.
3. Use apply to destroy
If you want to specifically remove a resource, it is easier to delete the resource from your configuration (if you won’t ever need it) and applying the code, rather than doing a targeted destroy. In any case, check the execution plan before making the change.
4. Use remote state and state locking
It is fundamental to use a remote state for collaboration, and state locking is helpful to ensure that no concurrent operations happen in the state. Without locking, multiple engineers can do operations at the same time on the state file, resulting in state corruption and errors overall.
5. Ensure least privileged access
To keep your infrastructure safe, you should always ensure that only authorized users have permissions to destroy resources.
6. Use deletion protection
In case of production critical resources such as production databases, enable deletion protection to prevent accidental removal.
Terraform destroy
is a command that should be used wisely. It is not something that would be used on a daily basis in managing the production environment.
However, making use of it in sub-production environments would be quite frequent as a lot of try-outs for proofs-of-concept require cleanup activities.
Spacelift lets you customize the entire infrastructure life-cycle management by providing the ability to run pre and post commands at every stage – including the destroy
phase – which can be very useful in keeping track of things.
We encourage you also to explore how Spacelift makes it easy to work with Terraform. If you need any help managing your Terraform infrastructure, building more complex workflows based on Terraform, and managing AWS credentials per run, instead of using a static pair on your local machine, Spacelift is a great tool for this. It supports Git workflows, policy as code, programmatic configuration, context sharing, drift detection, and many more great features. Create your free trial account or book a demo with one of our engineers.
Note: New versions of Terraform will be placed under the BUSL license, but everything created before version 1.5.x stays open-source. OpenTofu is an open-source version of Terraform that will expand on Terraform’s existing concepts and offerings. It is a viable alternative to HashiCorp’s Terraform, being forked from Terraform version 1.5.6.
Manage Terraform Better and Faster
If you are struggling with Terraform automation and management, check out Spacelift. It helps you manage Terraform state, build more complex workflows, and adds several must-have capabilities for end-to-end infrastructure management.