Remote backends and locking

Terraform · Practical · 10 min

Teams and CI must share one authoritative state, and concurrent applies corrupt it.

A backend decides where state lives. The default local backend writes a file next to your code, fine for experiments only. For anything shared, use a remote backend:

  • s3 (AWS): state in a bucket; locking with a lock file in the bucket via use_lockfile = true (the older DynamoDB table locking is deprecated).
  • gcs (Google Cloud) and azurerm (Azure Storage): built-in locking.
  • remote/cloud block for HCP Terraform or Terraform Enterprise, which also run plans for you.
  • Others: pg, consul, kubernetes, http.

Locking: an operation that may write state takes a lock first. If someone else holds it, you get "Error acquiring the state lock" with the holder's details. Wait; only use terraform force-unlock <ID> when you are sure the holder crashed.

Good practice for the storage itself: enable bucket versioning (to recover from a bad write), encryption, tight access control and block public access. Create the state bucket once, outside the configuration that uses it (a small bootstrap configuration or by hand).

Backend blocks can't use variables. Pass environment-specific settings with partial configuration: terraform init -backend-config=prod.s3.tfbackend.

Changing backends: update the block and run terraform init -migrate-state, which copies the state across.

Example

terraform {
  backend "s3" {
    bucket       = "acme-terraform-state"
    key          = "network/prod/terraform.tfstate"
    region       = "eu-west-1"
    encrypt      = true
    use_lockfile = true      # S3-native locking (Terraform 1.10+)
  }
}
# terraform init -migrate-state      # move existing local state into S3
# A second concurrent apply:
# Error: Error acquiring the state lock
#   Lock Info:
#     ID:        6f1c9a7e-...
#     Who:       ci@runner-42
#     Operation: OperationTypeApply

Remember

  • Remote backend + locking for any shared state
  • S3 uses use_lockfile; GCS/Azure lock natively
  • Version and encrypt the state bucket; force-unlock only when sure

Watch out for

  • Using force-unlock while another apply is genuinely still running.

Check yourself

  1. Why can't you write bucket = var.state_bucket in a backend block?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: developer.hashicorp.com/terraform/language/backend/s3