Moving Terraform State Off Your Laptop: Remote Backends and State Locking
Use a Terraform remote backend with state locking to prevent concurrent apply conflicts and share state safely across a team.
17 Mar 2026, 11:31 UTC

Two engineers, one state file, and a broken pipeline
Your team manages a small AWS footprint with Terraform. Everyone keeps terraform.tfstate on their laptop and pushes configuration to Git. One morning, two engineers run terraform apply at nearly the same time. Both read the same state file, both plan changes, and both write back. The second write overwrites the first, and resources that were just created disappear from Terraform’s view without being destroyed.
This is the classic local-state problem. Terraform uses a state file to map your configuration to real-world resources. When that file lives on individual machines, there is no single source of truth, no easy way to share it, and no built-in protection against concurrent edits.
The practical fix is to store state in a remote backend—a central storage service such as Amazon S3, Azure Blob Storage, or Terraform Cloud—and enable state locking so only one terraform apply can run at a time.
What a remote backend changes
A backend tells Terraform where to read and write the state file instead of saving it locally. With AWS, a common setup pairs an S3 bucket for state storage with a DynamoDB table for locking. When a run starts, Terraform creates a lock item in DynamoDB. If another run tries to start, it sees the lock and waits or fails. When the first run finishes, Terraform releases the lock.
State files can contain secrets in plain text, such as database passwords or private keys, so the bucket should have encryption at rest and access restricted to CI/CD roles and authorized humans.
Worked example: migrate local state to S3 with DynamoDB locking
Assumptions: Terraform 1.5 or later, the AWS provider configured, and permission to create S3 buckets and DynamoDB tables. Run these commands in your project directory on a local workstation or CI runner.
1. Create the backend resources
aws s3api create-bucket \
--bucket tf-state-example-12345 \
--region us-east-1
aws dynamodb create-table \
--table-name tf-state-locks \
--attribute-definitions AttributeName=LockID,AttributeType=S \
--key-schema AttributeName=LockID,KeyType=HASH \
--billing-mode PAY_PER_REQUEST
Replace tf-state-example-12345 with a globally unique bucket name. The DynamoDB table name can be anything you reference in the backend block.
2. Add the backend block
Open your configuration’s terraform block and add:
terraform {
backend "s3" {
bucket = "tf-state-example-12345"
key = "environments/prod/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "tf-state-locks"
encrypt = true
}
}
The key is the path inside the bucket. Use a different key per environment if you manage multiple.
3. Initialize and migrate
terraform init
Terraform detects the backend change and asks whether to migrate the existing local state. Type yes to copy terraform.tfstate into S3. Afterward, the local file is no longer the authoritative copy.
4. Verify locking
Open two terminal sessions. In the first, run terraform apply and let it reach the apply step. In the second, run terraform plan or terraform apply for the same workspace. The second session should report that it cannot acquire the state lock and will wait or exit. Do not force-unlock unless you are certain the first run is stuck; forcing a lock release while another process is writing state can corrupt it.
Trade-offs and limits
- Configuration drift: If someone edits resources in the AWS console outside Terraform, the remote state does not automatically update. Run
terraform planregularly to detect drift. - Backend migration is manual: Changing buckets or providers requires
terraform initand careful coordination. There is no undo button once state is copied. - Secrets exposure: Encryption at rest protects the bucket, but anyone with
s3:GetObjectcan read the state file. Limit IAM permissions and review what your provider writes into state.
Check that it worked
After a successful terraform apply, inspect the bucket:
aws s3api get-object \
--bucket tf-state-example-12345 \
--key environments/prod/terraform.tfstate \
state.json
The downloaded file should contain a resources array matching your configuration. If the file is missing or outdated, the backend is not configured correctly.
Bottom line
Stop passing terraform.tfstate around over chat or email. A remote backend with locking turns state from a shared liability into a coordinated, auditable part of your delivery pipeline. Start with one non-production environment, verify the lock behavior, then expand.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.