CoursesInfrastructure as Code & automationIntermediate · Terraform at scale

Remote state & locking

State for teams, done safely.

Intermediate14 min · lesson 10 of 23

The local state file works for one person experimenting, but the moment a team shares infrastructure it breaks down: everyone has a different copy, nobody has the truth, and two people applying at once corrupt it. A remote backend solves this by storing state in a shared, central location — an S3 bucket, Terraform Cloud, a GCS bucket — so the whole team (and CI) reads and writes the same state. This is the first thing to set up for any real Terraform project.

AN APPLY WITH REMOTE STATE + LOCKING
1Acquire lock
DynamoDB lock — concurrent applies are blocked
2Read shared state
from the S3 remote backend (encrypted at rest)
3Apply changes
write updated state back safely
4Release lock
the next apply can now proceed
Locking serializes applies so two runs cannot interleave and corrupt shared state.
backend.tf
terraform {
backend "s3" {
bucket = "acme-tfstate"
key = "prod/network/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "tf-locks" # the lock table (see below)
encrypt = true # encrypt state at rest
}
}

State locking: the critical piece

Shared state introduces a new danger — two people (or two CI runs) applying simultaneously can interleave writes and corrupt the state file. State locking prevents this: before an apply, Terraform acquires a lock (via a DynamoDB table for the S3 backend, or built in for Terraform Cloud), does its work, and releases it; anyone else who tries to apply meanwhile is blocked until the lock frees. Without locking, remote state is a shared file two people can clobber; with it, applies are serialized and safe. Always configure locking with your backend.

terminal
# with locking, a concurrent apply is blocked rather than corrupting state:
$ terraform apply
Error: Error acquiring the state lock
Lock Info:
ID: 4f2a...
Who: deploy@ci-runner-3
Created: 2026-07-03 10:14:02 # someone else is applying — wait, do not force
# (terraform force-unlock exists for a truly stuck lock — use it very carefully)

Security and separation

Because state holds secrets in plaintext, the remote backend must protect it: enable encryption at rest (encrypt = true, plus the bucket’s own encryption), lock down access with IAM so only the right people and CI can read it, and enable versioning so a corrupted or bad state can be rolled back. It is also good practice to split state by environment and component (separate state files for prod-network, prod-db) so a mistake in one has a small blast radius and applies are faster — which is exactly what the key = "prod/network/..." path above is doing.

The state backend is production infrastructure — protect it
Your remote state backend holds the plaintext secrets and the map of everything Terraform manages, so its security is your infrastructure’s security: an attacker who reads state gets your secrets, and one who corrupts it can wreak havoc on your next apply. Encrypt it, restrict access tightly with IAM, enable versioning for recovery, and never make the state bucket public. And configure locking before your team grows — discovering you had no locking after two applies corrupted state is a painful way to learn.