Terraform Guide
Terraform is a tool for describing infrastructure as text, in a file you can review, version and
re-run, so that the thing running in your cloud account matches the thing written in your repository.
That sentence is uncontroversial and almost useless. The interesting part is everything it hides: what
happens between typing terraform apply and the resource existing, why the tool sometimes proposes to
destroy something you only renamed, and what you do at 3am when the state file is locked by a
pipeline run that died twenty minutes ago.
This article is about that part.
Who it's for
A working engineer who will have to operate the thing — not just write it once. You will have to defend the design in review, hand the repository to someone else, and get paged when it breaks. The article assumes you can use a terminal, have used at least one cloud console, and have never seriously used Terraform. It does not assume you know Go, HCL, or what a directed acyclic graph is.
If you already write Terraform daily, the pages worth your time are The Plan/Apply Lifecycle, The Dependency Graph, Meta-Arguments and Import & Refactoring.
The promise
Plain-English intuition first, then the precise mechanics underneath, then the trade-offs nobody advertises.
Most Terraform material picks one of those three. Tutorials give you the intuition and a working
snippet, then stop before anything is explained. Reference documentation gives you the mechanics with
no reason to care. Almost nobody writes down the third thing — that provisioner is a supported
feature and is nearly always the wrong answer, that -target is a debugging tool masquerading as a
workflow, that workspaces solve a problem most teams don't have while creating one they do.
Two more commitments:
- Every term is defined the first time it appears. Including the ones that seem obvious. "Drift", "state", "resource address", "backend" and "provider" all mean something specific and none of them mean what a newcomer guesses.
- Every runnable example exists in AWS, Azure and GCP. Terraform's pitch is provider-agnostic
syntax; an article that only ever shows
aws_*resources quietly undercuts its own argument. The running example is object storage — a bucket — for the whole article, so you are never learning a new cloud service and a new Terraform concept in the same paragraph. Where the three clouds do the same thing, you get one block and a note; where they genuinely diverge, you get all three side by side.
Read it in order
This matters more here than in the AWS and Azure articles, which are catalogues you can dip into by service. Terraform isn't a catalogue. It is one tool with about a dozen deep ideas, and the ideas depend on each other in a specific way.
The six stages are ordered by that dependency. Each answers the question what can you not correctly do without this? — so a topic appears as early as it can be understood, and as late as it must be to be understood correctly. Three consequences are worth flagging, because they're places where this article deliberately disagrees with how Terraform is usually taught:
- State is split in two. Early on you get only "there is a file that maps your configuration to
real objects, and you don't hand-edit it" — the minimum needed to run
applyhonestly. Backends, locking andstate mvwait until State & Backends, because state is only comprehensible once you can read a plan. countandfor_eachcome after the dependency graph, not with the rest of the syntax. Taught as syntax,for_eachlooks like a loop and you writecount = length(var.list)forever. Taught after the graph, "thefor_eachkey becomes the resource address" explains itself — and so does the most common self-inflicted production disaster in Terraform.- Import and refactoring is a mid-article survival skill, not an advanced flourish. Every real job is brownfield.
If you're impatient: Stages 0–2 are the point at which you can be trusted with a real repository under supervision. Stages 3–5 are the difference between writing Terraform and owning it.
The shape of every page
Every topic page has the same sections in the same order, so once you've read three pages you know where to scroll in the fourth. Small topics drop sections rather than padding them; surviving sections never reorder.
| Section | The question it answers |
|---|---|
| What & Why | What is it, what bad practice did it replace, and when is it the wrong tool? |
| Core Concepts | Every noun you'll meet — plain-English gloss, then precise definition |
| How It Works | What Terraform actually does: what happens at plan time, what gets written where, what forces replacement, how it fails |
| Getting Started | The smallest throwaway config that proves the behaviour. Config → commands → output, and the output is the lesson |
| In Practice | The version you'd ship: typed variables, pinned versions, what a reviewer should look for, blast radius, rollback |
| Ecosystem | The 3–5 things it's used with — other features, provider quirks, external tooling |
| Production | Security · Blast radius · Scale · Team workflow · Reliability |
| Interview Questions | Fifteen, in three tiers, with answers — including at least one "how does this differ across AWS, Azure and GCP?" |
Each page closes with a Commands & Gotchas block: the handful of commands and expressions you'd actually reach for on that topic, plus the behaviours worth memorising.
Contents
Legend: ✅ Available · 🚧 In progress · 📋 Planned
Stage 0 — Orientation
You can provision and destroy something real, and explain every file that appeared. · ~1 week
| # | Topic | What it covers | Status |
|---|---|---|---|
| 1 | Why IaC Exists | Click-ops and why it fails; drift as the real problem; declarative versus imperative; Terraform against CloudFormation, Bicep, Pulumi, Ansible, Crossplane | 🚧 |
| 2 | The Core Workflow | init → validate → plan → apply → destroy; what each command reads and writes; which are safe unattended |
🚧 |
| 3 | Anatomy of a Project | The .tf split as convention; .terraform/; the lock file; the state file; what belongs in git and what absolutely does not |
🚧 |
Stage 1 — The Language
You can write a multi-resource configuration from scratch without copying an example. · ~3 weeks
| # | Topic | What it covers | Status |
|---|---|---|---|
| 1 | HCL & the Type System | Blocks, arguments, attributes; primitives; list vs set vs map vs object vs tuple; conversion rules; why a .tf file is evaluated, not executed |
🚧 |
| 2 | Providers & the Registry | Providers as plugins; required_providers and the ~> constraint; the lock file; alias and multi-account; the auth chain for each cloud |
🚧 |
| 3 | Resources & References | The resource block; arguments versus attributes; implicit dependencies; (known after apply); resource addresses; data sources and when they're the wrong reach |
🚧 |
| 4 | Variables, Locals & Outputs | Typed inputs with validation and sensitive; the precedence order that trips everyone; locals versus variables; what outputs are really for |
🚧 |
| 5 | Expressions & Functions | Conditionals; for expressions; splat; dynamic blocks and when they're a mistake; templatefile; the functions worth memorising |
🚧 |
Stage 2 — The Machinery
You can look at a plan and say what will happen and why, before running it. · ~2 weeks
| # | Topic | What it covers | Status |
|---|---|---|---|
| 1 | The Plan/Apply Lifecycle | Parse → resolve → refresh → graph → diff → render; plan files; reading + - ~ -/+ <=; what forces replacement; why unknown values propagate |
🚧 |
| 2 | The Dependency Graph | What creates an edge; what parallelises and what serialises; -parallelism; walking the graph to explain an ordering surprise |
🚧 |
| 3 | Meta-Arguments | count versus for_each and the addressing consequence; depends_on as a code smell; the four lifecycle settings; per-resource provider |
🚧 |
Stage 3 — Structuring Real Code
A second engineer can clone your repo and apply to a different environment without asking you anything. · ~2 weeks
| # | Topic | What it covers | Status |
|---|---|---|---|
| 1 | Modules | Root versus child; a module as an interface; sources and pinning; composition versus nesting; when a module is premature | 📋 |
| 2 | Repo & Environment Structure | Directory-per-environment versus workspaces versus Terragrunt; .tfvars layering; where to put the state seams; the layout to pick and the one to reject |
📋 |
| 3 | State & Backends | Backend configuration; locking and contention; the bootstrapping chicken-and-egg; state list/show/mv/rm; why a state file is a security problem |
📋 |
Stage 4 — Working on a Team
A change reaches production through a pipeline with a review and an approval, and someone other than you can roll it back. · ~3 weeks
| # | Topic | What it covers | Status |
|---|---|---|---|
| 1 | Import & Refactoring | import blocks and config generation; moved and removed; renaming without destroying; adopting a brownfield estate |
📋 |
| 2 | Testing & Validation | fmt and validate; tflint; checkov/tfsec; check blocks; terraform test; Terratest and when the cost is justified |
📋 |
| 3 | CI/CD & Automation | Plan on pull request; apply on merge; OIDC instead of long-lived keys; approval gates; -detailed-exitcode; Atlantis as the alternative model |
📋 |
| 4 | Governance & Policy as Code | Sentinel and OPA/Conftest; HCP Terraform workspaces and run tasks; private module registries; cost gates | 📋 |
Stage 5 — Production & Depth
You can be on call for a Terraform-managed estate. · ~3 weeks
| # | Topic | What it covers | Status |
|---|---|---|---|
| 1 | Secrets & Sensitive Data | Secrets in state and plan output; the real limits of sensitive = true; ephemeral values and resources; the Vault provider; who can read the bucket |
📋 |
| 2 | Scale & Performance | Behaviour at thousands of resources; refresh cost; -parallelism; provider rate limits; when the answer is "split the state" |
📋 |
| 3 | Failure & Recovery | Partial applies; force-unlock and when it's safe; lost or corrupted state; restoring from a versioned backend; the drill you should have rehearsed |
📋 |
| 4 | Drift & Reconciliation | Detecting console changes; ignore_changes as concession versus surrender; continuous drift detection; what to do when reality is right |
📋 |
| 5 | Extending & the Ecosystem | The plugin protocol; provider-defined functions; writing a provider; Stacks; CDKTF; Terragrunt; OpenTofu; migrating from CloudFormation or Bicep | 📋 |
Reference
| Page | What it covers | Status |
|---|---|---|
| Glossary & Cheatsheet | Every term defined anywhere in the article, alphabetised and linked back; the CLI surface grouped by workflow; the expression forms worth knowing; the consolidated gotchas table | 📋 |
Versions, and the OpenTofu question
Written against Terraform 1.x. Where a feature has a minimum version, the page says so — import
blocks and check blocks arrived in 1.5, terraform test in 1.6, removed blocks in 1.7,
provider-defined functions in 1.8, ephemeral values in 1.10. If an example doesn't work for you,
terraform version is the first thing to check.
In August 2023, HashiCorp changed Terraform's licence from the Mozilla Public License to the Business
Source License, which restricts use in competing commercial products. The Linux Foundation forked the
last MPL version as OpenTofu, which remains open source and is broadly a drop-in replacement:
same HCL, same providers, same state format, tofu instead of terraform on the command line. The
two have since diverged somewhat in features, and OpenTofu has shipped things Terraform hasn't
(state encryption, for one).
For anyone learning, this changes almost nothing — everything in this article applies to both unless a page says otherwise, in which case it says so in one line rather than forking into two versions. For anyone choosing for an organisation, it's a licensing and roadmap question rather than a technical one, and it's worth reading the current licence text rather than a summary written at some point in the past.