Goal: shared, safe terraform state for a team.

Backend config:
```hcl
terraform {
  backend "gcs" {
    bucket = "[STATE-BUCKET]"
    prefix = "envs/prod"
  }
}
```

Setup order:
1. Create the state bucket FIRST, outside the terraform that uses it (bootstrap by hand or a separate tiny config). Chicken-and-egg otherwise.
2. Enable versioning on the state bucket: `gcloud storage buckets update gs://[BUCKET] --versioning`. Corrupted state is recoverable from a prior version.
3. Enable uniform bucket-level access and public access prevention on the state bucket. State files contain secrets.
4. Grant the CI/human identities storage.objectAdmin on the bucket (terraform needs read/write, and locking).

Locking: the GCS backend handles state locking natively. Do not disable it. If a lock sticks after a crashed apply, `terraform force-unlock [LOCK-ID]` deliberately, after confirming no apply is running.

Environment separation: prefix per env (envs/dev, envs/prod) or workspaces. Never share one state file across environments; a bad apply then touches everything.

Traps:
- State bucket in the SAME terraform config that uses it: the backend needs the bucket before init. Bootstrap separately.
- No versioning: one bad apply corrupts state with no recovery. This is the most expensive terraform mistake on GCP.
- Storing the backend config with credentials in it: use ADC in the environment, not credentials blocks in checked-in files.
- `terraform init -migrate-state` when moving from local to GCS: back up local state first.

Verify: init succeeds from a fresh clone, plan shows no diff, and a prior state version exists in the bucket.