← all cheat sheets
FUNDAMENTALS · MECHANISM WALKTHROUGH

terraform apply
What Really Happens, Step by Step

from HCL on disk to real infrastructure — refresh, graph, plan, and apply, in the order they actually run.
INIT (PROVIDERS) REFRESH STATE BUILD DEPENDENCY GRAPH PLAN (DIFF) CONFIRM APPLY (API CALLS) STATE UPDATED LOCK RELEASED
01 The Full Sequence — Worked Example
1

Provider Plugins Are Already Initialized

terraform apply assumes terraform init has already run — provider plugins (e.g. the AWS provider binary) are downloaded into .terraform/ and ready to make API calls before apply even starts.

Prerequisite
2

State Lock Acquired

Terraform first acquires a lock on the state file (via the backend — e.g. a DynamoDB table for an S3 backend) to prevent another apply from running concurrently against the same state.

Lock
3

Refresh — Read Real Infrastructure

For every resource already in state, Terraform calls the provider's read API to check the actual current value in the real world — this catches drift, e.g. someone manually resized an EC2 instance outside of Terraform.

Refresh
4

Build the Dependency Graph

Terraform parses every resource/module reference in the .tf files (e.g. a security group ID referenced by an EC2 instance) and builds a directed acyclic graph — this determines both execution order and what can safely run in parallel.

Graph
5

Checkpoint — Plan Computed, Nothing Changed Yet

Terraform walks the graph, comparing desired config against refreshed real state for every resource, and produces a plan: a list of creates/updates/destroys with a full diff. Up to this exact point, zero real-world infrastructure has been touched.

Checkpoint
6

Plan Is Displayed for Review

The plan output shows each change with + (create), ~ (update in place), or -/+ (destroy and recreate) — the last is important because it means downtime/data loss risk for that resource, not a safe in-place change.

Review
7

User Confirms (or -auto-approve Skips This)

Interactively, Terraform prompts "Do you want to perform these actions?" and waits for literal yes. In CI, -auto-approve skips this — which is exactly why it's a CI-only flag, not an interactive habit.

Confirm
8

Apply Walks the Graph, Calling Provider APIs

Terraform now executes the graph — independent resources (no dependency between them) are created/updated/destroyed in parallel, up to the configured parallelism limit; dependent resources wait for their dependencies to finish first.

Execute
9

State File Updated After Each Resource

Critically, Terraform writes state updates incrementally, as each resource finishes — not in one final batch at the end. This is exactly why a partially-failed apply leaves state accurately reflecting what did succeed, rather than nothing at all.

State Write
10

Lock Released

Once apply completes (success or failure), the state lock is released, allowing the next apply — from any engineer or CI job — to proceed.

Unlock
02 How to Explain This in an Interview
03 Follow-Up / Gotcha Questions
Q If apply fails on resource 3 of 5, what's the state of resources 1 and 2?
A They're already created and already recorded in state, because Terraform writes state incrementally as each resource completes — re-running apply will only attempt the remaining/failed resources, not recreate everything from scratch.
Q Does terraform plan ever make real infrastructure changes?
A No — plan performs the refresh (read-only) and diff, but never calls a create/update/destroy API. It's fully safe to run repeatedly, which is exactly why it belongs in CI as a pre-merge check.
Q Why would a plan show -/+ instead of a simple ~ update for a change that seems minor?
A Some resource attributes are marked ForceNew by the provider — changing them isn't supported as an in-place API update, so Terraform must destroy and recreate the resource entirely to apply that specific change.
Q Can two unrelated resources apply in parallel?
A Yes, if the dependency graph shows no edge between them, Terraform applies them concurrently up to the parallelism limit (default 10) — this is why apply is often much faster than a naive one-at-a-time execution.
Q What's the risk of skipping refresh (terraform apply -refresh=false)?
A The plan is computed against potentially stale state instead of real current infrastructure, so drift caused by manual changes outside Terraform won't be detected — the apply might "succeed" while leaving the actual resource in an unexpected combined state.
Q Why does the plan sometimes show a change that seems unrelated to anything you edited?
A Usually a provider default value changed between provider versions, or a computed attribute genuinely drifted in the real infrastructure — refresh (step 3) surfaces this even without any .tf file edits.
04 Quick-Fire Glossary
TermMeaning
Provider PluginBinary translating HCL resource blocks into a specific API's calls (AWS, Azure, etc.)
State FileTerraform's record mapping resource addresses to real-world IDs and attributes
RefreshReading real infrastructure to detect drift before computing a plan
DAGDirected Acyclic Graph — the dependency graph determining execution order
PlanA computed diff of creates/updates/destroys, with no real changes applied yet
ForceNewAttribute that requires destroy-and-recreate rather than an in-place update
-auto-approveSkips the interactive confirmation prompt — CI use only
parallelismMax concurrent resource operations during apply (default 10)
State LockBackend-enforced lock preventing concurrent applies against the same state
DriftDivergence between Terraform's recorded state and actual infrastructure