Terraform
Advanced 15 min read Terraform

Terraform State & Backends

Where the plan's 'reality' lives

State is Terraform's memory; remote backends make it accurate and team-safe. Locking prevents concurrent applies; versioning enables rollback; workspaces/folders isolate environments. Treat state as a secret and a backup.

statebackendlockremoteworkspaces3gcsazure
Before we start

What you will learn

  • Set up a remote backend (S3+GCS+Az) with locking + encryption
  • Manage state lifecycle: init-migrate, refresh, rollback
  • Choose workspaces vs directory-state isolation
In 60 seconds

Local state keeps memory on your disk — fine for solo demos. Remote state (+ DynamoDB/consul lock + versioning) makes plans accurate across a team. State contains sensitive attribute values; protect it.

Introduction

What is this?

Backend types: local (data in dir), S3 (bucket, key, region; optional dynamodb lock, encrypt), GCS (bucket + prefix), Azure (blob + LeaseId), Terraform/OpenTofu Cloud remote. Lifecycle: terraform init -migrate-state moves it; refresh reads live values into state; state mv/rm/pull/push inspect/edit; import binds existing; tainted/untaint (legacy). Workspaces: multiple named states on one backend (convenient but couples state/config); preferred isolation: separate directory/repo + remote state per env. Lock: Dynamo GSI ItemCondition; lost locks delete manually. State roles: breadcrumb of apply, prevents orphans, zero-sum deletion reality.

Motivation

Why do you need it?

  • Without remote+locked, two engineers apply the same state and clobber each other.
  • State drift = plans built on fiction; refresh is cheap.
  • State rollback via object versioning repairs a bad apply without archaeology.
  • A state bucket in S3 is a backup plus an audit trail — a team superpower.
Think about it

Mental model

Imagine

A hospital census board that updates after every discharge

Think of

State + refresh

The board (state) must reflect ward occupancy (live infra) before planning staffing (plans). If someone updates it from home without the register (no lock), two shifts (engineers) plan around stale numbers.

Mechanics

How it works

  • init reads backend config and downloads/locks state.
  • plan refreshes attributes from provider reads into state.
  • apply writes the final value set, holding the lock until done.
  • Versioning means each commit of state is a recoverable snapshot.
  • Workspaces/dir-states isolate envs so prod and dev plans never merge.

Remote state with lock.

Engineer A
Lock
State object
Engineer B
Hands-on

Examples

A team-safe backend block

A team-safe backend block
terraform {
backend "s3" {
bucket = "acme-tfstate"
key = "envs/prod/site.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
# runtime guardrails:
# - bucket policy: deny unencrypted/versioned-off writes
# - lock table required (dynamo)
# - state read = least privilege; no commits of state files to git

Isolation by key, concurrency by lock table, durability by versioning+encryption — the remote-state trifecta.

Real world

In production

  • Per-environment directories each with remote state key (prod read-locked to a narrow principal).
  • State rollback from version history when a bad apply swept a resource.
  • Import drift correction: state refresh + import for resources pre-dating IaC.
Guidelines

Best practices

  • Always remote + locked outside throwaway local demos.
  • Treat state as sensitive: encrypt the bucket, scope IAM read tightly.
  • Never commit .tfstate files; make .gitignore enforce it.
  • Migrate state with -migrate-state and reload, not by copying files around.
  • Prefer directory-per-env + remote key over workspace nesting when envs have heavy config (like k8s contexts).
Watch out

Common mistakes

  • Local state in the repo — a literal time-bomb for reviews and storage.
  • Deleting a lock to 'unstick' a stuck apply while another apply is mid-flight.
  • State file pushes over ssh — breaks checksums and leaves stale copies.
  • Ignoring state permission erosion: the state bucket becomes a data leak if leaked.
When things break

Debugging tips

  • State lock Held → find LeaseID in the lock table, confirm no concurrent apply, release.
  • Plan reads churn → a data source/provider read mismatch; run refresh and diff.
  • State rollback without versioning → use import + plan to reconstruct; configure versioning next time.
  • Migrating state between backends failing → ensure both backends authenticated and keys consistent.
Interviews

Interview checkpoint

Practice

Hands-on lab

Remote State with Lock for a Team
40 min · advanced
In progress
Objectives
  • Create state bucket + lock table
  • Point a module's backend at it and migrate state
  • Simulate a blocking concurrent apply and release the lock

Remote State with Lock for a Team

40 min · advanced

0/4 steps
0%
sandbox · Linux sandbox
$
Try these commands
Test yourself

Quick quiz

0/2
1

Which feature prevents two concurrent applies?

2

The upgrade path from local to remote state uses:

Go deeper

Official documentation