Skip to content

Plan and Apply

Everything in Terraform orbits one loop:

write  →  plan  →  read the plan  →  apply  →  reality matches
  ↑                                                     │
  └─────────────  change something, repeat  ────────────┘

plan: the diff, before anything moves

terraform plan changes nothing. It refreshes its picture of reality (asking each provider "what does this object look like now?"), reads data sources, compares the result with your configuration, and prints the difference as a list of proposed actions. The example's first plan begins:

$ terraform plan
data.local_file.banner: Reading...
data.local_file.banner: Read complete after 0s [id=db86c0bfa3387a3ea77846944ec3a07490fbd1c9]

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # local_file.pages["about"] will be created
  + resource "local_file" "pages" {
      + content              = (known after apply)
      + content_base64sha256 = (known after apply)
      # ...further checksum attributes elided
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "./out/dev-about.txt"
      + id                   = (known after apply)
    }

The symbols are the whole vocabulary of change:

Symbol Meaning
+ create — this will be made
- destroy — this will be deleted
~ update in place — same object, changed attributes
-/+ destroy then recreate — replacement
(known after apply) a value nobody can know yet

That last line deserves a pause. The pages resource's content is "(known after apply)" because the text embeds the random server name, and the name doesn't exist until the random provider generates it during apply. Honest unknowability is printed as such, never guessed.

Plans end with a summary and an outputs forecast — memorise the shape of this line, you will read it a thousand times:

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

Changes to Outputs:
  + admin_token=<redacted>
  + banner_sha256   = "7a509ab4f7bc8f42619a533e43bdfa0a94c7d904b06aa50d45e9e900e69973a1"
  + generated_files = [
      + "./out/dev-about.txt",
      + "./out/dev-index.txt",
    ]
  + note_file       = "./out/note.txt"
  + server_name     = (known after apply)
  + settings        = (known after apply)

Two details: the sensitive output shows as <redacted> (the State chapter explains why that is display-deep only), and the checksum of the banner file is already known — the data source was read during planning, so anything derived purely from reads can be shown exactly.

apply: agree, then watch it happen

terraform apply computes the same plan, shows it, and asks yes before proceeding (-auto-approve skips the question; the lab used it, your production account should not). It then executes in dependency order, printing each step:

random_pet.server: Creation complete after 0s [id=correct-dane]
null_resource.build_log (local-exec): Executing: ["/bin/sh" "-c" "echo applied dev on correct-dane >> ./out/build.log"]

Apply complete! Resources: 10 added, 0 changed, 0 destroyed.

Outputs:

admin_token=<redacted>
banner_sha256 = "7a509ab4f7bc8f42619a533e43bdfa0a94c7d904b06aa50d45e9e900e69973a1"
generated_files = [
  "./out/dev-about.txt",
  "./out/dev-index.txt",
]
note_file = "./out/note.txt"
server_name = "correct-dane"
settings = {
  "environment" = "dev"
  "message" = "Deployed by hand the first time."
  "pages" = tolist([
    "about",
    "index",
  ])
}

The summary's three numbers always mirror the plan's. If apply fails halfway, Terraform stops, saves the state of what did happen, and the next plan picks up from there — partial progress is normal and recoverable, not corruption.

Check: the files exist and contain what the configuration promised:

$ ls out/
build.log  dev-about.txt  dev-index.txt  note.txt  stamp-0.txt  stamp-1.txt

$ cat out/dev-index.txt
FIELD NOTES - generated locally, managed by Terraform.
Page: index
Server: correct-dane
Message: Deployed by hand the first time.

Two more plan shapes worth meeting early

Nothing to do. Plan immediately after a successful apply, with nothing changed:

$ terraform plan
...
No changes. Your infrastructure matches the configuration.

That sentence is the sound of desired state achieved, and it is what makes re-running apply safe to automate.

A saved plan. terraform plan -out=demo.tfplan writes the exact plan to a file; terraform show demo.tfplan reads it back, and terraform apply demo.tfplan executes that plan — no re-planning, no prompt. Both were run in the lab exactly so. This is how teams separate "approve the change" from "run the change": a pipeline produces the plan file, a human reads it, and apply consumes the very same file.

destroy

terraform destroy plans the mirror image — everything in state, deleted — and, after the same yes, does it. The Worked Example ends with one; the summary line there reads "Destroy complete! Resources: 10 destroyed." Destroy is a real plan with real deletions, and it deserves the same careful read as any apply. What destruction doesn't reach is also instructive, and saved for that chapter.