Terraform / Modules and state-aware lifecycle / Lesson 10
Lifecycle and replacement controls
Understand what lifecycle settings can constrain and why they do not provide availability, data recovery, or unconditional deletion protection.
On this page
Lesson goals
- Explain replacement triggers, ordering, prevent_destroy, and ignore_changes.
- Review provider constraints, quotas, uniqueness, and shared ownership.
Replacement semantics come from a resource contract
A provider can treat an argument change as an in-place update, replacement, or unsupported transition. Lifecycle meta-arguments influence supported Terraform behavior around those actions but do not rewrite the remote API’s rules. Before changing lifecycle settings, identify the actual resource identity, data, dependencies, naming constraints, and downtime implications. The same create-before-destroy idea can be straightforward for one disposable object and impossible for another with a unique fixed name or insufficient quota. A lifecycle block is therefore a reviewed design choice, not a universal reliability switch.
Separate changed data from forced replacement intent
terraform_data provides an explicit teaching distinction: input can hold data, while triggers_replace records values whose change requests replacement of that instance. The fixture below uses a synthetic revision string and create_before_destroy ordering. No external service is represented and no transition is executed. A real resource must still support simultaneous old and new instances when that ordering is required. The example does not prove zero downtime, successful startup, or safe transfer of persistent data.
terraform {
required_version = ">= 1.16.0, < 1.17.0"
}
variable "revision" {
type = string
default = "release-a"
}
resource "terraform_data" "release" {
input = { revision = var.revision }
triggers_replace = [var.revision]
lifecycle {
create_before_destroy = true
}
}
Changing revision changes triggers_replace for this built-in resource. That is a specific documented contract, not a rule for all provider input fields. The lifecycle setting requests supported ordering and does not perform an application readiness check. No state is created or changed.
Protection only applies within its documented scope
prevent_destroy rejects plans that would destroy the protected instance while the relevant configuration is present. Removing the resource declaration also removes that configuration-based protection, so it is not a substitute for external permissions, backups, or review. ignore_changes can support deliberate shared management of selected attributes, but can also hide important drift. It does not grant another actor ownership safely by itself or prevent every deletion. Document which actor owns each ignored attribute and how changes are observed and reconciled.
Model one explicit trigger rule offline
The Python example compares authored records under a declared teaching rule. It distinguishes changed input from changed replacement trigger. That narrow model cannot infer replacement for arbitrary providers or reproduce Terraform’s full plan logic, dependency graph, and state history. Keep the result tied to this stated contract and verify real actions through the approved workflow. A tidy rule name such as update should never be interpreted as proof that a real operation is reversible or has no service impact.
def action(old, new):
if old['trigger'] != new['trigger']:
return 'replace'
return 'update' if old['input'] != new['input'] else 'no-op'
old = {'input': 'blue', 'trigger': 'v1'}
print('input_change=' + action(old, {'input': 'green', 'trigger': 'v1'}))
print('trigger_change=' + action(old, {'input': 'blue', 'trigger': 'v2'}))
input_change=update
trigger_change=replace
The declared rule returns update for input-only change and replace for trigger change. No provider, plan, object, or state is inspected. Actual availability and data consequences remain outside this bounded comparison.
Lifecycle review includes dependencies and failure recovery
Ordering changes can propagate through dependencies and affect the complete transition, not merely one isolated block. Check quotas, uniqueness, traffic handoff, readiness, data migration, and how a partial failure will be reconciled. Avoid broad ignore_changes=all as a shortcut to a quiet plan unless the resulting ownership model is intentionally designed and reviewed. Preserve state and logs when a replacement fails. Neither reverting source text nor disabling protection automatically restores a previous real-world state or makes an interrupted transition safe to repeat.
Common errors and fixes
Instance cannot be destroyed.
- Why it happens
- A proposed action conflicts with a configured prevent_destroy requirement.
- How to fix it
- Review the actual intended transition and authorization instead of removing protection blindly.
- Prevent it next time
- Treat protection exceptions as explicit reviewed lifecycle decisions.
create_before_destroy fails under a unique name or quota.
- Why it happens
- The target system cannot host the required old and new objects simultaneously.
- How to fix it
- Choose a supported naming, capacity, and handoff strategy for that resource.
- Prevent it next time
- Rehearse replacement constraints before approving availability claims.
Important drift disappears from review.
- Why it happens
- ignore_changes was applied beyond a clearly assigned shared-ownership scope.
- How to fix it
- Narrow ignored attributes and define observation and reconciliation responsibilities.
- Prevent it next time
- Review ignored fields and owners as an ongoing contract.
Check your understanding
1. Does create_before_destroy guarantee an uninterrupted application?
Show worked solution
No. It concerns supported resource ordering. Application readiness, traffic routing, name uniqueness, quota, dependency behavior, and data handoff are separate requirements. Validate the complete transition and failure path rather than translating a lifecycle flag into an availability promise.
2. Why is deleting a protected resource block not a safe backup strategy?
Show worked solution
The configuration-based prevent_destroy setting disappears with the declaration, while a backup requires retained recoverable data and a tested restore. They protect different boundaries. Preserve ownership evidence and follow the approved lifecycle and recovery process rather than relying on the former flag after it has been removed.
Key takeaways
- Lifecycle settings constrain particular transitions; they do not replace resource semantics.
- Protect identity, data, availability, and recovery as separate requirements.