Terraform basics · Lesson 2 of 9 · about 20 minutes

Reading a plan

How to read every kind of change in a Terraform plan, why some changes replace a resource instead of updating it, why deleting code deletes infrastructure, and how saved plans make sure you apply exactly what you reviewed.

By the end you'll know

  • What each symbol in a plan means: +, ~, - and -/+
  • Why some changes update a resource in place, and others replace it
  • How to spot the line that forces a replacement
  • Why removing a resource from your code removes the real thing
  • How to save a plan and apply exactly that plan

Where you are

At the end of Lesson 1, your terraform-practice folder has a main.tf describing one file, and Terraform has created hello.txt. Every plan so far has said the same thing: one to add.

Real work is mostly changes to things that already exist, and that's where plans matter. A plan that says "1 to change" is routine. A plan that says "1 to destroy" might be about to delete a production database. This lesson teaches you to tell the difference at a glance, by making each kind of change on purpose.

Changing something in place

Repainting a front door doesn't mean replacing the door: you change one thing about it, and it's still the same door. Some Terraform changes work that way, and the plan calls them updates in place.

To see one, add a second resource. terraform_data is built into Terraform itself, so it needs no provider. It does nothing except hold a value, which makes it handy for practising, and here it stands in for a setting you might change on a real server. Add this to the end of main.tf:

HCL
resource "terraform_data" "release" {
  input = "v1"
}

Run terraform apply and type yes. Now change "v1" to "v2", and run terraform plan:

