In this article, we will take a look at the various stages a Terraform resource goes through during its lifetime. We will look at the default resource behavior before looking at the lifecycle meta-argument, which can allow you to customize that behavior.
A Terraform resource
block defines a piece of infrastructure with the given settings. When the resource
block is defined in code, the resource object does not actually exist until terraform apply
is executed. Applying a configuration can result in the creation, modification, or destruction of a resource, depending on the configuration and state of the infrastructure. Terraform will make the real infrastructure match the configured settings for the resource.
Once an object is created, it is saved in the Terraform state. Terraform can then update the object if its settings are changed in the configuration or destroy it if the resource
is removed from the configuration.
Depending on the settings defined in the configuration, Terraform will take one of the following actions when applying the configuration:
Create
Creates the object with the defined settings.
Destroy
Destroys the object when the configuration no longer exists.
Update-in-place
Updates the object accordingly when the settings in the resource
block are changed. For example, adding a disk to a VM in Azure can be created and added without destroying the VM first.
Destroy and recreate
Destroys the object before re-creating it, if certain setting changes within the resource
configuration block means, this must happen on the given platform. For example, changing the name of a VM in Azure is not possible without first destroying the VM. It is destroyed and then recreated with the new VM name specified in the settings of the resource
block.
Terraform state can contain very sensitive data. Sometimes this is unavoidable because of the design of certain Terraform providers or because the definition of what is sensitive isn’t always simple and may vary between individuals and organizations. Spacelift provides two different approaches for sanitizing values when resources are stored or passed to Plan policies:
- Default Sanitization: All string values are sanitized.
- Smart Sanitization: Only the values marked as sensitive are sanitized.
Learn more about how Spacelift can help you with Resource Sanitization, and get started on your journey by creating a free trial account.
The Terraform lifecycle is a nested configuration block within a resource block. The lifecycle meta-argument can be used to specify how Terraform should handle the creation, modification, and destruction of resources. Meta-arguments are arguments used in resource blocks.
Terraform Lifecycle Meta-Argument Example
he lifecycle
meta-argument can be used within any resource
block like so:
resource "azurerm_resource_group" "example-rg" {
resource settings...
lifecycle {
ignore_changes = true
}
}
Controlling the flow of Terraform operations is possible using the lifecycle
meta-argument. This is useful in scenarios when you need to protect items from getting changed or destroyed.
A common scenario that requires the use of a lifecycle
meta-argument occurs when the Terraform provider itself does not handle a change correctly and so can be safely ignored, rather than the provider attempting to update an object necessarily. With the provider version updates, these “bugs” are slowly ironed out, at which point the lifecycle
meta-argument can be removed from the resource.
There are several attributes available for use with the lifecycle
meta-argument:
create_before_destroy
When Terraform determines it needs to destroy an object and recreate it, the normal behavior will create the new object after the existing one is destroyed. Using this attribute will create the new object first and then destroy the old one. This can help reduce downtime. Some objects have restrictions that the use of this setting may cause issues with, preventing objects from existing concurrently. Hence, it is important to understand any resource constraints before using this option.
lifecycle {
create_before_destroy = true
}
prevent_destroy
This lifecycle option prevents Terraform from accidentally removing critical resources. This is useful to avoid downtime when a change would result in the destruction and recreation of resource
. This block should be used only when necessary as it will make certain configuration changes impossible.
lifecycle {
prevent_destroy = true
}
Terraform will error when it attempts to destroy a resource when this is set to true:
Error: Instance cannot be destroyed
resource details...
Resource [resource_name] has lifecycle.prevent_destroy set, but the plan calls for this resource to be destroyed. To avoid this error and continue with the plan, either disable lifecycle.prevent_destroy or reduce the scope of the plan using the -target flag.
ignore_changes
The Terraform ignore_changes lifecycle option can be useful when attributes of a resource are updated outside of Terraform.
It can be used, for example, when an Azure Policy automatically applies tags. When Terraform detects the changes the Azure Policy has applied, it will ignore them and not attempt to modify the tag. Attributes of the resource that need to be ignored can be specified.
In the example below, the department tag will be ignored:
lifecycle {
ignore_changes = [
tags["department"]
]
}
If all attributes are to be ignored, then the all
keyword can be used. This means that Terraform will never update the object but will be able to create or destroy it.
lifecycle {
ignore_changes = [
all
]
}
replace_triggered_by
Lastly, this option forces the replacement of the resource when the specified resource is changed. This may be useful when the resource references another resource and needs to be replaced when this happens, for example, when a resource id changes. In the example below, when the id of VM 1 changes, the resource will also be replaced. For this option to take effect, references to the target resource must be under Terraform management (in the state file).
precondition and postcondition
You can also use custom condition checks with the lifecycle meta-argument. By adding precondition
and postcondition
blocks with a lifecycle
block, you can specify assumptions and guarantees about how resources and data sources operate.
The precondition
block is used to ensure a certain condition is met before the resource is created, and the postcondition
block is used to execute specific actions or checks after the resource is created.
The example below has a precondition set for the creation of an AWS instance that throws an error id the architecture is not equal to “x86_64”.
resource "aws_instance" "example" {
instance_type = "t2.micro"
ami = "ami-abc123"
lifecycle {
# The AMI ID must refer to an AMI that contains an operating system
# for the `x86_64` architecture.
precondition {
condition = data.aws_ami.example.architecture == "x86_64"
error_message = "The selected AMI must be for the x86_64 architecture."
}
}
}
Understanding the default behavior of the Terraform resource lifecycle can help avoid unwanted downtime when Terraform executes operations. The lifecycle of every resource can be manipulated as needed using the lifecycle meta-argument.
Note: New versions of Terraform are 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 expands 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.