Terraform

Reading terraform plan Diffs Accurately (Associate Exam + Real-World Ops)

2026-04-19
NicheeLab Editorial Team

terraform plan visualizes the upcoming changes derived from state, real resources, and your current configuration. A common misconception: plan is purely a simulation — infrastructure does not change until you apply.

This article walks through how to read diff symbols correctly, when to use each major option, automatic checks in CI/CD, and drift detection tips — from both the Associate exam and real-world ops angles.

Plan Basics and How to Read Diff Symbols

terraform plan emits an execution plan derived from your HCL configuration, the current state, and a read of the real environment (refresh by default). The output shows what will change, in what order, and why — making it the core tool for review and risk reduction.

In the diff output, + means create, - means destroy, ~ means in-place update, and -/+ or +/- means replacement (destroy then recreate). ~ also appears at the attribute level, but -/+ at the resource header line means destroy and recreate is required.

  • plan makes no real changes. Application happens via terraform apply.
  • By default it refreshes (re-reads real resources). Control this with the -refresh flag.
  • Output order follows dependencies. Destroy then create is preferred for safety.
  • A saved plan file (-out) is only valid for the same workspace and configuration.
SymbolMeaningTypical example
+CreateAdding a new aws_s3_bucket
-DestroyRemoving an unused aws_security_group
~In-place updateTag value change, retention_period update
-/+ or +/-Replacement (destroy then create)Immutable attribute change, instance type change requiring recreation

How plan fits in (config → plan → apply)

ReadRead/writeHCL (config)desired stateterraform planSimulation outputReal resources (API)Read current stateState file (state)Read/write (refresh)plan is a simulation that computes the diff from HCL, state, and real resources. Only apply makes real changes

Minimum workflow and saving the output

terraform init
terraform plan -out=plan.tfplan
# Reviewers can inspect plan.tfplan with
# terraform show plan.tfplan

Key Options for Sharper Diffs

In operations you mix and match options for drift detection and scoped impact analysis. -refresh=false skips the external re-read, which helps in unstable network conditions for a tentative check — but raises risk because it does not reflect the latest real state.

-refresh-only creates a plan that only updates state to match the real resources, without touching anything in the cloud. You apply it with terraform apply -refresh-only — effectively a state sync (drift-to-zero) operation.

  • -replace=<address> creates an explicit replacement plan (the modern replacement for taint).
  • -destroy creates a destroy-only plan (useful for safely confirming final teardown).
  • -var-file and -var pin the input values used in the plan, improving reproducibility.
OptionPurposeCaveats
-refresh=falseSkip external readRisk of missing drift. Not suitable for final decisions
-refresh-onlyPlan that only updates stateApplied with apply -refresh-only. Resources themselves are unchanged
-replace=addrForce recreation of a specific resourceCheck blast radius. Beware of effects on dependent resources
-destroyBuild a destroy planTighten review to prevent accidental deletion
-target=addrPlan with limited scopeNot recommended for regular ops. Easy to miss dependency resolution

How refresh-only works

Real resourcesRead current state via APIReadNo changes appliedstateSync update onlyrefresh-only only syncs state from real resources — resources themselves are not changed

Typical command combinations

# Strict drift check
terraform plan

# Tentative diff during network issues (avoid for final decisions)
terraform plan -refresh=false

# Plan that only updates state
terraform plan -refresh-only

# Replace a specific resource
terraform plan -replace=aws_instance.web

Automated Checks and Plan Sharing in CI/CD

In CI you use -detailed-exitcode to detect changes programmatically. 0 means no diff, 2 means changes detected, 1 means error. Posting plan output as a PR comment is a common pattern.

To improve reproducibility, generate a binary plan file with -out and convert it to machine-readable JSON with terraform show -json. Plan files depend on the Terraform version, backend, and workspace, so avoid carrying them across environments.

  • Separating plan → review → approve → apply enforces least privilege.
  • Applying a saved plan skips the approval prompt (well-suited to unattended apply).
  • JSON output makes diff visualization dashboards possible.
Use caseCommandReturn / Output
Detect diff on PRterraform plan -detailed-exitcode0: none / 2: changes / 1: failed
Pin and share the planterraform plan -out=plan.tfplanApplied without approval at apply time
Machine-readable reviewterraform show -json plan.tfplanExtract diff details as JSON

Typical CI pipeline

CommitPR pushterraform plan -detailed-exitcode0: no changes / 2: changes / 1: failedReview / ApproveOnly when exit 2terraform apply plan.tfplanApply the saved plan unattendedexit 2 (changes) goes through review/approval then apply; exit 0 ends the run

Bash example: fail only when there are changes

set -euo pipefail
terraform init
if terraform plan -detailed-exitcode -out=plan.tfplan; then
  echo "No changes"
else
  code=$?
  if [ "$code" -eq 2 ]; then
    echo "Changes detected" >&2
    terraform show -json plan.tfplan > plan.json
    exit 2
  else
    echo "Plan failed" >&2
    exit $code
  fi
fi

Real-World Patterns and Pitfalls (Drift, Import, Unknown Values)

When existing resources have been changed manually, plan surfaces the drift as ~ or -/+. To take ownership of existing assets safely, bind them to state via terraform import first.

Data sources and values that are only resolved at apply time may appear as unknown in plan. Read carefully to check whether an unknown triggers a replacement requirement.

  • Schedule regular plan runs for ongoing drift monitoring.
  • Before importing, make sure the config matches the existing resource (name, tags, key attributes).
  • If there are many unknowns, revisit your dependencies and lifecycle settings.
SituationPlan symptomAction
Manual changes were madeMany ~ or -/+ entriesReview the diff with terraform plan, then fix the config or import
Bring existing assets under managementPlan proposes creating new resourcesBring them into state with terraform import
Many unresolved valuesUnknown values appear throughoutRevisit depends_on and your output/variable design

Drift detection flow

Real resources (current)Including manual changesstateUpdated via refreshplanVisualize the diffapplySync state and real resourcesrefresh updates state → plan visualizes the diff → apply syncs them

Importing and producing a minimal diff

# Bring an existing S3 bucket into state
# Match the HCL to the existing settings beforehand
terraform import aws_s3_bucket.logs my-existing-logs-bucket
terraform plan  # Confirm the drift is resolved

Topics Frequently Tested on the Associate Exam

plan is a proposal, and only apply actually performs changes. The meaning of -detailed-exitcode, and the fact that applying a saved plan from -out runs without approval, come up often.

Expect questions about intent: -target is not recommended for regular ops, -destroy builds a destroy plan, and -refresh-only builds a plan whose purpose is state sync.

  • Exit codes: 0 = no changes, 2 = changes, 1 = failed.
  • Applying a saved plan does not prompt (it can be unattended).
  • Get plan JSON via terraform show -json (plan itself does not emit JSON directly).
TopicCorrect answer pointsCommon wrong answers / confusions
Role of planSimulation of changesBelieving plan applies changes
-detailed-exitcodeDecide based on three exit codes 0/2/1Confusing 0 and 2
Applying a saved planapply plan.tfplan needs no approvalThinking -auto-approve is required
JSON outputshow -json plan.tfplanThinking plan has -json directly

Simplified exam decision chart

YesNoYesNoDetect diff programmatically?Automated check in CIplan -detailed-exitcodeDecide on exit 0/2/1Regular planInteractive reviewSave then apply?Whether unattended apply is neededplan -out && apply plan.tfplanNo recompute, no approvalapplyRecomputesDecision chart for picking plan options by intent

Summary of commonly tested commands

# Just check whether there are changes
terraform plan -detailed-exitcode

# Destroy plan
terraform plan -destroy

# Save then apply
terraform plan -out=plan.tfplan
terraform apply plan.tfplan

# Review as JSON
terraform show -json plan.tfplan > plan.json

Troubleshooting: Replacement, Move, Lock, and Permissions

When plan shows 'forces replacement', check whether an immutable attribute changed. If recreation is genuinely required by the spec, indicate intent explicitly with -replace for safety.

For resource renames or module reorganization, use a moved block to migrate state automatically without destroy/create. Backend lock contention and authorization errors are caught before plan runs — start by checking credentials and lock state.

  • moved blocks enable safe renames — preferred over state mv.
  • Wait for backend locks to release. Force-unlock is a last resort.
  • Insufficient provider permissions tend to fail during the refresh phase of plan.
SymptomCauseFix
forces replacementImmutable attribute change / by specUse -replace to declare intent; plan a maintenance window
destroy proposed during renameState has not been migratedMigrate automatically with a moved block
Failure due to lock infoAnother process holds the lockWait it out / serialize CI jobs
Authorization errorInsufficient credentialsReview provider authentication

How a moved block works during rename

plan shows movedaws_instance.old_namebefore (old address)moved { from, to }Declarative state migrationaws_instance.new_nameafter (new address)Terraform stateOnly binding updates; resource itself staysmoved blocks redirect state binding to avoid destroy/recreate

moved blocks and explicit replacement

# main.tf (Terraform 1.x)
moved {
  from = aws_instance.old_name
  to   = aws_instance.new_name
}

# Declare unavoidable recreation
# terraform plan -replace=aws_instance.web

Check with a Question

Associate

問題 1

You run terraform plan in CI and want to fail the PR only when there are changes. Which is the most appropriate minimal implementation?

  1. Use terraform plan -detailed-exitcode and fail on exit code 2
  2. grep terraform plan's stdout and fail if + or ~ appears
  3. Run terraform apply plan.tfplan after terraform plan -out, and fail when changes occur
  4. Use terraform plan -refresh=false so the output is identical each time, then compare

正解: A

The officially supported method is programmatic detection via -detailed-exitcode. Exit code 0 means no changes, 2 means changes, 1 means failure. Grepping output is fragile and not recommended. apply executes the plan and should not be used for failure detection in CI. -refresh=false compromises diff accuracy.

Frequently Asked Questions

Does terraform plan really change nothing?

Yes. plan only computes and displays an execution plan. Real resources only change when you run terraform apply (or destroy).

Can a saved plan file (plan.tfplan) be reused in a different environment?

Not recommended. A plan file depends on the Terraform version, backend, workspace, and variables. It is meant to be applied promptly in the same environment where it was generated.

I want the diff as JSON — does terraform plan have a -json flag?

plan itself has no -json flag. Save the plan with terraform plan -out, then get JSON via terraform show -json plan.tfplan.

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.