Resource Lifecycle and Replacement
One of the most important moments in a Terraform plan is the moment you see a resource marked for replacement. Terraform is telling you that the existing object cannot simply be updated to match the new configuration, so it needs to create a new object and remove the old one.
Understanding why that happens is much more useful than memorizing lifecycle syntax.
Update or Replacement?
When you change configuration, Terraform asks the provider what can be changed on the existing object.
Conceptually, there are two possibilities:
Configuration change
β
βΌ
Can existing object be updated?
/ \
yes no
β β
βΌ βΌ
Update Replace
in placeAn in-place update might look conceptually like:
server A
β
β modify
βΌ
server AA replacement looks like:
server A
β
β destroy/create
βΌ
server BThe exact behavior depends on the resource and provider.
For some attributes, the provider API supports modification. For others, the underlying infrastructure system requires a new object.
Why Would a Resource Need Replacement?
Some infrastructure properties are effectively immutable.
Imagine a resource where changing an argument means the provider cannot modify the existing object:
resource "some_provider_server" "web" {
name = "web"
size = "small"
}If changing size requires a new server, a plan might indicate something conceptually like:
-/+ some_provider_server.web
size: "small" -> "large"The -/+ marker indicates replacement: destroy and create.
Terraform is not arbitrarily deciding to recreate the server. It is following the resource’s behavior as defined by the provider.
Note
Whether a change is in-place or replacement is resource-specific. Always read the plan rather than assuming that changing a particular argument will behave one way across all providers.
Read the Plan Carefully
A plan can contain several different kinds of changes.
Conceptually:
+ create
~ update in place
- destroy
-/+ replaceThe exact plan formatting can contain additional details, but the distinction is important.
For a normal update:
~ resourceTerraform expects to modify the existing object.
For replacement:
-/+ resourceTerraform expects the old object to be replaced.
Replacement may cause downtime, change addresses, rotate identifiers, or cause dependent resources to change as well.
The lifecycle Block
Terraform provides a lifecycle block for controlling certain aspects of resource lifecycle behavior.
For example:
resource "some_provider_server" "web" {
name = "web"
lifecycle {
create_before_destroy = true
}
}The important lifecycle settings include:
create_before_destroyprevent_destroyignore_changes
They are not general-purpose “make Terraform safer” switches. Each changes Terraform’s behavior and introduces trade-offs.
create_before_destroy
Normally, replacement can conceptually happen like this:
Old resource
β
βΌ
Destroy
β
βΌ
Create newThat creates a period where the old resource is gone.
With:
lifecycle {
create_before_destroy = true
}Terraform attempts the opposite ordering:
Old resource
β
ββββββββββββββββ
β βΌ
β Create new
β β
β βΌ
βββββββββββ Destroy oldThis can reduce downtime.
However, it only works when the infrastructure system allows both objects to exist simultaneously.
If a resource requires a globally unique name, for example, Terraform may not be able to create the replacement before destroying the original.
Warning
create_before_destroy does not guarantee zero downtime. The provider and infrastructure design must allow both old and new objects to coexist.
It can also temporarily increase resource usage because two objects exist during replacement.
prevent_destroy
You can protect a resource with:
lifecycle {
prevent_destroy = true
}Terraform will reject operations that would destroy that resource through normal Terraform actions.
This can be useful for resources where accidental destruction would be particularly serious.
For example:
resource "some_provider_database" "main" {
name = "production"
lifecycle {
prevent_destroy = true
}
}The purpose is not to make the resource impossible to delete forever. It is to force an explicit decision rather than allowing an ordinary plan to destroy it.
Note
prevent_destroy can also make legitimate changes fail when those changes require replacement. It is a guardrail, not a substitute for backups and operational discipline.
ignore_changes
Sometimes an external system is intentionally allowed to modify part of a resource.
You can tell Terraform to ignore changes to selected attributes:
resource "some_provider_server" "web" {
name = "web"
lifecycle {
ignore_changes = [
metadata
]
}
}Terraform will avoid planning changes solely because the ignored attribute differs from the configuration.
This can be appropriate when another system intentionally owns that attribute.
But it can also hide drift.
Imagine your configuration declares:
size = "small"and you add:
ignore_changes = [size]Now an administrator can change the size outside Terraform and Terraform will intentionally stop trying to reconcile that difference.
That may be correctβor it may hide a real configuration problem.
Warning
ignore_changes should answer a specific ownership question: “Who is responsible for this attribute?” If the answer is still Terraform, ignoring it is usually the wrong solution.
Lifecycle Settings Change the Rules
It is useful to think of lifecycle settings as policy decisions:
Resource change
β
βΌ
Terraform plan
β
βββββββββββ΄ββββββββββ
βΌ βΌ
Normal lifecycle Custom lifecycle
β
ββββββββββββββΌβββββββββββββ
βΌ βΌ βΌ
create first block destroy ignoreThe settings exist because infrastructure operations sometimes have requirements that cannot be represented by the default lifecycle.
But every customization makes Terraform’s behavior less automatic.
A Small Replacement Exercise
Create a deliberately simple configuration using a provider/resource where one chosen argument is documented to require replacement.
For example:
replacement-example/
βββ main.tfUse the provider’s documentation to choose a resource whose immutable attribute makes the behavior obvious.
Your workflow is:
terraform init
terraform plan
terraform applyThen change the replacement-triggering argument and run:
terraform planRead the plan before applying it.
Your goal is not merely to see the replacement marker. Ask:
- Which attribute changed?
- Why can the provider not update it in place?
- Will the replacement cause downtime?
- Does the resource have a stable identity?
- Do dependent resources also need changes?
Finally, clean up:
terraform destroyThe exact resource used for this exercise depends on the provider you are practicing with, so do not copy a made-up provider resource from a generic tutorial into a real environment.
Lifecycle Is About Trade-Offs
A common beginner mistake is to discover create_before_destroy, prevent_destroy, and ignore_changes and assume they should be added everywhere.
They should not.
Think about the trade-offs:
| Setting | Useful For | Main Trade-Off |
|---|---|---|
create_before_destroy | Reducing replacement downtime | Requires old and new objects to coexist |
prevent_destroy | Guarding important resources | Can block legitimate replacement |
ignore_changes | Attributes intentionally managed elsewhere | Can hide drift |
The right question is not:
“Which lifecycle option makes Terraform safer?”
It is:
“What lifecycle behavior does this resource actually require, and who owns each change?”
That mindset prevents lifecycle blocks from becoming a collection of unexplained exceptions.
The next chapter moves from lifecycle behavior into Terraform’s expression language, where functions, collection transformations, and dynamic blocks let you build more flexible configurations.