OUTPUT
Terraform will perform the following actions:

  # terraform_data.release will be updated in-place
  ~ resource "terraform_data" "release" {
        id     = "e807b3f2-86ed-e1fd-e968-a8f4e1a6c459"
      ~ input  = "v1" -> "v2"
      ~ output = "v1" -> (known after apply)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

(Your id will be different.) Here's how to read it:

  • ~ means "change in place". The resource stays; something about it changes.
  • "v1" -> "v2" shows the old value on the left and the new one on the right.
  • Lines with no symbol, like id, aren't changing. Terraform shows them so you can tell which resource this is.
  • The summary says 1 to change. Nothing is added or destroyed.

Run terraform apply and type yes to make the change.

Changes that replace

You can repaint a door, but you can't change a baked cake into a different flavour: you bake a new one. Some things can't be changed where they are, so Terraform has to destroy the old one and create a new one. That's a replacement, and it's the change to look out for, because whatever was inside the old one goes with it.

A file on disk is one of those things, as far as Terraform's local provider is concerned. Change the content of hello.txt in main.tf:

HCL
  content  = "Hello again from Terraform!"

Run terraform plan, and among the output you'll see:

OUTPUT
  # local_file.hello must be replaced
-/+ resource "local_file" "hello" {
      ~ content              = "Hello from Terraform!" -> "Hello again from Terraform!" # forces replacement

followed by more lines, and then the summary, Plan: 1 to add, 0 to change, 1 to destroy. Three things tell you this is a replacement:

  • The # line says "must be replaced", not "will be updated".
  • -/+ means "destroy, then create a new one in its place".
  • # forces replacement at the end of a line marks the setting that caused it. When a plan surprises you with a replacement, find this marker first: it tells you which change is to blame.

Notice the summary: one to add and one to destroy, for a single change. With a file, that's harmless. With a cloud server, the same pattern means the server, and everything stored on it, is deleted and a blank new one is made. Changing a server's region, for example, usually forces replacement. That's why you read the plan before typing yes. Run terraform apply and type yes, then cat hello.txt to see the new content.

Deleting code deletes things

Terraform's job is to make the real world match your files. So if a resource disappears from your files, Terraform makes it disappear from the real world too. Delete the whole terraform_data "release" block from main.tf, all four lines, and run terraform plan:

OUTPUT
  # terraform_data.release will be destroyed
  # (because terraform_data.release is not in configuration)
  - resource "terraform_data" "release" {
      - id     = "e807b3f2-86ed-e1fd-e968-a8f4e1a6c459" -> null
      - input  = "v2" -> null
      - output = "v2" -> null
    }

Plan: 0 to add, 0 to change, 1 to destroy.

- means "destroy", and -> null means each value is going away. The second # line spells out why: the resource is no longer in your configuration. This is how most accidental deletions happen in real projects: someone removes or renames a block while tidying up, and the plan says "destroy". Run terraform apply and type yes, so release is gone and only hello.txt remains.

Here are all the symbols together:

SymbolMeansWorry level
+createLow: something new
~update in placeLow: same thing, changed
-/+destroy, then create a replacementHigh: the old one and its contents are gone
-destroyHigh: check you meant it

You may also meet +/- one day: create the replacement first, then destroy the old one. It's a setting you can turn on for resources that mustn't have a gap, and Lesson 9 shows where it lives.

Four kinds of changeFour panels. Create, with a plus: nothing becomes a new resource. Update in place, with a tilde: the same resource with one setting changed. Replace, with minus slash plus: the old resource is destroyed and a new one created, marked by forces replacement. Destroy, with a minus: the resource is removed, often because its code was deleted.+ createnothing there yet,so it's madePlan: 1 to add~ updatesame resource,one setting changedPlan: 1 to change-/+ replaceold one destroyed,new one madelook for: forces replacement- destroyremoved, often becauseits code was deletedPlan: 1 to destroyRead the summary line first. Anything with "destroy" in it deserves a closer look.
The two on the left are routine. The two on the right remove something real, so they're the ones to read carefully before typing yes.

Saved plans: apply exactly what you reviewed

When you run terraform apply on its own, Terraform makes a fresh plan and asks you to approve it. Usually that's the same plan you just read. But if anything changed in between, like a colleague's apply, or a file you edited, you'd be approving a plan you never reviewed.

A saved plan closes that gap. Change hello.txt's content once more, to anything you like, then:

BASH
terraform plan -out=tfplan

-out=tfplan saves the plan to a file called tfplan. Read it as usual, then apply that exact file:

BASH
terraform apply tfplan

This time Terraform doesn't ask for yes: you already reviewed this plan, and saving it was your approval. It applies precisely what the file says, and nothing else.

Keep saved plans out of Git. A plan file holds every value Terraform will use, including any secrets, like passwords and tokens, and a plan saved on your own computer stores them in plain text. Lesson 3 explains why the same is true of state.

In my real setup

Before a risky change to CivicGrid's production, like rebuilding the cluster or rotating a token, I save the plan with -out, read it, and apply that exact plan. My plan files are tiny, though: CivicGrid's Terraform runs on HCP Terraform, which Lesson 8 covers, so the plan itself stays on its servers, and the file on my computer is just a short pointer to it. A plan saved by Terraform on your own computer is different: it contains everything, secrets included, which is why *.tfplan is in my .gitignore either way.

Break it on purpose

Try each one, then put it back.

Apply a saved plan twice. You applied tfplan above. Apply the same file again:

BASH
terraform apply tfplan
OUTPUT
Error: Saved plan is stale

The given plan file can no longer be applied because the state was changed by
another operation after the plan was created.

A saved plan is a snapshot of what needed doing at one moment. Once anything has changed since, including applying the plan itself, Terraform refuses to apply it, rather than guess. Make a fresh one when you need it. Delete the old file with rm tfplan (rm removes a file).

Rename a resource. In main.tf, change the resource's name from "hello" to "greeting", leaving everything else the same, and run terraform plan. The summary says:

OUTPUT
Plan: 1 to add, 0 to change, 1 to destroy.

You only changed a name inside Terraform, not the file, but Terraform sees local_file.hello gone from your code and a new local_file.greeting in it, so it plans to destroy one and create the other. With a file it's harmless; with a database it wouldn't be. Terraform has a way to rename safely, which Lesson 3 covers. For now, change the name back to "hello" and run terraform plan again: No changes.

Approve without reading. Run terraform apply -auto-approve. It applies straight away, with no plan to read and no yes. You'll see it in scripts and tutorials, and it's useful in automation that's already been reviewed. Typed by hand, it skips the one step that protects you. Since there are no changes right now, nothing happens, which is the only safe time to use it while you're learning.

Where you are now

You can read every kind of change in a plan, find the setting that forces a replacement, and apply exactly the plan you reviewed. But how does Terraform know that hello.txt already exists, or what its id was? It keeps a record, and that record is Lesson 3: state.

Check yourself

  1. In a plan, what does the ~ symbol mean?

  2. What does -/+ mean in a plan?

  3. A plan says a resource must be replaced. Which line tells you why?

  4. You delete a resource block from main.tf and run terraform apply. What happens?

  5. Why save a plan with -out and apply that file?

  6. terraform apply tfplan says Saved plan is stale. Why?

Key terms

  • Update in place (~): a change to a resource that keeps the same resource.
  • Replacement (-/+): destroying a resource and creating a new one, because a setting can't be changed in place.
  • forces replacement: the note in a plan that marks the setting causing a replacement.
  • Destroy (-): removing a resource, often because its block was deleted from the code.
  • terraform_data: a resource built into Terraform that just holds a value. Handy for practising.
  • Saved plan: a plan written to a file with -out, then applied exactly with terraform apply <file>.
  • Stale plan: a saved plan that no longer matches reality, which Terraform refuses to apply.
  • -auto-approve: applies without asking for yes. For reviewed automation, not for typing by hand.

Go deeper