Terraform — Part 7 — Remote State and State Locking
Part 6 closed on local state’s limits: no locking, and tied to one laptop. This part covers the fix — moving state into a shared remote backend, and why locking is what actually makes team use of Terraform safe.
apply against that same state at the same time.
Where Terraform stores state — local by default, or a shared remote location like an S3 bucket.
A mechanism that blocks a second apply from running while one is already in progress against the same state.
Moving state from one backend to another, handled automatically by terraform init.
Introduction
Part 6 ended on local state’s two limits: it has no locking, and it lives on one machine. Neither is a problem while you’re the only person running Terraform. Both become real problems the moment a second person, or a CI pipeline, needs to run apply against the same infrastructure.
What a Backend Is
A backend determines where Terraform stores its state data. Every configuration has one, whether it’s declared explicitly or not — the default, used implicitly in every example so far in this series, is the local backend, which simply writes terraform.tfstate to the working directory.
A backend is configured inside the terraform block:
terraform {
backend "s3" {
bucket = "example-terraform-state"
key = "project/terraform.tfstate"
region = "us-east-1"
}
}
A configuration can only have one backend block. It also can’t reference variables, locals, or data sources — backend configuration has to be resolvable before Terraform has read any of the rest of the configuration, since it’s what tells Terraform where to even look for state in the first place.
terraform init again. Terraform detects the change and offers to migrate existing state into the new backend automatically.
Why Remote State
A remote backend puts state somewhere every team member, and any CI/CD pipeline, can reach — instead of on whoever happened to run apply first. This is the same “reproducible, not personal” theme from Part 1’s case against click-ops, applied to state itself: infrastructure that only one laptop knows the true status of isn’t meaningfully reproducible.
Common backend types include s3 (AWS), azurerm (Azure Blob Storage), gcs (Google Cloud Storage), and remote for HCP Terraform, alongside several others for platforms like Consul, Kubernetes, and PostgreSQL. Which one you use generally follows which cloud your infrastructure already lives in.
Example: The S3 Backend
The s3 backend takes three required arguments: bucket, the S3 bucket state gets stored in; key, the path to the state file within that bucket; and region, the AWS region the bucket lives in.
What Locking Prevents
Imagine two engineers both run terraform apply against the same remote state within seconds of each other. Without locking, both read the same starting state, both compute a plan, and both start writing changes — and whichever one finishes writing state last silently overwrites the other’s changes, with no error and no warning.
State locking prevents exactly this. When one apply starts, it acquires a lock; a second apply against the same state has to wait until the first one finishes and releases it.
How Locking Works: S3 as an Example
Locking mechanics are backend-specific. On the S3 backend, the current approach is native S3 locking: setting use_lockfile = true makes Terraform write a lock file alongside the state object in the same bucket while an operation is in progress.
terraform {
backend "s3" {
bucket = "example-terraform-state"
key = "project/terraform.tfstate"
region = "us-east-1"
use_lockfile = true
}
}
LockID partition key. That approach is now deprecated in favour of native S3 locking, though it’s still supported and both can be configured together during a migration.
Other backends handle locking through whatever native mechanism their platform offers — the azurerm backend uses Azure Blob Storage’s own lease capability, for instance. The mechanism differs by platform; the guarantee it provides is the same everywhere: only one write in flight against a given state at a time.
Migrating to a Remote Backend
Moving from local state to a remote backend is a matter of adding the backend block and re-running terraform init, which Part 3 already covered as backend initialisation being one of init‘s core jobs:
# After adding or changing a backend block, Terraform detects
# it and offers to copy existing state into the new backend
terraform init
Terraform prompts before copying anything, so existing state isn’t silently abandoned mid-migration.
Quick Reference Summary
| Term | Meaning |
|---|---|
| Backend | Where Terraform stores state — local by default, or a shared remote store. |
| Locking | Blocks a second concurrent write to the same state. |
use_lockfile |
S3 backend argument enabling native S3-based locking (current recommended approach). |
| DynamoDB locking | The older S3 backend locking mechanism, now deprecated in favour of native locking. |
| State migration | Copying existing state into a newly configured backend, handled by terraform init. |
Final Thoughts
Remote state and locking together are what turn Terraform from a personal scripting tool into something a whole team can run safely against the same infrastructure. Neither changes anything about the configuration language itself — they change where state lives and who’s allowed to write to it at any given moment.
Terraform — Part 8 — Data Sources vs Resources. We cover how to read information about infrastructure Terraform doesn’t manage, without accidentally trying to create or change it.