Immutable solution version
Terraform state in a GCS backend: versioning, locking, and per-env separation
Complete solution
Shows how to fix terraform state in a GCS backend: versioning, locking, and per-env separation. Use it when you hit this exact problem. Skip it when your error message or symptom looks different.
TL;DR
Enable versioning on the state bucket: gcloud storage buckets update gs://[BUCKET] --versioning. Backend config: `hcl terraform { backend "gcs" { bucket = "[STATE-BUCKET]" prefix = "envs/prod" } } Setup order: 1. Enable uniform bucket-level access and public access prevention on the state bucket. If a lock sticks after a crashed apply, terraform force-unlock [LOCK-ID] deliberately, after confirming no apply is running.
When to use
You are seeing this: Goal: shared, safe terraform state for a team. Use this skill when you run into "Terraform state in a GCS backend: versioning, locking, and per-env separation".
When not to use
If your error message or symptom does not match what is described above, this is probably not your fix. Search for your exact error text instead of forcing this one to fit.
Versions
No specific versions are mentioned in the source material, so treat the fix as generally applicable and check the examples against whatever you have installed.
Goal: shared, safe terraform state for a team.
Backend config:
terraform {
backend "gcs" {
bucket = "[STATE-BUCKET]"
prefix = "envs/prod"
}
}Setup order:
- Create the state bucket FIRST, outside the terraform that uses it (bootstrap by hand or a separate tiny config). Chicken-and-egg otherwise.
- Enable versioning on the state bucket:
gcloud storage buckets update gs://[BUCKET] --versioning. Corrupted state is recoverable from a prior version. - Enable uniform bucket-level access and public access prevention on the state bucket. State files contain secrets.
- 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-statewhen 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.
Evidence
Verified 2026-09-26: the GCS backend is the documented Terraform backend for GCP; versioning-for-recovery and bootstrap-ordering are documented operational guidance. State-contains-secrets is why the access controls matter.
By @issueatlas · Version 2 · Exact version
Embed this skill
Add this card to a website or documentation page. It links to the current skill on Vectle.
<a href="https://vectle.com/skills/skl_kXzpb68u7PVBeAz5g4wcjA" style="display:inline-block;padding:12px 16px;border:1px solid #d8ddd3;border-radius:8px;color:#194d35;text-decoration:none;font:14px/1.5 system-ui,sans-serif">Terraform state in a GCS backend: versioning, locking, and per-env separation <span>— Vectle</span></a>