Remote State with S3 and DynamoDB: Terraform State Backend Deep Dive
In this tutorial, you'll learn about Remote State with S3 and DynamoDB: Terraform State Backend Deep Dive. We cover key concepts, practical examples, and best practices.
Remote state with S3 and DynamoDB is the most widely adopted Terraform state backend for production teams, combining durable encrypted storage with distributed locking for safe concurrent operations.
What You'll Learn
In this tutorial, you will learn how to build a production Terraform remote state backend using S3 for storage and DynamoDB for locking, configure state file isolation per environment and service, implement state versioning and encryption, and handle lock conflicts and state recovery.
Why It Matters
Local state breaks for teams. Without remote state, each team member must share a single state file, leading to corruption, conflicts, and lost changes. S3 plus DynamoDB gives you encrypted, versioned, locked state that scales from solo developers to hundred-person platform teams.
Real-World Use
DodaTech manages 30 state files across three environments and five services in a single S3 bucket. Durga Antivirus Pro's platform team runs concurrent applies across multiple services without conflicts, and S3 versioning enables point-in-time recovery of any previous state.
Architecture Overview
graph TD
A[Terraform CLI / CI] --> B[S3 Backend]
B --> C[terraform-state-bucket]
C --> D[prod/network/terraform.tfstate]
C --> E[prod/database/terraform.tfstate]
C --> F[prod/compute/terraform.tfstate]
C --> G[staging/network/terraform.tfstate]
B --> H[DynamoDB Lock Table]
H --> I[LockID: prod/network/terraform.tfstate-md5:abc123]
H --> J[LockID: prod/database/terraform.tfstate-md5:def456]
C --> K[S3 Versioning]
K --> L[Version 1: 2026-06-23 10:00]
K --> M[Version 2: 2026-06-23 14:30]
K --> N[Version 3: 2026-06-23 16:45]
style B fill:#4a90d9,color:#fff
style C fill:#ff9900,color:#fff
style H fill:#ff9900,color:#fff
style K fill:#50c878,color:#fff
State Infrastructure Setup
S3 Bucket for State Storage
# state-infrastructure/state-bucket.tf
resource "aws_s3_bucket" "terraform_state" {
bucket = "dodatech-terraform-state-${data.aws_caller_identity.current.account_id}"
}
resource "aws_s3_bucket_versioning" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.terraform_state.arn
}
}
}
resource "aws_s3_bucket_public_access_block" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_s3_bucket_lifecycle_configuration" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
rule {
id = "expire-old-versions"
status = "Enabled"
noncurrent_version_expiration {
noncurrent_days = 90
}
}
}
terraform apply -auto-approve
Expected output:
Apply complete! Resources: 5 added, 0 changed, 0 destroyed.
Outputs:
state_bucket_name = "dodatech-terraform-state-123456789012"
lock_table_name = "terraform-state-locks"
DynamoDB Lock Table
# state-infrastructure/lock-table.tf
resource "aws_dynamodb_table" "terraform_locks" {
name = "terraform-state-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
point_in_time_recovery {
enabled = true
}
server_side_encryption {
enabled = true
}
tags = {
Name = "Terraform State Locks"
Environment = "Global"
ManagedBy = "Terraform"
}
}
KMS Key for State Encryption
# state-infrastructure/kms.tf
resource "aws_kms_key" "terraform_state" {
description = "KMS key for Terraform state encryption"
deletion_window_in_days = 30
enable_key_rotation = true
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow]
Principal = {
AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
}
Action = "kms:*"
Resource = "*"
}
]
})
}
resource "aws_kms_alias" "terraform_state" {
name = "alias/terraform-state-key"
target_key_id = aws_kms_key.terraform_state.key_id
}
Backend Configuration Patterns
Environment-Isolated State Files
# production/backend.hcl
bucket = "dodatech-terraform-state-123456789012"
key = "production/terraform.tfstate"
region = "us-east-1"
encrypt = true
kms_key_id = "alias/terraform-state-key"
dynamodb_table = "terraform-state-locks"
# staging/backend.hcl
bucket = "dodatech-terraform-state-123456789012"
key = "staging/terraform.tfstate"
region = "us-east-1"
encrypt = true
kms_key_id = "alias/terraform-state-key"
dynamodb_table = "terraform-state-locks"
# Initialize production with its backend
cd terraform/environments/production
terraform init -backend-config=backend.hcl -reconfigure
Service-Level State Isolation
# ----- network/backend.hcl -----
key = "production/network/terraform.tfstate"
# ----- database/backend.hcl -----
key = "production/database/terraform.tfstate"
# ----- compute/backend.hcl -----
key = "production/compute/terraform.tfstate"
# Terraform can read outputs from other state files
terraform init -backend-config=backend.hcl
State Data Source for Cross-State References
# database/main.tf
data "terraform_remote_state" "network" {
backend = "s3"
config = {
bucket = "dodatech-terraform-state-123456789012"
key = "production/network/terraform.tfstate"
region = "us-east-1"
}
}
resource "aws_db_subnet_group" "main" {
name = "production"
subnet_ids = data.terraform_remote_state.network.outputs.private_subnet_ids
}
terraform plan
Expected output:
data.terraform_remote_state.network: Reading...
data.terraform_remote_state.network: Read complete after 1s
No changes. Your infrastructure matches the configuration.
Locking Behavior and Troubleshooting
Testing State Locking
# Terminal 1: Start a long-running apply
terraform apply -auto-approve
# Terminal 2: Attempt concurrent apply
terraform apply -auto-approve
Expected output for Terminal 2:
Acquiring state lock. This may take a few moments...
Error: Error acquiring the state lock
Error message: ConditionalCheckFailedException: The conditional request failed
Lock Info:
ID: 2f8a1b3c-4d5e-6f78-9abc-def012345678
Path: production/terraform.tfstate
Operation: OperationTypeApply
Who: ci-runner-47@dodatech.com
Version: 1.9.5
Created: 2026-06-23 14:30:21.123 +0000 UTC
Info: ci-runner-47
Force Unlock Procedure
# Step 1: Verify no apply is running
aws dynamodb get-item \
--table-name terraform-state-locks \
--key '{"LockID": {"S": "production/terraform.tfstate-md5"}}'
# Step 2: Force unlock
terraform force-unlock 2f8a1b3c-4d5e-6f78-9abc-def012345678
Common Mistakes
1. Bucket Deletion Protection
Without a bucket policy denying deletion, someone can accidentally delete the S3 bucket and all state files.
2. Missing Versioning
Without S3 versioning, a corrupted state overwrite is unrecoverable. Always enable versioning on the state bucket.
3. Using One State for Everything
A monolithic state file creates a single point of failure. Split state by environment and service.
4. Exposing State Outputs
State outputs are readable by anyone with state access. Do not store secrets in Terraform outputs.
5. Ignoring Lock Table Region
The DynamoDB lock table must be in the same region as the S3 bucket. Cross-region locking fails silently.
Practice Questions
1. What problem does DynamoDB locking solve for Terraform teams? It prevents concurrent apply operations from corrupting the state file by acquiring a distributed lock.
2. How do you reference outputs from one state file in another Terraform configuration?
Using the data "<a href="/devops/terraform/">Terraform</a>_remote_state" data source with the backend configuration pointing to the other state file.
3. Why should KMS encryption be used for the state bucket? KMS provides envelope encryption with key rotation, audit logging, and integration with AWS CloudTrail for compliance.
4. Challenge: Set up a complete state infrastructure with S3, DynamoDB, and KMS. Configure three environment-separated state files. Create a service that uses terraform_remote_state to reference VPC IDs from the network state.
Mini Project: Multi-Service State Infrastructure
Provision the complete state backend infrastructure: S3 bucket with versioning, KMS key with rotation, DynamoDB lock table with PITR. Configure separate state files for network, database, and compute in production. Wire the database service to read VPC and subnet IDs from the network state using data.terraform_remote_state.
Related Concepts
What's Next
Configure Terraform remote state with S3 and DynamoDB for your production infrastructure, then build complete AWS environments with Terraform. Study Docker containerization patterns for application deployment.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro