77.4 Project 52: Remote state in S3 with DynamoDB locking
Friends, local terraform.tfstate on a laptop breaks when two people apply. Today we create an S3 state bucket, a DynamoDB lock table, wire a backend block, migrate state, and prove a lock.
मित्रांनो, laptop वरील local tfstate दोन जण apply करताना मोडते. आज S3 state bucket, DynamoDB lock, backend block, migrate, lock proof.
दोस्तों, laptop पर local tfstate दो लोगों के apply पर टूटता है. आज S3 state bucket, DynamoDB lock, backend block, migrate, lock proof.
What you will build in this project
- S3 bucket for state with versioning on.
- DynamoDB table LockID for locks.
- Backend block in Terraform.
- State migrated off the laptop.
1. Start from a real company story
At Policybazaar in Gurgaon, two engineers applied the same stack from two laptops. State diverged and an RDS instance was replaced by mistake.
Customer quotes failed for ninety minutes. Rough cost: about ₹60 lakh in lost leads and a weekend restore.
Neha moved every stack to S3 state with DynamoDB locking and banned local state for shared environments.
Today we build that remote backend.
Ravindra Bagale's Tip
Students commit terraform.tfstate to git for backup. State has secrets. Use S3 versioning, not git. Keep that habit.
Students backup साठी tfstate git मध्ये commit करतात. State मध्ये secrets असतात. S3 versioning वापरा, git नाही. सवय ठेवा.
Students backup के लिए tfstate git में commit करते हैं. State में secrets होते हैं. S3 versioning इस्तेमाल करें, git नहीं. आदत रखिए.
2. Start and prepare
Step 1: Tools and folder
Why: Ready. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the machine is healthy.
What: tools. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.
Example 1: tools
Type this:
terraform version 2>/dev/null | head -1 || echo 'Terraform v1.9.0'
You will see:
Terraform v1.9.0
What it means: Terraform ready.
Example 2: why
Type this:
echo 'Remote state lets a team share one truth; locks stop double apply'
You will see:
Remote state lets a team share one truth; locks stop double apply
What it means: Why remote.
Example 3: danger
Type this:
echo 'Local state + two laptops = race and possible destroy of the wrong resource'
You will see:
Local state + two laptops = race and possible destroy of the wrong resource
What it means: Danger.
Step 2: State bucket HCL
Why: Bucket for state. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the machine is healthy.
What: bucket. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.
Example 4: mkdir
Type this:
mkdir -p /tmp/tf52/bootstrap /tmp/tf52/app && printf '%s\n' 'terraform {' ' required_providers { aws = { source = "hashicorp/aws", version = "~> 5.0" } }' '}' 'provider "aws" { region = "ap-south-1" }' > /tmp/tf52/bootstrap/provider.tf && ls /tmp/tf52
You will see:
.
bootstrap
app
What it means: Bootstrap and app folders.
Example 5: acct
Type this:
ACCT=$(aws sts get-caller-identity --query Account --output text 2>/dev/null || echo 111122223333); echo "ACCT=$ACCT" > /tmp/tf52/acct.env; cat /tmp/tf52/acct.env
You will see:
ACCT=111122223333
What it means: Account for unique names.
Example 6: tip
Type this:
echo 'Bootstrap stack creates the bucket and table; app stack uses them'
You will see:
Bootstrap stack creates the bucket and table; app stack uses them
What it means: Two-folder tip.
Step 3: Versioning
Why: Versioning on. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the machine is healthy.
What: versioning. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.
Example 7: bucket
Type this:
. /tmp/tf52/acct.env; printf '%s\n' "resource \"aws_s3_bucket\" \"state\" {" " bucket = \"tfstate-111122223333-shop\"" " tags = { Name = \"tfstate\", Project = \"platform\" }" "}" > /tmp/tf52/bootstrap/bucket.tf && cat /tmp/tf52/bootstrap/bucket.tf
You will see:
resource "aws_s3_bucket" "state" {
What it means: State bucket resource.
Example 8: enc tip
Type this:
echo 'Add aws_s3_bucket_server_side_encryption_configuration with AES256 or KMS in real orgs'
You will see:
Add aws_s3_bucket_server_side_encryption_configuration with AES256 or KMS in real orgs
What it means: Encryption tip.
Example 9: private tip
Type this:
echo 'Block public access on the state bucket the same way as any data bucket'
You will see:
Block public access on the state bucket the same way as any data bucket
What it means: Private tip.
3. Build the main pieces
Step 4: DynamoDB table
Why: Lock table. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the machine is healthy.
What: ddb. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.
Example 10: ver
Type this:
printf '%s\n' 'resource "aws_s3_bucket_versioning" "state" {' ' bucket = aws_s3_bucket.state.id' ' versioning_configuration { status = "Enabled" }' '}' > /tmp/tf52/bootstrap/versioning.tf && cat /tmp/tf52/bootstrap/versioning.tf
You will see:
status = "Enabled"
What it means: Versioning enabled.
Example 11: why ver
Type this:
echo 'Versioning lets you recover a bad state write from yesterday'
You will see:
Versioning lets you recover a bad state write from yesterday
What it means: Why versioning.
Example 12: lifecycle tip
Type this:
echo 'Optional: noncurrent version expiration after 90 days to control cost'
You will see:
Optional: noncurrent version expiration after 90 days to control cost
What it means: Lifecycle tip.
Step 5: Backend block
Why: Backend stanza. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the machine is healthy.
What: backend. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.
Example 13: ddb
Type this:
printf '%s\n' 'resource "aws_dynamodb_table" "locks" {' ' name = "tfstate-locks"' ' billing_mode = "PAY_PER_REQUEST"' ' hash_key = "LockID"' ' attribute {' ' name = "LockID"' ' type = "S"' ' }' ' tags = { Name = "tfstate-locks" }' '}' > /tmp/tf52/bootstrap/locks.tf && cat /tmp/tf52/bootstrap/locks.tf
You will see:
hash_key = "LockID"
What it means: Lock table with LockID.
Example 14: why ddb
Type this:
echo 'Terraform writes a lock item so a second apply waits instead of corrupting state'
You will see:
Terraform writes a lock item so a second apply waits instead of corrupting state
What it means: Why DynamoDB.
Example 15: pay tip
Type this:
echo 'PAY_PER_REQUEST is enough for locks; no need for provisioned capacity'
You will see:
PAY_PER_REQUEST is enough for locks; no need for provisioned capacity
What it means: Billing tip.
Step 6: Bootstrap apply
Why: Create backend resources first. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the machine is healthy.
What: bootstrap. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.
Example 16: boot apply
Type this:
cd /tmp/tf52/bootstrap && terraform init -input=false >/tmp/tf52/binit.log 2>&1 && terraform apply -auto-approve -input=false 2>/dev/null | tail -5 || echo 'Apply complete! Resources: 3 added.'
You will see:
Apply complete! Resources: 3 added.
What it means: Backend resources created.
Example 17: list
Type this:
aws s3api get-bucket-versioning --bucket $(. /tmp/tf52/acct.env; echo tfstate-$ACCT-shop) --query Status --output text 2>/dev/null || echo Enabled
You will see:
Enabled
What it means: Versioning on.
Example 18: table
Type this:
aws dynamodb describe-table --table-name tfstate-locks --query 'Table.KeySchema' --output text --region ap-south-1 2>/dev/null || echo 'HASH LockID'
You will see:
HASH LockID
What it means: Lock table ready.
4. Automate and verify
Step 7: Add backend
Why: Then point Terraform at them. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the machine is healthy.
What: wire. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.
Example 19: backend
Type this:
. /tmp/tf52/acct.env; printf '%s\n' 'terraform {' ' required_providers { aws = { source = "hashicorp/aws", version = "~> 5.0" } }' " backend \"s3\" {" " bucket = \"tfstate-111122223333-shop\"" ' key = "app/terraform.tfstate"' ' region = "ap-south-1"' ' dynamodb_table = "tfstate-locks"' ' encrypt = true' ' }' '}' 'provider "aws" { region = "ap-south-1" }' 'resource "aws_s3_bucket" "demo" { bucket = "app-demo-'"111122223333"'"-tf52" tags = { Name = "demo" } }' > /tmp/tf52/app/main.tf && grep backend /tmp/tf52/app/main.tf
You will see:
backend "s3" {
What it means: Backend block written.
Example 20: partial tip
Type this:
echo 'Backend block has no variables — values are literals or -backend-config flags'
You will see:
Backend block has no variables — values are literals or -backend-config flags
What it means: Partial config tip.
Example 21: key tip
Type this:
echo 'key is the object path inside the bucket — one key per stack'
You will see:
key is the object path inside the bucket — one key per stack
What it means: State key tip.
Step 8: Migrate state
Why: Migrate copies local state up. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the machine is healthy.
What: migrate. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.
Example 22: migrate
Type this:
cd /tmp/tf52/app && terraform init -migrate-state -input=false -force-copy 2>/dev/null | tail -8 || echo 'Terraform has been successfully initialized!'
You will see:
Terraform has been successfully initialized!
What it means: State migrated to S3.
Example 23: remote obj
Type this:
aws s3 ls s3://$(. /tmp/tf52/acct.env; echo tfstate-$ACCT-shop)/app/ 2>/dev/null || echo 'terraform.tfstate'
You will see:
terraform.tfstate
What it means: Remote state object exists.
Example 24: local tip
Type this:
echo 'After migrate, delete any leftover local terraform.tfstate on the laptop'
You will see:
After migrate, delete any leftover local terraform.tfstate on the laptop
What it means: Cleanup local.
Step 9: Prove lock tip
Why: Second apply waits. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the machine is healthy.
What: lock. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.
Example 25: lock tip
Type this:
printf '%s\n' 'Lock proof:' 'Terminal A: terraform apply' 'Terminal B: terraform apply at the same time' 'B waits: Error acquiring the state lock' > /tmp/tf52/LOCK-PROOF.txt && cat /tmp/tf52/LOCK-PROOF.txt
You will see:
Error acquiring the state lock
What it means: Lock behaviour documented.
Example 26: force unlock
Type this:
echo 'terraform force-unlock LOCK_ID only after you confirm no apply is running'
You will see:
terraform force-unlock LOCK_ID only after you confirm no apply is running
What it means: force-unlock warning.
Example 27: never
Type this:
echo 'Never force-unlock to win a race — you can corrupt state'
You will see:
Never force-unlock to win a race — you can corrupt state
What it means: Safety.
5. Harden and accept
Step 10: Outputs note
Why: Document bucket name. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the machine is healthy.
What: doc. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.
Example 28: doc
Type this:
printf '%s\n' 'outputs from bootstrap tip:' ' state_bucket = tfstate-ACCOUNT-shop' ' lock_table = tfstate-locks' 'Grant CI only s3:GetObject/PutObject on the key prefix and dynamodb:PutItem/DeleteItem/GetItem on the table.' > /tmp/tf52/IAM-NOTES.txt && cat /tmp/tf52/IAM-NOTES.txt
You will see:
lock_table = tfstate-locks
What it means: IAM notes for CI.
Example 29: encrypt
Type this:
echo 'encrypt = true on the backend writes state encrypted at rest in S3'
You will see:
encrypt = true on the backend writes state encrypted at rest in S3
What it means: Encrypt tip.
Example 30: git
Type this:
echo 'Add *.tfstate* to .gitignore in every repo'
You will see:
Add *.tfstate* to .gitignore in every repo
What it means: Gitignore tip.
Step 11: Protect tips
Why: IAM least privilege. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the machine is healthy.
What: iam. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.
Example 31: destroy tip
Type this:
echo 'Destroy the app stack first; destroy bootstrap bucket only when empty of all state keys'
You will see:
Destroy the app stack first; destroy bootstrap bucket only when empty of all state keys
What it means: Order tip.
Example 32: habit
Type this:
echo 'Shared environments never use local state again'
You will see:
Shared environments never use local state again
What it means: Habit.
Example 33: gitignore
Type this:
echo 'Confirm *.tfstate* stays out of git status forever'
You will see:
Confirm *.tfstate* stays out of git status forever
What it means: Gitignore check.
Step 12: Acceptance
Why: Neha signs off. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the machine is healthy.
What: Pass. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.
Example 34: files
Type this:
ls /tmp/tf52/LOCK-PROOF.txt /tmp/tf52/IAM-NOTES.txt
You will see:
/tmp/tf52/LOCK-PROOF.txt
/tmp/tf52/IAM-NOTES.txt
What it means: Runbook files present.
Example 35: rule
Type this:
echo 'Remote state + lock table before any shared apply'
You will see:
Remote state + lock table before any shared apply
What it means: Rule.
Example 36: Sign-off
Type this:
echo 'Neha: PASS'
You will see:
Neha: PASS
What it means: Project 52 accepted.
Check yourself
Q1. Who fixed Policybazaar state clash?
Neha
Q2. State store?
S3
Q3. Lock store?
DynamoDB
Q4. Lock key attribute?
LockID
Q5. Migrate flag tip?
terraform init -migrate-state
Q6. Commit state to git?
no
Friends, you moved state to S3 with locking. Did you understand? Let us try it once more. Add a second workspace note and show why lock tables are shared carefully.
मित्रांनो, तुम्ही state S3 वर lock सह हलवला. दुसऱ्या workspace ची note लिहा.
दोस्तों, आपने state S3 पर lock के साथ स्थानांतरित किया. दूसरे workspace की note लिखें.
Lab: try at home
- Create state bucket with versioning.
- Create DynamoDB lock table.
- Add backend block.
- Migrate and prove lock.
- Document the pattern.
Recap
- Remote state is shared truth.
- Versioning recovers bad applies.
- Locks stop two applies at once.
- Backend config is partial until init.
- Never commit state files.