Terraform Remote State Locking with Amazon S3 and DynamoDB: Architecture Note
Learn the minimal, secure design for Terraform remote state locking using Amazon S3 and DynamoDB, including trust boundaries, operational checks, failure modes, and when to revisit the architecture.
08 Apr 2026, 01:32 UTC

Requirements
When using Terraform in a team or automated pipeline, concurrent runs must not corrupt the shared state file. The solution needs to:
- Store the state durably and immutably.
- Provide a lock mechanism that serializes Terraform operations.
- Restrict access to principals that truly need to read or write state.
- Enable detection of lock‑contention or storage problems.
Smallest Suitable Design
The minimal configuration that satisfies the requirements consists of:
- An Amazon S3 bucket that holds a single
terraform.tfstateobject (or a hierarchy under a key prefix). - A DynamoDB table used exclusively for Terraform locks, with a primary key named
LockIDof type String. - An IAM policy that grants the least‑privilege permissions:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": "arn:aws:s3:::my-terraform-state-bucket/path/to/state/terraform.tfstate"
},
{
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:DeleteItem"
],
"Resource": "arn:aws:dynamodb:us-east-1:123456789012:table/terraform-locks"
}
]
}
The backend configuration in backend.tf references these resources:
terraform {
backend "s3" {
bucket = "my-terraform-state-bucket"
key = "path/to/state/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
Trust and Data Boundaries
The S3 bucket is a trust boundary because the state file may contain secrets (e.g., database passwords, API keys). Treat it as follows:
- Enable S3 bucket versioning to protect against accidental overwrites.
- Enable server‑side encryption (SSE‑S3 or SSE‑KMS) so data at rest is unreadable without the appropriate IAM or KMS permissions.
- Restrict the bucket policy (or IAM policy) to the specific state key; avoid wildcard
s3:*on the bucket or account. - The DynamoDB lock table holds only lock metadata (LockID, timestamp, etc.). No application data resides there, so its trust boundary is lower, but it must still be protected from unauthorized write access.
Operational Checks
After terraform init, verify that locking works as expected:
- Run
terraform applyin one terminal; note the lock ID printed in the output (e.g.,Lock ID: 0123456789abcdef). - In a second terminal, run any Terraform command that would modify state (e.g.,
terraform apply -target=aws_instance.example). The command should block or return an error likeError acquiring the lock. - Inspect the DynamoDB table for an item where
LockIDmatches the value shown in step 1. No further interpretation of the item is needed; its presence indicates an active lock. - Enable CloudWatch metrics on the DynamoDB table (SuccessfulRequestCount, UserErrors, ThrottledRequests) and on the S3 bucket (4xx/5xx error rates). Create alarms that trigger when:
- ThrottledRequests > 0 for DynamoDB (indicates lock‑acquisition pressure).
- 4xx/5xx errors > 0 for S3 (indicates access problems).
Additionally, enable S3 server‑access logs and CloudWatch Logs for DynamoDB to capture GetItem, PutItem, and DeleteItem calls during Terraform runs. These logs provide an audit trail without altering the locking behavior.
Failure Modes
Understand how the design behaves when underlying services degrade:
- DynamoDB throttling or network partition: Terraform will wait for the lock until the configured timeout (default 10 minutes) and then fail with a clear lock‑acquisition error. The state file remains unchanged because no write occurs while the lock is held.
- S3 eventual consistency (if versioning disabled): A concurrent read might retrieve an older version of the state, leading to a stale plan. Keeping versioning enabled guarantees that any
GetObjectreturns the latest version for the requested key. - IAM policy too permissive: If credentials are leaked, broad permissions increase the blast radius. The least‑privilege policy above limits the impact to state read/write and lock operations only.
- Manual state edits or external tools: The DynamoDB lock only protects against concurrent Terraform invocations using the same backend. Direct edits to the S3 object or state file by other automation can still cause conflicts; treat the bucket as a controlled artifact and prohibit out‑of‑band modifications.
Conditions That Would Change the Design
Re‑evaluate the architecture when any of the following become true:
- Multi‑region disaster recovery is required: replicate the S3 bucket via Cross‑Region Replication and either use a global DynamoDB table (with appropriate partition key design) or maintain separate lock tables per region, ensuring that lock semantics remain region‑local.
- Lock contention becomes a regular bottleneck: consider migrating to Terraform Cloud/Terraform Enterprise, which provides a managed locking service with higher throughput and built‑in audit.
- Regulatory mandates demand stricter encryption controls: switch to SSE‑KMS with a customer‑managed CMK and rotate the key according to policy.
- The state file grows beyond practical S3 object limits (unlikely, but if using very large state shards): evaluate splitting state into multiple buckets with corresponding lock tables, or adopt a remote‑state data source pattern.
Practical Verification Checklist
- Bucket versioning enabled:
aws s3api get-bucket-versioning --bucket my-terraform-state-bucketreturnsStatus: Enabled. - Server‑side encryption active:
aws s3api get-bucket-encryption --bucket my-terraform-state-bucketshows AES256 or aws:kms. - DynamoDB table primary key schema:
aws dynamodb describe-table --table-name terraform-locksincludesAttributeName: LockID, KeyType: HASH. - IAM policy attached to the Terraform runner role/user grants only the actions listed above.
- After a successful
terraform apply, a single item with the observed LockID exists in the table; after the command completes, the item is removed. - CloudWatch alarm for DynamoDB ThrottledRequests is in OK state under normal load.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.