Migrating Terraform State from Local Files to an S3 Remote Backend
Move team infrastructure state out of Git and into a locked remote backend to prevent concurrent edits and secret leaks.
05 Dec 2025, 22:37 UTC

The problem: local state does not scale to teams
When you run terraform apply with the default backend, Terraform writes the state of every managed resource to a local file named terraform.tfstate. That file is a JSON record of what exists in your cloud account, and it often contains secrets such as database passwords or API keys in plain text.
Teams that share this file through Git run into two problems. First, Git cannot merge state files safely. If two engineers run terraform apply at the same time, the second upload can overwrite the first, corrupting infrastructure or causing Terraform to delete resources it still manages. Second, committing state to a repository exposes secrets to anyone with read access to the repo.
The practical takeaway is to store state in a remote backend that serializes writes with a lock. For AWS users, that usually means an S3 bucket for state storage and a DynamoDB table for locking.
Configure an S3 backend with DynamoDB locking
A backend block tells Terraform where to store state instead of the local filesystem. The following example assumes you already have an S3 bucket and a DynamoDB table created in the same AWS region. Replace the placeholders with your own names.
terraform {
backend "s3" {
bucket = ""
key = "environments/prod/terraform.tfstate"
region = "us-east-1"
dynamodb_table = ""
encrypt = true
}
}
Run this from your local workstation where you normally execute Terraform. You need AWS credentials with permission to read and write objects in the bucket, and to perform dynamodb:GetItem, dynamodb:PutItem, and dynamodb:DeleteItem on the lock table. Incorrect permissions can cause silent failures or leave the state locked after a run.
The key value is the path inside the bucket. Using a separate key per environment—for example, environments/staging/terraform.tfstate—keeps a mistake in one environment from corrupting another.
Migrate state and verify the move
After adding the backend block, run:
terraform init
Terraform detects that local state exists and remote state does not, then prompts you to copy the local state to the configured backend. Confirm the migration. This command changes where state is stored, so treat it as a state-changing operation.
To verify the migration worked without altering infrastructure, run:
terraform plan
If the state moved successfully, Terraform should report that no changes are needed. You can also run terraform state list to confirm Terraform can read resources from the remote backend.
If two engineers run terraform apply simultaneously, DynamoDB creates a lock on the first run. The second run waits or fails with a locking error instead of overwriting state. If a run crashes while holding the lock, Terraform may leave the state locked. You can inspect the lock record in DynamoDB, and only as a last resort run terraform force-unlock <lock-id>. Forcing an unlock while another process is actively writing state can cause corruption.
Trade-offs and limitations
- Remote backends incur cloud storage costs. S3 and DynamoDB charges are small for most teams, but they are not zero.
- State files can still contain sensitive values. Encryption at rest helps, but access to the bucket or table grants access to state contents.
- Migrating to a remote backend does not split state by itself. If you manage all environments in one state file, a single error can still affect every environment. Consider separate state files or workspaces per environment.
terraform force-unlockis dangerous. Use it only after confirming no other Terraform process is running.
Roll back to local state if needed
Because migration changes where state lives, you can revert by removing the backend block, running terraform init again, and copying the state file back to your local working directory. This restores the original concurrency and secrecy risks, so treat rollback as a temporary measure rather than a long-term pattern.
Check the result before you rely on it
After migration, confirm the following:
terraform planshows no pending changes.terraform state listreturns the expected resources.- The S3 bucket contains the state object at the path you configured.
Verify these commands in a non-production account before applying them to production infrastructure. If any check fails, do not run terraform apply. Revisit the backend permissions and bucket path first.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.