How Terraform Works
What this module is for
Section titled “What this module is for”Every earlier module in this course taught you to use Terraform and Terragrunt: writing HCL, running init/plan/apply, storing state remotely, wiring variables and modules, building a multi-environment live tree, and shipping through CI with policy checks. None of it required knowing what terraform plan actually builds before it prints a diff, what terraform.tfstate actually is at the byte level, what -out=tfplan actually writes to disk, how hashicorp/azurerm (or aws, or google) actually talks to terraform as a process, or how terragrunt run --all actually decides which unit runs before which.
That gap is exactly where AI-generated Terraform tooling and reviews fail in ways that look fine on the surface: a “policy check” script that greps terraform plan text output instead of reading the structured resource_changes[].change.actions array terraform show -json actually produces; a CI step that assumes terraform apply -parallelism=50 is always faster with no idea what the flag bounds; a teammate who “fixes” a stuck state lock by hand-editing terraform.tfstate’s serial field, not knowing state push will simply refuse a stale one; a dev_overrides block copy-pasted from a blog post that silently breaks init for a reason nobody can explain from the error text alone. None of these are HCL syntax errors. None of them are caught by terraform validate. All of them are facts about the runtime this module makes provable.
This module has one job: give you five pieces of runtime knowledge that let you look at any Terraform or Terragrunt output — a graph, a state file, a -out plan, a provider crash, a run --all log — and know exactly what produced it and why.
- What does
terraform plan/applyactually walk, and in what order? (The Graph and the Walk — a realterraform graph, DOT and Mermaid,-parallelism,depends_onvs implicit references) - What is
terraform.tfstate, really, at the schema level? (State File Anatomy — realstate pullJSON,serial/lineageenforcement proven with a real refusal,state replace-provider,show -jsonvs raw state) - What is a saved plan file, and what does CI/policy tooling actually read from it? (Plan File and JSON Output —
-outis a real zip, proven withunzip -l; the fullresource_changes[].change.actionsmatrix;-detailed-exitcode) - How does Terraform actually talk to a provider? (Provider Plugin Protocol — go-plugin over gRPC, a real
TF_LOG=TRACEhandshake andGetProviderSchemacall, whyinitdownloads a binary per OS/arch, a real induced provider crash,dev_overrides) - How does Terragrunt decide what to run, and in what order? (Terragrunt Discovery and DAG — real
terragrunt find/list --dag, a realrun --all --parallelism 1ordered log,.terragrunt-cache,mock_outputs)
Every claim in this module is backed by one of three things: a real, reproduced local run against Terraform 1.16.4 and Terragrunt 1.1.0 (this course’s pinned versions) — pasted below the claim, trimmed of machine-specific paths but never invented; the official docs at developer.hashicorp.com or terragrunt.gruntwork.io; or, where neither is possible offline, an explicit note saying so.
What this module covers
Section titled “What this module covers”| Lesson | Answers the question |
|---|---|
| The Graph and the Walk | What does Terraform actually build from your .tf files before it does anything, and how does -parallelism change what happens on apply? |
| State File Anatomy | What, exactly, is inside terraform.tfstate, and what stops two people from silently clobbering each other’s state? |
| Plan File and JSON Output | What is a -out plan file made of, and what does a policy engine or cost tool actually parse out of show -json? |
| Provider Plugin Protocol | What actually happens between terraform init and a provider being ready to plan, and what does a provider crash look like? |
| Terragrunt Discovery and DAG | How does Terragrunt find your units, build its own dependency order, and decide what run --all does first? |
The State File already introduced state as “a JSON file that maps configuration to real infrastructure.” This module goes past that framing into the actual schema, the actual graph algorithm, the actual bytes on disk, and the actual process boundary between Terraform Core and a provider.
flowchart TD hcl["Your .tf files"] --> graphbuild["Build the dependency graph\n(The Graph and the Walk)"] graphbuild --> walk["Walk the graph, up to -parallelism nodes concurrently\n(default 10)"] walk --> rpc["Per node: gRPC call to the provider plugin\n(Provider Plugin Protocol)"] rpc --> plan["Plan result assembled"] plan --> outfile["terraform plan -out=tfplan\n(a real zip -- Plan File and JSON Output)"] outfile --> apply["terraform apply tfplan"] apply --> state["Written to state\n(State File Anatomy)"] state --> tg["Terragrunt units: their own DAG over many state files\n(Terragrunt Discovery and DAG)"]
Mental Model
Section titled “Mental Model”Everything in this module is one story told from five different points on the same pipeline: Terraform never executes your configuration top to bottom — it compiles it into a graph, and every command (plan, apply, destroy, graph itself) is a walk over that graph, dispatching each node to whichever provider plugin owns it over gRPC, and recording the result in one versioned state document. Terragrunt sits one layer above this and does the same thing at a coarser grain: instead of resources, its nodes are whole Terraform units (directories), its edges come from dependency/dependencies blocks instead of resource references, and its own --parallelism bounds how many units run concurrently — completely independent of the -parallelism any individual terraform apply inside a unit uses. Once you can place any fact in this module against “graph, walk, gRPC call, state write” (and, one layer up, “Terragrunt’s own graph, walk, unit run”), reading an unfamiliar plan diff, a state file, a CI policy step, or a run --all log stops being five separate mysteries.
Common Misconceptions
Section titled “Common Misconceptions”- “
terraform applyruns resources in the order they’re written in the file.” The Graph and the Walk proves the opposite with a realterraform graph: order comes entirely from the dependency graph (implicit references and explicitdepends_on), and independent resources are dispatched concurrently, up to-parallelism(default 10) — proven with real wall-clock timing showing 5 independent 2-second operations finish in ~2s by default and ~10s at-parallelism=1. - “State is just a cache; you can hand-edit it if
terraform statecommands are inconvenient.” State File Anatomy proves state is versioned and enforced: a realterraform state pushwith a staleserialor a mismatchedlineageis refused outright with the real error text, not silently merged. - “A saved plan file is basically the same as the human-readable
planoutput, just cached.” Plan File and JSON Output proves-outproduces a real zip archive (unzip -lshown live) containing the plan, prior state, post-refresh state, and a full config snapshot — enough forapplyto proceed with zero re-evaluation of your.tffiles. - “Terraform ‘just knows’ how to talk to AWS, Azure, and GCP built in.” Provider Plugin Protocol proves every provider is a separate subprocess Terraform starts and speaks gRPC to over a Unix socket, with a real, visible
GetProviderSchemacall captured underTF_LOG=TRACE. - “
terragrunt run --allwalks units in the orderfindlists them.” Terragrunt Discovery and DAG proves ordering comes fromdependency/dependenciesblocks forming Terragrunt’s own DAG, independent of alphabetical or discovery order — proven with a real--parallelism 1log showing a dependency unit’splancompleting before either of its two dependents starts.
Reading AI-generated code
Section titled “Reading AI-generated code”The whole point of this module is this section, five times over — one per lesson. As a preview, here is the survey-level version: a CI “policy gate” script that looks reasonable and silently checks nothing.
🐛 Spot the bug
#!/usr/bin/env bash# A teammate's "policy gate" before terraform apply in CIterraform plan -out=tfplan > plan_output.txtif grep -q "Plan: 0 to add, 0 to change, 0 to destroy" plan_output.txt; thenecho "no changes, skipping approval"elseecho "changes detected, needs approval"fiterraform apply tfplanShow the answer
This greps the human-readable summary line, which is a localization- and formatting-fragile string, not a stable interface — Terraform has never documented that exact text as an API. Plan File and JSON Output proves the real, stable interface is terraform show -json tfplan, which exposes resource_changes[].change.actions as a real array (["no-op"], ["create"], ["update"], ["delete","create"], ["forget"], …) meant exactly for this use case — and terraform plan -detailed-exitcode (proven in the same lesson: exit code 2 for “changes present”, 0 for “no changes”, 1 for a real error) replaces the whole grep with a documented exit code the script can branch on directly, with no text parsing at all.