Terraform replace a resource is -replace on the CLI (terraform apply -replace=aws_instance.web) or lifecycle { replace_triggered_by = [...] } in the config. The CLI flag is for one-off operations (a taint that does not need a code change). The config block is for reproducibility - the replace is in the code, so the next terraform apply does it again. The team that uses -replace for a one-off and the lifecycle block for repeat behavior is correct.
Table of contents
- The CLI flag: terraform apply -replace
- The config block: lifecycle.replace_triggered_by
- lifecycle.create_before_destroy
- lifecycle.prevent_destroy
- The full lifecycle block
- How this fits the rest of the stack
- FAQ
The CLI flag: terraform apply -replace
terraform apply -replace="aws_instance.web"
Or in older versions: terraform taint aws_instance.web followed by terraform apply. The -replace flag (Terraform 0.15.2+) is the modern way; taint is deprecated.
What it does: marks the resource for replacement, so the next apply destroys and recreates it. The team’s machine state in the resource’s attributes is what gets recreated - the new resource has a new ID, new IP, new everything.
The team that uses -replace for a one-off rotation (a certificate, a key, an AMI) is correct. The team that uses it for every change is missing the config-driven pattern.
The config block: lifecycle.replace_triggered_by
In the resource:
resource "aws_instance" "web" {
ami = "ami-12345678"
instance_type = "t3.micro"
# ...
lifecycle {
replace_triggered_by = [aws_security_group.web.id]
}
}
When the security group’s id changes, the instance is replaced. The team that uses this for cross-resource dependencies - “when the AMI changes, the instance is replaced” - has the right pattern.
Common uses: replace an instance when the AMI changes, replace a load balancer when the cert changes, replace a database when the parameter group changes. The team’s intent is encoded in the config, not in a manual command.
lifecycle.create_before_destroy
The default for replace is destroy-then-create - the old resource is gone before the new one is up. For zero-downtime:
lifecycle {
create_before_destroy = true
}
This creates the new resource first, then destroys the old. The team that uses this for a load balancer or a database cluster (where a brief outage is unacceptable) has the right pattern. The team that uses this for a one-off dev environment does not need it.
Some resources do not support create_before_destroy (most AWS data resources, RDS, etc.). Check the provider docs for the specific resource type.
lifecycle.prevent_destroy
For resources that should never be destroyed by Terraform:
lifecycle {
prevent_destroy = true
}
terraform destroy refuses to remove the resource. The team that uses this for production databases, S3 buckets with important data, or IAM roles with critical permissions has the right protection.
The full lifecycle block
lifecycle {
create_before_destroy = true
prevent_destroy = false # default
ignore_changes = [tags, ami]
replace_triggered_by = [aws_iam_role.app.arn]
}
ignore_changes tells Terraform to not try to update the listed attributes when they drift. The team that uses this for tags (so manual tag changes do not get clobbered) and ami (when the AMI is managed outside Terraform) has the right pattern.
The team that over-uses ignore_changes is hiding drift. The right pattern: ignore only what is legitimately managed elsewhere, and let Terraform detect drift on the rest.
FAQ
What is the difference between -replace and taint?
taint (deprecated in Terraform 0.15.2, removed in 1.0+) marks a resource for replacement but the syntax is terraform taint <address>. -replace is the modern CLI flag: terraform apply -replace=<address>. The team that uses taint in a recent Terraform version gets a deprecation warning.
Does replace_triggered_by work for module outputs?
Yes - reference a module output: replace_triggered_by = [module.network.vpc_id]. The team that has a module that emits a version number and wants the dependent resources replaced on version change uses this pattern.
What happens to data when a resource is replaced?
For stateful resources (RDS, S3 with versioning off, EBS with snapshot off), the data is lost. The team that uses -replace on a database takes a backup first. The team that uses create_before_destroy on a stateful resource still loses the data on the old resource - the create_before_destroy is for availability, not durability.
Can I prevent Terraform from replacing a resource?
Yes - lifecycle { prevent_destroy = true }. The team that uses this for a critical resource gets an error on terraform destroy instead of silent data loss.
What is the difference between create_before_destroy and -replace?
-replace is a CLI flag that triggers replacement on the next apply. create_before_destroy is a config setting that changes the order of destroy-and-create. They can be combined: -replace triggers the replacement, create_before_destroy changes how the replacement happens.
How this fits the rest of the stack
For a sense of what the full project costs before it commits, the RunxBuild hosting calculator shows the line items together. The API, the database, the storage, the worker, the bandwidth - each one is a separate number, and the team’s mental model for the platform is the sum of those numbers.
Useful related references: