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.
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.
| Symbol | Meaning | Typical example |
|---|---|---|
| + | Create | Adding a new aws_s3_bucket |
| - | Destroy | Removing an unused aws_security_group |
| ~ | In-place update | Tag 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)
Minimum workflow and saving the output
terraform init
terraform plan -out=plan.tfplan
# Reviewers can inspect plan.tfplan with
# terraform show plan.tfplanIn 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.
| Option | Purpose | Caveats |
|---|---|---|
| -refresh=false | Skip external read | Risk of missing drift. Not suitable for final decisions |
| -refresh-only | Plan that only updates state | Applied with apply -refresh-only. Resources themselves are unchanged |
| -replace=addr | Force recreation of a specific resource | Check blast radius. Beware of effects on dependent resources |
| -destroy | Build a destroy plan | Tighten review to prevent accidental deletion |
| -target=addr | Plan with limited scope | Not recommended for regular ops. Easy to miss dependency resolution |
How refresh-only works
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.webIn 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.
| Use case | Command | Return / Output |
|---|---|---|
| Detect diff on PR | terraform plan -detailed-exitcode | 0: none / 2: changes / 1: failed |
| Pin and share the plan | terraform plan -out=plan.tfplan | Applied without approval at apply time |
| Machine-readable review | terraform show -json plan.tfplan | Extract diff details as JSON |
Typical CI pipeline
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
fiWhen 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.
| Situation | Plan symptom | Action |
|---|---|---|
| Manual changes were made | Many ~ or -/+ entries | Review the diff with terraform plan, then fix the config or import |
| Bring existing assets under management | Plan proposes creating new resources | Bring them into state with terraform import |
| Many unresolved values | Unknown values appear throughout | Revisit depends_on and your output/variable design |
Drift detection flow
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 resolvedplan 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.
| Topic | Correct answer points | Common wrong answers / confusions |
|---|---|---|
| Role of plan | Simulation of changes | Believing plan applies changes |
| -detailed-exitcode | Decide based on three exit codes 0/2/1 | Confusing 0 and 2 |
| Applying a saved plan | apply plan.tfplan needs no approval | Thinking -auto-approve is required |
| JSON output | show -json plan.tfplan | Thinking plan has -json directly |
Simplified exam decision chart
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.jsonWhen 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.
| Symptom | Cause | Fix |
|---|---|---|
| forces replacement | Immutable attribute change / by spec | Use -replace to declare intent; plan a maintenance window |
| destroy proposed during rename | State has not been migrated | Migrate automatically with a moved block |
| Failure due to lock info | Another process holds the lock | Wait it out / serialize CI jobs |
| Authorization error | Insufficient credentials | Review provider authentication |
How a moved block works during rename
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.webAssociate
問題 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?
正解: 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.
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.
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...