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.
PrerequisiteTerraform 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.
LockFor 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.
RefreshTerraform 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.
GraphTerraform 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.
CheckpointThe 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.
ReviewInteractively, 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.
ConfirmTerraform 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.
ExecuteCritically, 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 WriteOnce apply completes (success or failure), the state lock is released, allowing the next apply — from any engineer or CI job — to proceed.
Unlock| Term | Meaning |
|---|---|
| Provider Plugin | Binary translating HCL resource blocks into a specific API's calls (AWS, Azure, etc.) |
| State File | Terraform's record mapping resource addresses to real-world IDs and attributes |
| Refresh | Reading real infrastructure to detect drift before computing a plan |
| DAG | Directed Acyclic Graph — the dependency graph determining execution order |
| Plan | A computed diff of creates/updates/destroys, with no real changes applied yet |
| ForceNew | Attribute that requires destroy-and-recreate rather than an in-place update |
| -auto-approve | Skips the interactive confirmation prompt — CI use only |
| parallelism | Max concurrent resource operations during apply (default 10) |
| State Lock | Backend-enforced lock preventing concurrent applies against the same state |
| Drift | Divergence between Terraform's recorded state and actual infrastructure |