linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
Intro
Lessons
Footer
linuxlab-TutorialsPricingAboutPrivacy & cookies
Copyright © 2026 LinuxLab. All rights reserved.
linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
  • Introduction
  • Lessons
  • How it works
  • Knowledge base
  • Cheat sheet
  • Capstone
  • Interview prep
home/terraform/how/tf-plan-three-way-diff

how/state

Plan as a three-way diff: HCL vs state vs cloud

What terraform plan actually compares. Why it refreshes, how three sources become one plan, and what -refresh=false hides.

The familiar picture of git diff is a two-way diff: before and after. In Terraform there are three diffs, and they form a triangle:

  • main.tf is the desired state. What you wrote by hand.
  • terraform.tfstate is Terraform's cache. A snapshot of reality as Terraform last saw it.
  • the cloud (AWS, GCP, LocalStack) is the real state of the resources.

terraform plan first fixes one side of the triangle (state vs cloud, through refresh), then computes the diff along another (HCL vs state). Press ▶ to see how it comes together.

step 1/6·00 · after the previous apply
MAIN.TFжелаемое состояниеTERRAFORM.TFSTATEчто terraform знаетAWSреальностьaws_s3_bucket.demoOwner = "student"aws_s3_bucket.demoOwner = "student"aws_s3_bucket.demoOwner = "student"после предыдущего apply: три источника сошлись

§ steps

  1. The baseline position of the triangle: all three sources agree.

    • HCL describes one bucket with the tag Owner = "student".
    • state knows about this bucket and remembers the same tag.
    • in AWS one such bucket actually exists.

    Running plan again now returns No changes. This is the invariant Terraform always aims for after a successful apply.

recap

The main points about the three-way diff:

  • plan is refresh + diff, in that order. First state syncs with the cloud, then it compares against HCL. Reverse the order and you get a mess, not a plan.
  • A single plan can hold different kinds of mismatch: drift you need to roll back, and a new resource you need to create. They arrive in one output.
  • -refresh=false skips the first step. The plan becomes fast but blind: Terraform will not notice if someone edited the cloud by hand. Handy for local pre-commit checks, dangerous for CI and for a prod plan.
  • Terraform 1.5+ has a separate command terraform plan -refresh-only. It only gets the updated state, without comparing against HCL. Useful for drift detection.

Next: tf-drift on what to do once drift is already visible, and tf-state-mv-rm-import on imperative operations on state.

§ dig into the knowledge base

  • tf-planterraform plan: refresh, diff, exit codes
  • tf-stateWhat is inside the state file
  • tf-driftDrift: state and reality diverged
  • tf-refresh-onlyplan -refresh-only for drift detection
  • tf-plan-flagsPlan flags: -target, -var-file, -refresh

§ try it hands-on

  • ›tf-beginner-03-plan- Plan: reading the output and understanding intent
  • ›tf-garden-02-state-drift- The broken garden: state drift
  • ›tf-production-10-drift-detection- Drift detection in GitHub Actions
Footer
linuxlab-
Copyright © 2026 LinuxLab. All rights reserved.
Tutorials
Pricing
About
Privacy & cookies