Terraform Associate: lifecycle meta-arguments ignore_changes, create_before_destroy, prevent_destroy
Reorders, blocks, or hides. Know which knob you turned.
<!-- hal:authoritative:yaml -->
lifecycle does not stop every replace. It changes order, blocks destroy, or hides selected attributes from the diff. Know which knob you turned.
§I. Frame: Associate objectives for tonight
09-26 covered type constraints and optional(). 09-23 covered data sources. 09-20 covered dynamic blocks. 09-17 covered ephemeral values. 09-14 covered import and moved. 09-02 covered replace_triggered_by. Leave all of that.
Tonight is the lifecycle meta-argument: create_before_destroy, prevent_destroy, and ignore_changes. Ops declares an Artifact Registry repository whose format is immutable after create. That is the concrete surface. The drills below use terraform_data so every plan is real and credential-free.
§II. Three knobs, three jobs
| Meta-argument | Job | Exam trap |
|---|---|---|
create_before_destroy | On replace, create the new object before destroying the old | Does not avoid replace; only reorders it |
prevent_destroy | Refuse a plan that would destroy this address | Apply fails if destroy is required; fix the config or remove the flag |
ignore_changes | Drop listed attributes from the diff | Force-new attributes you ignore still force a replace when other ForceNew fields change; ignored fields drift from code |
Named technique: Lifecycle Shelf. Before you add a lifecycle block, name the failure mode: gap during replace, accidental destroy, or out-of-band mutation. Pick the matching knob. Do not stack all three by habit.
§III. create_before_destroy: order, not avoidance
variable "reader_token" {
type = string
default = "a"
}
resource "terraform_data" "reader" {
triggers_replace = var.reader_token
lifecycle {
create_before_destroy = true
}
}
resource "terraform_data" "plain" {
triggers_replace = var.reader_token
}
Apply token a, then plan with token b. Terraform 1.14.3 on the lab Mac printed two symbols in one plan:
-/+ destroy and then create replacement # terraform_data.plain
+/- create replacement and then destroy # terraform_data.reader
terraform show -json tfplan gave the action arrays:
terraform_data.plain ["delete", "create"]
terraform_data.reader ["create", "delete"]
The flag does not cancel the replace. It swaps the order. The UI symbol +/- is create-first. -/+ is destroy-first. Associate stems that ask about a gap want this pair.
§IV. prevent_destroy: the flag has to still be in the file
variable "token" {
type = string
}
resource "terraform_data" "registry" {
triggers_replace = var.token
lifecycle {
prevent_destroy = true
}
}
Apply -var=token=a. Then plan -var=token=b (a replace). Exit code 1:
Error: Instance cannot be destroyed
Resource terraform_data.registry 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 option.
terraform destroy while the block is still in the file fails the same way.
The trap. Delete the resource block entirely and the lifecycle block goes with it. A plan then destroys the object with exit 0. prevent_destroy only fires while the argument remains in configuration. On an Artifact Registry production repo, the flag stops a replace or a terraform destroy. It does not survive you deleting the resource from the module.
§V. ignore_changes: hide a field, not a replace
resource "terraform_data" "registry" {
input = var.desired_input
triggers_replace = var.force_token
lifecycle {
ignore_changes = [input]
}
}
After apply of desired_input = "v1" and force_token = "tok-a":
- Plan
-var=desired_input=v2printedNo changes. Your infrastructure matches the configuration. - Plan
-var=force_token=tok-bstill replaced. JSON actions were["delete", "create"]. The human plan hid the ignored field as# (1 unchanged attribute hidden).
Ops adjacency. On google_artifact_registry_repository, ignore_changes = [labels] can hide labels edited in the Console. Ignoring format does not turn a format change into a no-op: format is ForceNew, and you should not ignore a create-time field you intend to manage. Prefer a correct plan over a quiet one.
§VI. Five drills
- With
create_before_destroy = true, which JSON action array appears on a ForceNew change? (["create", "delete"]. The UI symbol is+/-.) prevent_destroy = truestays in the file and a ForceNew change arrives. What is the plan exit code? (1. Error: Instance cannot be destroyed.)- You delete the whole resource block, including
prevent_destroy. Does the next plan still refuse to destroy? (No. The flag left with the block.) ignore_changes = [input]and onlyinputchanges. Does the plan show an update? (No. "No changes.")- Same resource,
triggers_replacechanges andinputis ignored. Does replace still happen? (Yes. Actions["delete", "create"].)
§VII. Close
Lifecycle meta-arguments are surgical. create_before_destroy reorders (+/- versus -/+). prevent_destroy blocks only while it remains in the file. ignore_changes hides listed attributes and leaves ForceNew fields alone. Ops tonight needs the first two rarely and the third only for true out-of-band fields. Format immutability stays a replace.
Related
- Tome: Brikman 3e pp.248-255 (lifecycle tips) — grounded-in
- Bootcamp: tfpro Lab 01 (lifecycle CLI) — grounded-in; Lab 22 (create_before_destroy) — referenced
- Prior Cert: type constraints 09-26 · data sources 09-23 · replace_triggered_by 09-02