Lesson 0003 · Understand Terraform

Remote state & locking

One idea · ~12 min. State stops being a file on your laptop and becomes a shared, locked, versioned artifact.
Why this next? You aced single-player state. Everything that makes Terraform feel "professional" — team workflows, CI/CD, project structure — starts with one move: making the state file remote. And the locking story changed in Terraform 1.10/1.11, so most tutorials you'll find online are now teaching the deprecated way.

Recall reason #4 that state exists: syncing. A state file on your laptop means your laptop is the source of truth for production. Two laptops, two truths — and the three-picture model breaks, because there are now two "State File" pictures fighting over one Real World.

The fix is one block: the backend

A backend is where state lives and how it's locked. It's configured in the terraform {} block and activated at init — it is not part of state itself.

terraform {
  backend "s3" {
    bucket       = "acme-terraform-state"
    key          = "prod/network/terraform.tfstate"  # one state file per key
    region       = "us-east-1"
    encrypt      = true
    use_lockfile = true                             # S3-native locking, TF ≥1.10
  }
}

Three properties this buys you: sync (every run starts from the latest state), locking (a state-writing run takes a lock first — concurrent applies can't corrupt state), and recovery (bucket versioning makes a bad state write a rollback, not a disaster).

Modern-Terraform alert: the classic setup used a DynamoDB table for locking. Terraform 1.10 introduced use_lockfile = true (S3 conditional writes create a .tflock object next to your state), and the DynamoDB arguments are deprecated since 1.11. If a tutorial shows dynamodb_table = …, it's dated.

Two more facts that do real work: the key is your isolation strategy — locking is per state file, so teams split state by environment and component (prod/network, dev/app, …); that's the seed of next lesson. And state can contain secrets in plaintext — which is why the bucket is encrypted and private, and why state never, ever goes in git.

You're the on-call engineer — you call it

Six scenarios. Reason from: one shared memory, lock-per-state-file, config-wins.

✅ Prove it on your own machine (optional, 10 min, real AWS)

  1. Create a state bucket: versioning on, default encryption, public access blocked.
  2. Take any config with local state, add the backend "s3" block above (with use_lockfile = true), run terraform init — say yes when it offers to migrate your state. Confirm terraform.tfstate is now empty/gone locally.
  3. Run a slow terraform apply in one terminal; in a second terminal run terraform plan — watch the lock error, then retry with -lock-timeout=2m and watch it wait. While the apply runs, look for the .tflock object in the bucket.
  4. In S3, open the state object's version history — there's your rollback story.
💬 Good follow-ups for this lesson: "How do separate states reference each other's outputs?" (terraform_remote_state — coming with project structure) · "What does the .tflock object actually contain?" · "How do teams bootstrap the state bucket itself — isn't that a chicken-and-egg problem?" (great question — ask me).