How it works
The explanation, with examples you can run.
An S3 backend
terraform { backend "s3" { bucket = "example-tfstate-111122223333" key = "checkout/prod/terraform.tfstate" region = "eu-west-1" encrypt = true use_lockfile = true }}keyis the object path. Use one key per environment and component.use_lockfile = trueturns on S3 native locking: Terraform writes a.tflockobject beside the state using S3 conditional writes. The olderdynamodb_tablesetting still works, but the documentation marks DynamoDB-based locking as deprecated and due for removal; both can be set during a migration.- Enable bucket versioning, block public access, and restrict the bucket with IAM to the roles that run Terraform. Encryption with a KMS key you control adds an audit trail of who read state.
- Backend blocks cannot use variables. Pass environment-specific values with
terraform init -backend-config=prod.s3.tfbackendor keep a small root directory per environment.
Other backends follow the same model: azurerm (blob leases), gcs (lock objects), pg, kubernetes, consul, and HCP Terraform, which also runs plans and applies remotely and keeps state history.
Locking in practice
When a lock is held, the second run fails with a message of this shape (illustrative values; this one was not produced by a real S3 run):
Error: Error acquiring the state lock Lock Info: ID: 3c9f1a2e-... Path: example-tfstate-111122223333/checkout/prod/terraform.tfstate Operation: OperationTypeApply Who: ci@runner-17 Version: 1.16.6 Created: 2026-10-16 09:12:44 UTCDo not reach for -lock=false. If the holder really crashed (a killed CI job, a closed laptop), confirm nothing is still running and use terraform force-unlock LOCK_ID. Even the local backend locks: while an apply runs, a .terraform.tfstate.lock.info file sits next to the state.
Bootstrapping
The bucket for state has to exist before any configuration can use it. Common answers: create it once with a tiny separate configuration that uses local state (then migrate that state into the bucket with terraform init -migrate-state), or create it by hand under a documented, locked-down account. Either way, protect it with prevent_destroy and strict IAM; deleting it orphans everything.
Migrating backends
Change the backend block and run terraform init; Terraform offers to copy the existing state to the new location. Pull a backup first (terraform state pull), migrate when nobody else is running Terraform, and check that a plan afterwards shows no changes.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.