Terraform

Terraform ignore_changes: Smart Drift Handling for Design and Exam Prep

2026-04-19
NicheeLab Editorial Team

Terraform derives plans from the diff between configuration (HCL), state, and the real infrastructure (remote). ignore_changes partially suppresses that diff evaluation, acting as a safety valve for accepting external changes.

Overusing it, however, leads to overlooked drift. Following the official behavior, this article succinctly summarizes when to use it, when to avoid it, comparisons with alternatives, and exam pitfalls in a practical way.

ignore_changes basics: what, when, and how it 'ignores'

lifecycle.ignore_changes excludes diffs on the specified attributes from the plan. Even if those attributes are changed externally, Terraform will not revert them; only the state will be updated to the latest real-infrastructure values on the next refresh.

A key premise is that the behavior differs between creation and post-creation. On initial creation, the configured value (or provider default) is used, and ignore_changes only takes effect from the post-apply update phase. From then on, changes to that attribute in the config file produce no plan.

If another change requires a replacement (force new), the resource is recreated, and at that time the configured value (or default) is applied to the new resource even for attributes covered by ignore_changes.

  • Targets only resource attributes (cannot be applied directly to data sources or modules)
  • If you specify a map (e.g. tags), the whole attribute's diff is ignored (per-key specification is generally not possible)
  • State is updated to the real-infrastructure value on refresh, but no plan is produced — drift is accepted 'silently'
  • Exam perspective: lifecycle cannot be written on module blocks, and ignore_changes does not apply to initial creation

Minimal example: suppress the diff on a specific attribute

resource "aws_instance" "web" {
  ami           = var.ami
  instance_type = var.instance_type
  tags = {
    Name   = "web"
    Owner  = var.owner
  }
  lifecycle {
    ignore_changes = [tags]  # Do not surface in plan even if external systems append tags
  }
}

Behavior in detail: timeline at creation / update / drift

Terraform's plan is born from the process of synchronizing 'configuration → state ←→ real infrastructure.' ignore_changes acts as a filter that 'does not put on the plan' even when diffs on target attributes are visible. Keep the following flow in mind.

1) Initial apply creates with configured values. 2) During operation, target attributes are changed externally. 3) On the next plan, refresh updates state to external values, but diffs on target attributes do not appear in the plan. 4) If a replacement is triggered by changes to another attribute, the configured values (or defaults) are used at new-resource creation.

  • Drift is 'visible (reflected in state) but not corrected (plan stays silent)'
  • Note that on replacement, the configured values take effect again even under ignore_changes
  • Not appearing in plan = harder to audit. Make external changes visible through a separate channel

Position of ignore_changes within diff evaluation

   [Configuration]
          |
          v
       diff calc  ---->  [Filter: ignore_changes on attributes]
          |                           |
          |                           v
   [Current State]  <---- refresh ---- [Remote]
          |
          v
        [Plan]  (ignored attributes: no-op)

Verify the behavior: plan is silent, state changes

# Even if tags are changed externally
terraform plan
# => No changes. (no plan because it is covered by ignore_changes)

# State is updated to external values via refresh
terraform plan -refresh-only
terraform apply -refresh-only  # state only update, no resource changes

Use cases and anti-patterns

Use ignore_changes only where 'coexistence' is required. Typical cases include auto-applied tags/labels, fields touched by external controllers, and values that the provider recalculates each time. On the other hand, avoid using it in areas like security or network control where consistent management by Terraform is required.

Exams test your ability to discern which attributes 'may be overwritten externally' and which 'should be fully managed.'

  • Good fits: enterprise-wide automatic tagging, version identifiers populated by CI/CD, metadata appended by external monitoring tools
  • Avoid: security groups/firewall rules, IAM policies, encryption keys and secrets, routing, and size parameters that directly drive cost
  • Ignoring an entire map is powerful but coarse. Keep it to the minimum necessary attributes
  • Limit scope with a design that confines it to the module side (use ignore_changes only inside the module)

Example of a map you want to partially manage (tags/labels)

resource "google_compute_instance" "app" {
  name         = "app-1"
  machine_type = "e2-medium"
  # ... other required attributes ...
  labels = {
    owner = var.owner
    role  = "app"
  }
  lifecycle {
    ignore_changes = [labels] # Do not conflict with external auto-labeling
  }
}

Comparison with alternatives: when to choose ignore_changes

Your means depend on whether you aim to 'accept,' 'only detect,' or 'remediate' drift. Exams ask about the difference in purpose and scope. The following table organizes how to use each.

  • refresh-only 'only brings state up to date.' It does not touch real infrastructure
  • import 'brings existing external assets under management.' A design that accepts only part of them via ignore_changes is also possible
  • taint/-replace 'intentionally replaces.' Use it for drift remediation or resolving known defects
