Skip to main content

Command Palette

Search for a command to run...

Terraform Under the Hood: State, Graphs, and a Compiler-Inspired Execution Engine

Published
8 min read
Terraform Under the Hood: State, Graphs, and a Compiler-Inspired Execution Engine
A

DevOps Engineer

This article explains Terraform not as a user tool, but as a system: how it thinks, how it reasons, and how it executes.

1. The Fundamental Problem Terraform Solves

Before Terraform, infrastructure automation suffered from three structural problems:

  1. No global view of infrastructure

  2. No reliable mapping between code and real resources

  3. No deterministic ordering or safe parallelism

2. Terraform’s Core Design Philosophy

Terraform is built on five non-negotiable principles:

  1. Declarative intent, imperative execution

  2. State is a first-class primitive

  3. Dependency graph drives everything

  4. Providers are isolated, replaceable plugins

  5. Planning and execution must be separate phases

So, if we understand these five principles, we understand Terraform to a good extent.

3. Terraform as a Compiler for Infrastructure

Although Terraform is not a compiler but it behaves very much like a compiler than a scripting tool.
We can understand it through this analogy:

Compiler ConceptTerraform Equivalent
Source Code.tf files
ASTParsed HCL configurations
Symbol Tablevariables, locals, resources, modules
IR(Intermediate Representation)Dependency Graph
Optimization PassDiff minimization
ExecutionProvider API calls

(I am sure this will remind everyone of Compiler Design 😅)
Note: Here, Diff minimization refers to the smallest and safest set of changes Terraform computes to make real infrastructure match the desired configuration.

So, this is how it goes:
A classic compiler: Source Code → AST → IR → Optimized IR → Machine Code
Terraform: .tf → Parsed Config → Dependency Graph → Execution Plan → API calls

4. Configuration Loading & HCL Evaluation

Parsing HCL

Terraform first parses all .tf files into an abstract syntax tree (AST). At this stage:

  • Only syntax is validated

  • No provider calls occur

  • No resources are evaluated

This allows Terraform to support:

  • Static analysis

  • Formatting (terraform fmt)

  • Validation without credentials

Expression Evaluation Model

Terraform uses a lazy, dependency-aware expression model:

  • Expressions are evaluated only when needed

  • References create implicit graph edges

  • Unknown values are allowed during planning

For example,
instance_id = aws_instance.example.id
During planning, this value may be unknown, but Terraform still proceeds.
This design enables forward references and dynamic graphs.

5. Providers: The Plugin Boundary

Terraform core never talks to cloud APIs.

Providers are:

  • Separate binaries

  • Communicated with via gRPC

  • Versioned and schema-driven

Provider Responsibilities

  1. Resource schemas

    • Defines the shape of each resource (attributes, types, optional/required fields, computed fields)

    • Tells Terraform what properties are available

  2. CRUD operations

    • Create, Read, Update, Delete for each resource

    • Terraform calls these during apply

  3. Diff / Plan logic

    • Determine what changed between the desired state (.tf) and the current state

    • Used to generate the execution plan

  4. Import behavior

    • Ability to import existing resources into Terraform state

    • Needed for resources that were created outside Terraform

  5. State upgrade / migration paths

    • Handle provider version upgrades

    • Transform stored state so it remains compatible

Terraform core delegates all domain-specific logic to providers.

6. State: Terraform’s Memory & Authority

What State Really Is

It is a file that acts as Terraform’s memory of the infrastructure it manages.

It’s the source of truth for Terraform about what resources exist and how they are mapped.
State is a bidirectional mapping.
For example,
aws_instance.example.id ↔ i-abcd123

Why Terraform Owns State

Terraform owns state because:

  • Providers cannot track cross-resource dependencies

  • Clouds do not expose dependency graphs

  • Drift detection requires historical knowledge

State enables precise diffs instead of blind recreation.

7. Backends and State Locking

Backends define:

  • Where state is stored

  • How it is locked

  • How concurrency is handled

Why Locking is Mandatory?

Terraform assumes exclusive mutation rights.

Without locking:

  • Two applies could destroy each other’s resources

  • Graph assumptions become invalid

Remote backends enforce locking via:

  • DynamoDB

  • Terraform Cloud

  • Native backend mechanisms

8. terraform init: System Bootstrapping

terraform init is Terraform’s bootstrap compiler phase. It prepares everything Terraform needs before it can understand, plan, or apply your infrastructure.
Just like a compiler must:

  • set up its environment

  • load libraries

  • prepare tooling

before compiling code, Terraform must initialize its working directory before doing anything else.

Configuration Scan & Root Module Assembly

Terraform first scans the working directory and constructs the root module:

  • All .tf files are loaded

  • Files are merged logically (order does not matter)

  • Only syntax and block structure are validated

No expressions are fully evaluated yet. Terraform is only building a structural model of the configuration.

Backend Resolution and State Writing

Terraform then initializes the backend:

  1. Determines backend type (local, S3, Terraform Cloud, etc.)

  2. Loads backend configuration

  3. Establishes credentials

  4. Verifies read/write permissions

At this stage:

  • No state is read

  • No locks are acquired

Terraform is only confirming where state will live and how it will be accessed later.

Provider Discovery & Dependency Resolution

Terraform identifies required providers from:

  • required_providers

  • Implicit provider usage in resources

