Added in Terraform 1.2, lifecycle.replace_triggered_by is a meta-argument that forces a resource to be explicitly replaced (recreated) when another resource or module output changes. Normally updates and replacements are decided by attribute diffs, but this argument is valuable for settings that are only read at creation time or whenever you need to lock in the safety of an external dependency.
This article covers when to use replace_triggered_by correctly and where it goes wrong, how it compares to other features, the angles the exam tends to probe, and the verification patterns you'll use in practice. We assume you're already familiar with the Terraform language and the basics of the lifecycle block.
When the value of the references you list (a whole resource, a specific attribute, or a module output) changes, replace_triggered_by makes Terraform plan a replacement instead of an update. That lets you intentionally recreate a resource even when the provider supports in-place updates.
Typical examples are "components that only read external settings on first creation" and "resources like certificates or images that must definitely be swapped out when the underlying hash changes." Pointing at a whole resource means "any diff in that resource triggers replacement," while pointing at an attribute means "replacement runs only when that attribute's value changes."
| Feature | Primary Purpose | Trigger / How to Specify | Effect on Plan |
|---|---|---|---|
| lifecycle.replace_triggered_by | Force replacement on a specific external change | Resource / attribute / module-output reference | Any change in the referenced value plans "must be replaced" |
| depends_on | Control create/destroy order | Resource / module reference | Does not affect diffs; only modifies the dependency graph |
| null_resource.triggers | Recreate a null_resource when a value changes | Any value (string or map) | Recreates the null_resource only; does not apply to other resources |
| lifecycle.ignore_changes | Ignore diffs on specific attributes | Attribute names (set or list) | Suppresses diffs so no update/replace is planned (other factors can still trigger replacement) |
The dependency-and-replacement flow of replace_triggered_by
Basic pattern: force replacement from a module-output or attribute change
# Module (example): outputs the certificate bundle hash
module "ca" {
source = "./modules/ca" # any implementation
# ...
}
# Example 1: always recreate the Secret on module-output change
resource "kubernetes_secret" "api" {
metadata {
name = "api-secret"
namespace = "default"
}
data = {
# Real contents may be referenced via other arguments
ca_hash = module.ca.bundle_hash
}
lifecycle {
replace_triggered_by = [
module.ca.bundle_hash
]
}
}
# Example 2: replace on a resource-attribute change
resource "aws_lb_listener" "https" {
load_balancer_arn = aws_lb.main.arn
port = 443
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-2016-08"
certificate_arn = aws_acm_certificate.cert.arn
default_action {
type = "fixed-response"
fixed_response {
content_type = "text/plain"
status_code = 200
message_body = "ok"
}
}
lifecycle {
replace_triggered_by = [
aws_acm_certificate.cert.arn # replace listener when the certificate is rotated
]
}
}After plan computes diffs, replace_triggered_by checks whether the value of each trigger reference has changed since the previous state, and flags the target resource for replacement if it has. So if refresh doesn't update the target, the change isn't seen.
Referencing a whole resource means "any planned change in that instance trips the trigger," while referencing an attribute restricts it to "a change in that specific attribute's value." Under count/for_each, evaluation happens per instance, so only the instances whose trigger changed are replaced.
Minimizing evaluation when combined with for_each
# Each VM is replaced only when the base image digest changes
locals {
vms = {
web = { size = "small" }
api = { size = "medium" }
}
}
module "base_image" {
source = "./modules/base_image"
# output: digest
}
resource "azurerm_linux_virtual_machine" "vm" {
for_each = local.vms
name = "${each.key}-vm"
resource_group_name = azurerm_resource_group.rg.name
size = each.value.size
# ... other required arguments
lifecycle {
replace_triggered_by = [
module.base_image.digest
]
}
}For certificates and images where the contents change but the reference name stays the same, you often want to err on the side of safety and force replacement, even if the provider would otherwise update in place. Components that pull external resources just once during initialization are especially good candidates for recreation.
Likewise, when you need to force a Deployment to roll on container image digest changes but the provider's diff detection occasionally misses it, replace_triggered_by gives you a design that swaps the resource reliably.
Example: replace the Deployment when the container image digest changes
module "artifact" {
source = "./modules/artifact"
# output: image_digest
}
resource "kubernetes_deployment" "app" {
metadata { name = "app" }
spec {
replicas = 3
selector { match_labels = { app = "app" } }
template {
metadata { labels = { app = "app" } }
spec {
container {
name = "app"
image = "example.com/app@${module.artifact.image_digest}"
}
}
}
}
lifecycle {
replace_triggered_by = [
module.artifact.image_digest
]
}
}Too coarse a target makes replace_triggered_by fire almost every run and balloons the plan. Too narrow a target won't replace when you expect. Tuning the reference granularity is everything. References to time-based or random values, which change "without you intending them to," cause unwanted replacement loops.
When you combine it with ignore_changes, replacement still runs whenever replace_triggered_by fires, even if ignore_changes is hiding the diff. The "intent to replace" takes precedence over diff suppression — keep that in mind.
Anti-pattern: referencing a value that changes on a schedule
# Bad: time_rotating changes its value on a fixed cadence, so replacement runs periodically
resource "time_rotating" "weekly" {
rotation_days = 7
}
resource "some_resource" "target" {
# ...
lifecycle {
replace_triggered_by = [
time_rotating.weekly.id # the value rotates and the resource is replaced every time
]
}
}Historically, recreations tied to external value changes were often emulated with null_resource's triggers, with other resources depending on it to force a reapply. That approach buries the intent indirectly and makes the dependency graph harder to read.
Migrating to replace_triggered_by expresses "which value change replaces which resource" directly in code, and plan output gets easier to read. Pair it with depends_on if you also need to control ordering.
Migration example: from null_resource triggers to lifecycle.replace_triggered_by
# Old: using null_resource as a pseudo-trigger
resource "null_resource" "trigger" {
triggers = {
ca_hash = module.ca.bundle_hash
}
}
# Old: the real resource depends on the trigger
resource "kubernetes_secret" "api" {
# ...
depends_on = [null_resource.trigger]
}
# New: consolidate the trigger onto the real resource
resource "kubernetes_secret" "api" {
# ...
lifecycle {
replace_triggered_by = [module.ca.bundle_hash]
}
# If you also need ordering, add depends_on
# depends_on = [module.ca]
}The exam often probes the differences between replace_triggered_by and depends_on / ignore_changes, the granularity of resource vs. attribute references, and the per-instance evaluation under count/for_each. The comparison with null_resource.triggers also shows up frequently.
For real-world validation, build a small sample, exercise both "reference changed" and "reference unchanged" paths, and confirm that plan output shows "must be replaced." Checking behavior in refresh-only mode is also useful.
Local verification commands
# Initial run
terraform init
terraform plan -out=tfplan
terraform apply tfplan
# Update the referenced value (e.g., a commit or version bump that changes a module output)
# Confirm the replacement is planned
terraform plan | grep -A2 "must be replaced" || true
# Confirm there are no remaining diffs
terraform plan -refresh-onlyPro
問題 1
Module module.ca outputs a certificate bundle hash (bundle_hash), and kubernetes_secret.api must always be recreated when that value changes. Which Terraform configuration is correct?
正解: A
lifecycle.replace_triggered_by is the right answer because it expresses the intent to replace directly. depends_on only controls ordering and does not force replacement. The null_resource + triggers combo is indirect and redundant. ignore_changes merely hides diffs and does not force replacement.
What can be referenced in replace_triggered_by? Can variables or data sources be used?
Targets are references whose value change Terraform can detect at plan time: resource references, resource attribute references, and module outputs. A purely static value like a plain variable is hard to treat as an "external change" and also blurs the intent of the design. In practice, restrict yourself to resource, attribute, and module-output references.
When combined with ignore_changes, which one wins?
ignore_changes suppresses diffs on specific attributes, but if replace_triggered_by fires, the resource is replaced. Think of it as the "intent to replace" overriding the diff suppression.
Does a drift in the referenced target also trigger replacement?
If plan-time refresh updates the state with the target's current value and Terraform recognizes the value as changed, a replacement is planned. The converse is true: if plan cannot detect a change in the target, no replacement happens. Regular plan/refresh and monitoring are essential.
Practice with certification-focused question sets
無料で問題を解いてみるNicheeLab Editorial Team
NicheeLab editorial team focused on data engineering and cloud certification learning. Content is structured around practical study needs and official exam domains.
HCL Syntax: Terraform's Configuration Language (2026)
HCL2 fundamentals for Terraform — blocks, attributes, expres...
Terraform Authoring & Operations Pro: Complete Guide (2026)
Tactics for the Terraform Pro exam — module authoring, works...
Terraform Providers: Plugin Management Fundamentals (2026)
Provider mechanics — required_providers, versions, mirrors, ...
Terraform Resource Blocks: Declarative Infra Units (2026)
Resource block fundamentals — addresses, references, common ...
Terraform Data Sources: Read-Only External Data (2026)
Data source basics — declaration, refresh behavior, dependen...