skip to content

Why would a team run `terraform plan -out=tfplan` and later `terraform apply tfplan` instead of just running `terraform apply`?

level: middleimportance: must knowfreq 65%

answer

  1. approve one thing, apply that thing
  2. apply re-plans unless handed a file
  3. variables are baked in
  4. the file remembers which state it saw
  5. stale plan beats surprise change

basics

~20 s

So the changes that get applied are exactly the changes that were reviewed. A bare terraform apply computes a brand-new plan at apply time; applying a saved plan file replays the recorded decisions instead, with no re-planning and no approval prompt.

solid answer

~50 s

A bare `terraform apply` plans again from scratch, so the diff you approve is computed at apply time — it is not the diff your colleague reviewed in the pull request. `terraform plan -out=tfplan` freezes that decision into a file: the planned actions, the variable values used, and a snapshot of the state it was based on. `terraform apply tfplan` then executes exactly those actions, without re-planning and without prompting, because the approval already happened when the plan was reviewed. It also fails safe: because the plan records the state it was created from, Terraform rejects it as stale if someone else applied in the meantime, rather than executing a decision made against a world that no longer exists. That property — reviewed diff equals applied diff — is the whole reason pipelines are built this way.

code

bash · 6 lines
bash
# review step
terraform plan -input=false -out=tfplan
terraform show -no-color tfplan > plan.txt

# execution step, after the diff in plan.txt was approved
terraform apply -input=false tfplan

go deeper

for a junior

Know the two-command form — terraform plan -out=tfplan then terraform apply tfplan — and that its purpose is to apply exactly the changes that were reviewed.

for a middle

Explain what the file records (actions, variables, the state it saw), that apply skips both re-planning and the prompt, and why passing -var to it is rejected.

for a senior

Demonstrate judgment about the review-to-execution gap: when you insist on a saved plan, how you handle a stale-plan rejection, and what the file's sensitivity costs you operationally.

for a principal

Own the change-control argument — whether diff equality is a requirement or a nicety for your estate, what evidence of the approved plan is retained, and the tradeoff against simply re-planning at apply time.

## The problem being solved The dangerous gap in an infrastructure change is between *approval* and *execution*. Someone reads a diff, agrees it is safe, and clicks approve. Minutes or hours later the change runs. If the tool re-derives what to do at that later moment, the approval was for a different thing than the one that happened. A bare `terraform apply` has exactly that gap. It runs its own planning phase, prints the result, and asks for confirmation. Even the operator standing at the terminal is approving a freshly computed diff — and in a pipeline with `-auto-approve` nobody sees it at all. ## What `-out` actually saves ```bash terraform plan -out=tfplan terraform apply tfplan ``` The file produced by `-out` is not a text report. It is an opaque binary artefact containing the planned action for each resource, the configuration, a snapshot of the state the plan was computed against, and the variable values that were in effect. Everything the apply phase needs to proceed without deciding anything again. Applying it changes Terraform's behaviour in several specific ways: - **No re-planning.** The recorded actions are executed as recorded. - **No approval prompt.** The plan file is itself the approval, so `-auto-approve` is unnecessary. - **No variable input.** Values are baked in. Passing `-var` or `-var-file` alongside a saved plan is an error, not an override — Terraform tells you it cannot set variables when applying a saved plan. This closes a real hole: you cannot review a plan built with one set of inputs and then apply it with different ones by changing a flag. ## The staleness check The plan records which state it was built from. If the state has moved on — another engineer applied, an automated job ran, a rollback happened — Terraform refuses the plan rather than executing it, reporting that the saved plan is stale because the state was changed by another operation after the plan was created. The fix is always the same: re-plan, re-review, re-apply. This is worth stating carefully, because it is not a general-purpose guarantee. Terraform detects that *the recorded state changed*. It does not detect that someone edited the resource in the cloud console without touching Terraform state — the plan file cannot know about that. Saved plans make the review honest; they do not make the world stand still. ## Where this shows up in practice The canonical pipeline shape is one job that runs `terraform plan -out=tfplan` and publishes the human-readable diff for review, then a second job — after merge or after a manual approval — that takes the same file and runs `terraform apply tfplan`. The alternative, re-planning in the apply job, is simpler to operate but gives up the guarantee entirely. That convenience comes with a real obligation: the plan file contains the prior state and every planned attribute value, sensitive ones included, so it has to be handled as secret material rather than as an ordinary build artefact. ## Reading a saved plan Because the file is binary, you inspect it through Terraform: ```bash terraform show tfplan # the same human-readable diff terraform show -json tfplan # machine-readable, for policy tooling ``` The JSON form is how automated checks consume a plan: a policy engine or scanner evaluates what is *about to happen* rather than guessing from the source files. That is a much stronger check, because it sees the resolved values after variables, locals and data sources have been evaluated. ## Limits to acknowledge A plan file is tied to the configuration and the Terraform version that produced it; a different version will refuse to read it. It must be applied from an initialised working directory with the same providers available. And it is short-lived by design — the older it gets, the more likely the staleness check will reject it, which is a feature and not an inconvenience. ## What a strong answer sounds like "Because apply otherwise re-plans, so the reviewed diff and the applied diff are two different computations. Saving the plan makes them one artefact, pins the variables, and gives me a stale-plan error instead of a surprise if the state moved. The cost is that the file holds sensitive values and has to be protected like state."

  • What happens if someone else applies a change between your plan and your `terraform apply tfplan`?
    Terraform refuses to apply. The plan file records the state it was computed from, so when the state has advanced Terraform reports the saved plan as stale and stops rather than executing decisions made against an older world. You re-run plan, re-read the new diff, and apply that. It is the intended outcome, not a bug to work around.
  • Can you pass `-var` or `-var-file` when applying a saved plan?
    No — Terraform errors out, saying variables cannot be set when applying a saved plan. The values used during planning are recorded in the file. That restriction is deliberate: if inputs could be swapped at apply time, the reviewed plan would no longer describe what runs, which defeats the entire purpose of saving it.
  • How do you review a plan file if it is binary?
    Run `terraform show tfplan` for the same human-readable diff, or `terraform show -json tfplan` for a machine-readable form. The JSON is what policy and scanning tools consume, and it is stronger than reading the source files because the values are already resolved — variables, locals and data source lookups have been evaluated.

saying these in an interview costs you the question

  • Thinks a bare apply replays the plan you just ran
  • Says a saved plan still prompts unless you add -auto-approve
  • Believes -var can override values when applying a saved plan
  • Treats a plan file as a plain text report
  • Assumes a saved plan protects against console edits too

context