The plan is the review artifact, not a formality on the way to apply. Almost every self-inflicted Terraform incident is visible in a plan somebody scrolled past.
Topic 1: The Four Symbols
| Symbol | Meaning | Risk |
|---|---|---|
+ | create | Low |
~ | update in place | Low — the resource survives |
-/+ | destroy, then create a replacement | High — a gap where nothing exists |
- | destroy | High — data loss possible |
Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement
# aws_sqs_queue.work must be replaced
-/+ resource "aws_sqs_queue" "work" {
~ arn = "arn:aws:sqs:REGION:ACCOUNT_ID:work" -> (known after apply)
~ name = "work" -> "tasks" # forces replacement
max_message_size = 262144
}
Plan: 1 to add, 0 to change, 1 to destroy.
Two attributes changed here and only one mattered. arn changes because the resource is being replaced — a symptom. name carries # forces replacement, which is the cause.
Topic 2: Read the Summary Line First
Plan: 1 to add, 0 to change, 1 to destroy.
Before reading a single resource block, ask whether that shape matches what you changed. If you added one resource and the summary says 0 to add, 4 to change, 4 to destroy, something is wrong, and no amount of reading individual blocks is as informative as that mismatch.
Then hunt for - and -/+. Those are the only two symbols that can cause an outage.
forces replacement is the string to grep for:
Provider documentation marks which attributes force replacement. When you change one, the resource cannot be updated in place — the API has no rename — so Terraform destroys and recreates. Between the two, the resource does not exist.
If you need the change anyway, create_before_destroy inverts the order. That works when the platform permits two of the thing simultaneously — which for uniquely-named resources it often does not, and the apply fails rather than silently doing the wrong thing.
For a genuinely non-replaceable rename, the safe sequence is three deploys: create the new resource alongside the old, migrate traffic, then delete the old one separately.
Topic 3: The Dependency Graph and Parallelism
Terraform builds a directed acyclic graph of resources, data sources and modules. Edges come from attribute references (implicit) or depends_on (explicit).
terraform graph | dot -Tsvg > graph.svg
Terraform walks that graph in parallel wherever it can. Two security group rules attached to the same group are created simultaneously once the group exists.
terraform apply -parallelism=20 # default is 10
Raise it for large applies the API can absorb; lower it when you hit provider rate limits. Circular dependencies are detected and reported at plan time — the fix is refactoring, not more depends_on.
Topic 4: plan Without apply
terraform plan # print the plan, no option to apply
terraform plan -out=change.tfplan # save it to a file
terraform plan -destroy # preview a full teardown
terraform plan -refresh-only # detect drift without proposing config changes
terraform plan -target=module.net # limit scope — use sparingly
Saving to a file is what makes pipelines safe.
Topic 5: Saved Plan vs. Auto-Approve
Both skip the confirmation prompt. They are not equivalent, and the difference is a production safety property.
terraform apply change.tfplan # execute exactly this reviewed plan
terraform apply -auto-approve # compute a fresh plan and run it unseen
Applying a saved plan file tells Terraform: execute precisely this. If reality moved since the plan was generated — someone else applied, a resource was deleted — Terraform errors out rather than doing something you did not review.
-auto-approve tells Terraform: work out what to do now and do it without asking. If the plan it computes destroys your database, it destroys your database.
This is why pipelines split plan and apply into separate jobs, publish the plan as an artifact for review, and pass that same file to apply. Treat -auto-approve as a local convenience for throwaway environments.
Topic 6: A Habit Worth Building
Read plans in this order, every time:
- Summary line. Does the shape match the change you made?
- Destroys and replacements. Search for
-and-/+first. forces replacementmarkers. Which attribute caused it, and did you mean to change it?(known after apply)values. Fine on create; on an existing resource they mean something is being recomputed.- Everything else. Usually uninteresting, and that is the point.
terraform show change.tfplan # human-readable
terraform show -json change.tfplan # machine-readable, for policy checks
The JSON form is what policy engines evaluate in CI — a later lesson uses it to block non-compliant changes before they reach an environment.
Try it yourself: Save a plan to a file, then change something in the console before applying it. Run terraform apply change.tfplan and read the error. That refusal is the safety property you are buying.
Common mistake: Skimming to the summary and approving because the counts look small. 1 to destroy on a stateful resource is a bigger event than 40 to add. The counts tell you the shape; only the symbols and forces replacement markers tell you the risk.