Ravindra BagaleCourses & study guides मराठी

Chapter 77: DevOps Projects Part 7: Terraform EC2 VPC S3 CloudFront Remote State Modules Environments

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.

What you will build in this project

  1. S3 bucket for state with versioning on.
  2. DynamoDB table LockID for locks.
  3. Backend block in Terraform.
  4. 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.

S3 versioningstate.jsonDynamoDBLockIDbackend blockinit -migratesecond applywaits on lock
Picture 1: laptop plan → S3 state + DynamoDB lock → safe apply

Ravindra Bagale's Tip

Students commit terraform.tfstate to git for backup. State has secrets. Use S3 versioning, not git. Keep that habit.

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.

Lab: try at home

  1. Create state bucket with versioning.
  2. Create DynamoDB lock table.
  3. Add backend block.
  4. Migrate and prove lock.
  5. 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.