Move Terraform State to S3 with DynamoDB Locking Before Your Team Breaks It
Migrate Terraform state from local files to an AWS S3 backend with DynamoDB locking, then verify concurrency protection with parallel applies.
06 Dec 2025, 02:14 UTC

Why Local State Breaks in a Team
Terraform’s .tfstate file maps every resource in your configuration to the real infrastructure it created. When that file lives on a laptop or is committed to Git, two people can run terraform apply at the same time and overwrite each other’s changes. The state file also often contains secrets in plaintext, so sharing it through version control exposes credentials to anyone with repo access.
A remote backend stores state in a shared service such as AWS S3, Azure Blob Storage, or Terraform Cloud. State locking adds a coordination layer so only one Terraform operation can modify the state at a time.
Migrate to an S3 Backend with DynamoDB Locking
This example assumes Terraform 1.0 or later and uses an S3 bucket for storage with a DynamoDB table for locking. Create the bucket and table before editing Terraform code.
Create the backend resources.
- S3 bucket: versioning enabled, default encryption.
- DynamoDB table: primary key named
LockID(string).
Add the backend block. Run this in the directory that contains your root
main.tf, creatingbackend.tfor adding to an existing file:terraform { backend "s3" { bucket = "" key = "/terraform.tfstate" region = "" dynamodb_table = "" encrypt = true } }Replace placeholders with your bucket, environment prefix, region, and DynamoDB table name.
Initialize and migrate state. Run in your terminal:
terraform initIf a local state file exists, Terraform asks whether to copy it to the new backend. Confirm with
yes. The command configures the remote connection and uploads the state.Verify the remote state file. Check the S3 bucket for
<ENVIRONMENT>/terraform.tfstate. Confirm the object exists and encryption is enabled. Do not edit the file directly in the console.
Test State Locking with Concurrent Applies
State locking prevents two terraform apply runs from corrupting the same state file. You can verify this behavior locally.
- Open Terminal A and run:
- While Terminal A is still running, open Terminal B and run the same command.
- Terminal B should wait or return an error indicating another operation holds the lock. When Terminal A finishes, the lock releases and Terminal B can proceed.
terraform apply -auto-approve
If your backend does not support locking, Terraform proceeds without a lock and may warn you. DynamoDB is not required for S3 storage, but it is required for locking with the S3 backend.
Trade-offs and Operational Limits
IAM permissions: The identity running Terraform needs
s3:GetObject,s3:PutObject, and DynamoDB actions such asdynamodb:PutItem,dynamodb:GetItem, anddynamodb:DeleteItemon the lock table. Missing permissions cause init or apply to fail.Lock persistence: If a process crashes while holding a lock, the lock can remain until it expires. Terraform retries automatically, but long outages can block runs.
Backend migration risk: Moving state changes where Terraform reads and writes truth. Back up the local
.tfstatebefore runningterraform init, and test in a non-production workspace first.Not all backends lock: Some object-storage backends lack native locking. Review backend documentation before assuming protection is active.
Rollback and Recovery
Because terraform init migrates state to S3, keep a copy of the pre-migration local state file. If migration fails or state appears corrupted, you can repoint the backend block to a local file, run terraform init, and restore from the backup. Avoid manually editing the remote state file; use terraform state subcommands for safe changes.
Moving state off laptops and enabling locking removes the most common causes of infrastructure drift in collaborative Terraform projects. Start with one non-production project, verify the lock under concurrent runs, then expand to the rest of your estate.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.