MethodPurposeScopeDrift detection
lifecycle.ignore_changesSuppress diffs (accept)Per attribute (within a resource)Hard to surface (does not appear in plan)
plan/apply -refresh-onlyState sync onlyEntire workspace / target resourceNo (diffs are not subject to remediation)
terraform importBring existing assets under managementPer resourceDetectable from subsequent plans
taint / -replaceForce replacementPer resource— (explicit operation)

CLI examples: state-only update / force replacement

# State-only update (real infra unchanged)
terraform plan -refresh-only
terraform apply -refresh-only

# Force replacement (comprehensive drift remediation)
terraform plan -replace=aws_instance.web
terraform apply -replace=aws_instance.web

Practical patterns: minimal-scope application and testing

'Localization' is essential for ignore_changes. Use it only inside modules and avoid controlling it via externally exposed variables; otherwise drift becomes invisible on the caller side. Also, combine refresh-only in CI to regularly grasp how much state has changed externally.

When you inject values only at initial creation and then leave the rest to the outside, test plan stability accounting for the time gap between apply and the external overwrite.

  • Use only inside modules and clearly state the reason for use in comments
  • Rather than the whole map, also consider a design that splits the target resource first (e.g. when a tag-dedicated resource exists)
  • Incorporate plan -refresh-only into CI and leave reports that visualize state changes

Minimal example inside a module (AWS: tolerate attributes appended by ELB)

resource "aws_lb_target_group" "api" {
  name     = "api-tg"
  port     = 80
  protocol = "HTTP"
  vpc_id   = var.vpc_id

  # Some health-check parts may be adjusted by the ALB side
  health_check {
    path = "/healthz"
  }

  lifecycle {
    ignore_changes = [health_check] # Allow external adjustments (keep to minimum)
  }
}

Operational and exam pitfall checklist

Accepting drift raises audit difficulty. Complement who/when/what changed using mechanisms other than Terraform (such as cloud audit logs). Exams often ask about the 'boundary' of which layer should perform the control.

Also, ignore_changes is not a silver bullet. Read-only attributes that the provider always recalculates internally do not appear in the plan in the first place, so no specification is needed.

  • lifecycle cannot be written directly under a module (resource blocks only)
  • Does not apply at initial creation. Effective from post-apply updates
  • When replacement occurs, the configured values (or defaults) are reapplied to the new resource
  • Combine with refresh-only reports and cloud audit logs when audit requirements are strict

Check state and the soundness of diffs

# Inspect a single state entry
terraform state show aws_instance.web

# Confirm config changes haven't rippled to other attributes
terraform plan -detailed-exitcode
# exit code 0: no changes / 2: changes present (watch for diffs other than ignore_changes)

Check Your Understanding

Associate / Pro

問題 1

Your organization's cloud-side policy engine automatically applies common tags to every resource. You manage some resources' tags in Terraform but want to coexist with externally added tags without removing them. Moreover, the policy is that even future changes to tag values on the Terraform configuration side should not be applied. Which response is most appropriate?

  1. Specify tags in the target resource's lifecycle.ignore_changes
  2. Periodically running terraform plan -refresh-only is enough; no configuration is needed
  3. Replace tags with a data source and make them reference-only
  4. Use terraform taint to recreate the resource each time to take in external tags

正解: A

lifecycle.ignore_changes = [tags] is ideal for requirements that exclude externally added/changed tags from the plan and do not apply future config-side changes. refresh-only only updates state and does not suppress diffs, and switching to a data source abandons resource management. taint forces replacement, which is the opposite of the requirement.

Frequently Asked Questions

Does ignore_changes apply at initial creation?

No. On initial creation, the configured value (or the provider default) is used. ignore_changes only suppresses diffs starting from the post-apply update phase.

Can I specify ignore_changes from the module caller side?

No. lifecycle is valid only inside a resource block. To encapsulate it within a module, write lifecycle on the resources inside the module.

How do I detect drift on attributes ignored by ignore_changes?

Since it never shows up in plan, use terraform plan -refresh-only to surface state diffs, or combine with cloud audit logs and policy-engine reports. If audit requirements are strict, keep the ignored scope as small as possible.

Check what you learned with practice questions

Practice with certification-focused question sets

無料で問題を解いてみる
Author

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.


Related articles
Terraform

HCL Syntax: Terraform's Configuration Language (2026)

HCL2 fundamentals for Terraform — blocks, attributes, expres...

Terraform

Terraform Authoring & Operations Pro: Complete Guide (2026)

Tactics for the Terraform Pro exam — module authoring, works...

Terraform

Terraform Providers: Plugin Management Fundamentals (2026)

Provider mechanics — required_providers, versions, mirrors, ...

Terraform

Terraform Resource Blocks: Declarative Infra Units (2026)

Resource block fundamentals — addresses, references, common ...

Terraform

Terraform Data Sources: Read-Only External Data (2026)

Data source basics — declaration, refresh behavior, dependen...

Browse all Terraform articles (102)
© 2026 NicheeLab All rights reserved.