Skip to content

Remote Backends and Locking

A remote backend stores state in a shared location like Cloud Storage instead of on one person’s laptop, and locking stops two people from writing to that shared state at the same time.

By default, terraform.tfstate lives on the machine where you ran apply. That is fine for a solo experiment, but it breaks down the moment a second person joins:

  • Teammate A runs apply and creates a VPC network. Their laptop’s local state file now knows about it.
  • Teammate B, on a different laptop, has never seen that state file. They run plan against their own stale or empty local state and either try to recreate the VPC network or get a plan that makes no sense next to what actually exists.
  • CI/CD pipelines have the same problem in a sharper form — a pipeline runner is a fresh, disposable environment with no local state file at all. It needs a canonical copy of state to read from and write to on every run, not a copy that happens to be sitting on someone’s laptop.

The fix is to move state off any individual machine and into a shared backend that every team member and every CI run points at.

The standard way to store Terraform state on Google Cloud is a Cloud Storage bucket configured as the backend:

terraform {
backend "gcs" {
bucket = "my-company-terraform-state"
prefix = "networking/state"
}
}

The bucket must already exist before you point a backend at it — Terraform will not create it for you. bucket names the Cloud Storage bucket, and prefix is the path within that bucket under which this configuration’s state object is stored, so different configurations (or different Terragrunt units) typically use different prefixes in the same bucket.

Notice there is no locking argument to set. The GCS backend has state locking built in — it is an inherent part of how the backend works, not an opt-in feature. Cloud Storage’s own object generation and versioning mechanism is what Terraform uses under the hood to make sure only one write to a given state object succeeds at a time, so two concurrent runs cannot corrupt it. There is no separate lock table to create, no extra argument to flip on, and no additional GCP resource to pay for and keep in sync with the state bucket. This is genuinely simpler than the locking story on some other clouds, where a separate locking resource has historically had to be provisioned alongside the state bucket.

It is still worth turning on Object Versioning on the state bucket itself. Versioning is not what provides locking — locking is automatic and always on — but it gives you a recovery path if a bad apply overwrites state with something you did not want.

Imagine locking did not exist. Two people, or a person and a CI run, both execute terraform apply against the same GCS-backed state at nearly the same moment. Both read the current state, both compute a plan, and both start writing their results back to the same state object. Depending on timing, the second write can overwrite the first — silently dropping the first run’s recorded changes even though the first run’s infrastructure changes actually happened — or the state file can end up corrupted mid-write.

A lock turns that race into a queue. The first apply to start acquires the lock; the second apply either waits for the lock to be released, or fails immediately with a clear “state is locked” error, depending on the flags used. Either way, the second run does not get to write over the first run’s state while it is mid-flight.

Terminal window
# A run that finds the state already locked fails fast with a clear message,
# rather than corrupting the state file
terraform apply
# Error: Error acquiring the state lock
flowchart LR
  eng1["Engineer A: terraform apply"] -->|acquires lock| gcs["GCS state object"]
  eng2["CI run: terraform apply"] -->|blocked, waits or fails| gcs
  gcs -->|lock released after apply| eng2
Two applies race for the same GCS-backed state; the built-in lock lets one through and makes the other wait
Why does a local terraform.tfstate file break down once a second team member starts running Terraform
What extra resource or argument does the GCS backend need configured to enable state locking
What does state locking actually prevent