Terraform then:

  1. Solves version constraints using semantic versioning

  2. Selects exact provider versions

  3. Downloads provider binaries

  4. Verifies checksums

Providers are installed as isolated executables, not libraries.

Plugin Handshake & Schema Discovery

Terraform performs a gRPC handshake with each provider to:

  • Validate protocol compatibility

  • Retrieve resource schemas

  • Retrieve data source schemas

  • Register lifecycle capabilities

At this point Terraform learns:

  • Which attributes are computed

  • Which changes force replacement

  • What operations are supported

Module Installation & Graph Expansion

Terraform resolves all modules:

  • Registry modules

  • Git modules

  • Local modules

Each module is:

  • Downloaded

  • Version-pinned

  • Cached

Terraform expands modules recursively, but does not yet evaluate their contents.

Dependency Lock File Generation

Terraform writes .terraform.lock.hcl, recording:

  • Exact provider versions

  • Cryptographic checksums

9. terraform plan: The Reconciliation Engine

terraform plan is Terraform’s core reasoning phase.
It’s sole purpose is to figure out the smallest and safest set of changes needed to make the real infrastructure match what you want.

State Loading & In-Memory State Graph

Terraform:

  • Reads the current state from the backend

  • Loads it into memory

  • Converts it into an internal graph structure

Refresh: Reality Interrogation

Terraform:

  • Calls the provider’s Read function for each resource

  • Queries the real cloud APIs

  • Normalizes the responses into Terraform’s expected format

Expression Evaluation & Unknown Propagation

Terraform:

  • Evaluates expressions (var.x, local.y, references)

  • Does so lazily (only when needed)

  • Marks values as unknown if they depend on something not yet created

Dependency Graph Construction (DAG)

Terraform:

  • Builds a Directed Acyclic Graph

  • Each node = a resource instance

  • Each edge = a dependency (explicit or implicit)

This graph:

  • Determines creation / update order

  • Enables safe parallel execution

  • Prevents cycles

Diff Computation Engine

Terraform:

  • Compares the desired configuration graph

  • Against the current (refreshed) state graph

  • At the attribute level

For each resource, Terraform decides:

  • No change

  • In-place update

  • Replace (destroy + create)

This is where minimal, safe changes are determined.

Plan Serialization

Terraform:

  • Produces an immutable execution plan

  • Stores it in a binary plan file

This plan:

  • Is deterministic

  • Can be reviewed

  • Can be applied later without recomputation

10. terraform apply: Graph Execution Engine

terraform apply is Terraform’s transactional execution phase. It is the phase where Terraform actually changes real infrastructure based on a previously computed plan.

State Lock Acquisition

Before making any changes, Terraform:

  • Acquires an exclusive lock on the state

  • Prevents other Terraform runs from modifying the same infrastructure at the same time

Why this matters:

  • Avoids race conditions

  • Prevents state corruption

Notes:

  • Locking depends on the backend (S3 + DynamoDB, Terraform Cloud, etc.)

  • Local state has implicit locking

Execution Graph Walk

Terraform:

  • Uses the dependency graph (DAG) built during planning

  • Walks the graph in a safe order

  • Executes independent nodes in parallel

Example:

  • Multiple EC2 instances in the same subnet can be created at the same time

  • A database waits until its network dependencies exist

This ensures:

  • Correct ordering

  • Faster execution

Provider RPC Invocation

For each node in the execution graph, Terraform:

  • Calls the provider’s Create / Update / Delete methods

  • Communicates via gRPC

  • Providers translate these calls into real API requests (AWS, Azure, etc.)

Terraform Core:

  • Orchestrates

  • Does not know API details

Providers:

  • Perform the actual work

Incremental State Mutation

After each successful operation, Terraform:

  • Immediately updates the in-memory state

  • Persists the updated state to the backend

Why this is important:

  • If execution stops midway, Terraform still knows what succeeded

  • Prevents redoing completed work

This is critical for crash recovery and retries.

Failure Semantics

Terraform does not provide automatic rollback.

If something fails:

  • Successfully applied changes are kept

  • Failed operations remain unapplied

  • State reflects partial success

Terraform’s recovery model is:

Fix the problem → re-run terraform plan → re-run terraform apply

Why this design:

  • Many infrastructure APIs do not support atomic transactions

  • Rollbacks can be dangerous or ambiguous

11. Lifecycle Controls

Terraform provides lifecycle rules that let you influence execution strategy i.e. how resources are created, updated, or destroyed, without changing Terraform’s core model of infrastructure.

They affect:

  • Order of operations

  • Safety guarantees

  • Which changes to act on

They do not change:

  • Dependency graph structure

  • Resource identity

  • Terraform’s state model

Common lifecycle settings: create_before_destroy, prevent_destroy, ignore_changes

12. Terraform’s True Strength

Terraform’s power is not provisioning.
It is accurate, minimal change computation at scale.

Terraform answers with precision:

“What is the smallest possible set of safe operations to reach the desired state?”

That is its real innovation.

Conclusion:

Terraform is:

  • A compiler for infrastructure

  • A graph execution engine

  • A state reconciliation system

It does not manage servers.

It manages truth.


Connect with me on LinkedIn: https://linkedin.com/in/scoder17
Follow me on GitHub: https://github.com/scoder17
Connect with me on Twitter/X: twitter.com/scoder17_
Subscribe me here: https://scoder17.hashnode.dev/