Terraform State, Locks, and Lessons from the Trenches
Step-by-step with the “why”
Terraform State, Locks, and Lessons from the Trenches

Step-by-step with the “why”
Today we are building two things at once:
- Infrastructure: A VPC network in AWS (main.tf)
- Infrastructure management system: How Terraform stores, locks, and updates that network safely (state.tf + DynamoDB + S3)
In my setup I’m using terraform-locks and mx-central-1.
Part 0 — What problem are we solving?
Problem 1: “ClickOps” doesn’t scale
If you build a VPC in the AWS Console:
- Nobody knows exactly what was created
- Changes aren’t reviewable
- Reproducing the same network in another environment is manual and error-prone
Solution: Write Infrastructure as Code (IaC), your network defined in main.tf
Problem 2: Terraform needs memory
After apply, AWS gives you real IDs like vpc-03a47233479547692.
Your code says aws_vpc.main , Terraform must remember that mapping.
Solution: A state file (terraform.tfstate) stored remotely on S3.
Problem 3: Two people can break state at once
If two terminals run apply simultaneously, they can corrupt the state file.
Solution: State locking via DynamoDB table terraform-locks with key LockID.
Who benefits?

The tools, and how they connect


Step 1 | Create the DynamoDB lock table
Command
aws dynamodb create-table \
--table-name terraform-locks \
--attribute-definitions AttributeName=LockID,AttributeType=S \
--key-schema AttributeName=LockID,KeyType=HASH \
--provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1 \
--region mx-central-1
Why each piece?

Why LockID specifically?
Terraform writes a row like: “You are running apply on this state file.”
It always uses the column name LockID. Wrong name (e.g. tf) = locking fails.
Verify:
aws dynamodb describe-table --table-name terraform-locks --region mx-central-1 \
--query 'Table.{Name:TableName,Key:Schema[0].AttributeName,Status:TableStatus}'
Expect: Key: LockID, Status: ACTIVE
Step 2 | Configure remote state + locking (state.tf)
My file:
terraform {
backend "s3" {
bucket = "ec-my-terraform-state"
key = "global/s3/terraform.tfstate"
region = "mx-central-1"
dynamodb_table = "terraform-locks"
}
}
Why each setting?

Important: dynamodb_table must match exactly the table you created (terraform-locks, not terraform-lock-file unless you create that name).
Step 3 | Define the network (main.tf)
I’m building this:

Resource by resource, understanding the “why”

Fixes I learned, and implemented in my code

Step 4 | terraform init (connect everything)
Because I changed how locking works in state.tf (from DynamoDB to S3 native locking):
cd "/Users/ec/Desktop/Cloud Engineering/Git Hub Repo/CloudFormation/terraform/terraform_newfolder"
terraform init -reconfigure
What init does:
- Downloads the AWS provider plugin
- Configures the S3 backend
- Enables S3-native state locking via use_lockfile = true
S3 locking vs DynamoDB locking

For this step I switched to S3 native locking, same S3 bucket, no DynamoDB dependency, and no deprecation warning from Terraform.
-reconfigure vs -migrate-state

The switch in state.tf:
Before:
region = "mx-central-1"
dynamodb_table = "terraform-locks"
After:
region = "mx-central-1"
use_lockfile = true
$ cd "/Users/ec/Desktop/Cloud Engineering/Git Hub Repo/CloudFormation/terraform/terraform_new_folder" && terraform init -reconfigure 2>&1
What I saw:
Initializing provider plugins found in the configuration...
- Reusing previous version of hashicorp/aws from the dependency lock file
- Using previously-installed hashicorp/aws v6.46.0
Initializing the backend...
Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.
Terraform has been successfully initialized!
Updated state.tf and ran terraform init -reconfigure, no deprecation warning, init succeeded.
Why this is a good change
- No DynamoDB table required for locking
- Fewer moving parts, state and lock live in the same S3 bucket
- Matched Terraform’s direction, dynamodb_table is deprecated in favor of use_lockfile
My terraform-locks DynamoDB table still exists in AWS from the lab. You can leave it or delete it later, Terraform no longer uses it.
Step 5 | terraform plan (preview, no changes)
terraform plan
What plan does
Compares three things:
- Code (main.tf), what you want
- State (S3), what Terraform thinks exists
- AWS, what actually exists
How to read output

Why this matters?
You review before spending money or breaking production. Best practice even when you “know” what’s in the code.
Two plans, two stories
While infrastructure was live (with DynamoDB locking):
Warning: Deprecated Parameter - dynamodb_table is deprecated...
aws_vpc.main: Refreshing state... [id=vpc-03a47233479547692]
...
No changes. Your infrastructure matches the configuration.
After destroy, with S3 native locking:
Plan: 7 to add, 0 to change, 0 to destroy.
That 7 to add is not a bug. Destroy removed the live VPC from AWS and cleared state, but main.tf still describes the network. Terraform’s job is to close that gap.
Step 6 | terraform apply (make it real)
terraform apply
Type yes
What happens:
- Terraform acquires a state lock (S3 lockfile, or DynamoDB if configured)
- Creates/updates AWS resources in dependency order (VPC first, then subnets, etc.)
- Writes real IDs back to S3 state
- Releases the lock
Done successfully, 7 resources added.

Step 7 | Verify in AWS Console
- Switch region to mx-central-1 (or the region you’re in)
- VPC -> find main-tf-vpc
- Confirm 2 subnets, 1 IGW, 1 route table with 0.0.0.0/0 -> IGW
Why verify? Terraform state can drift from reality. The console confirms what actually exists.
Step 8 | terraform destroy (finish the lab, avoid cost)
terraform destroy
Type yes
Why destroy?
- Lab VPCs cost money (small, but real)
- Teaches the full lifecycle: create -> verify -> tear down
- Proved Terraform manages cleanup, not just creation
What I saw:
Acquiring state lock. This may take a few moments...
Plan: 0 to add, 0 to change, 7 to destroy.
Destroy complete! Resources: 7 destroyed
Lock acquired, seven resources removed in the correct order. Lab complete.
What broke along the way

Each error taught me something about how Terraform thinks, not just syntax.
Full workflow
# 1. Go to project
cd "/Users/ec/Desktop/Cloud Engineering/Git Hub Repo/CloudFormation/terraform/terraform_new_folder"
# 2. After backend/lock changes
terraform init -reconfigure
# 3. Preview
terraform plan
# 4. Deploy (if needed)
terraform apply
# 5. Cleanup
terraform destroy
Final status

Lab complete.
Key takeaways:
- IaC turns infrastructure into reviewable, repeatable code.
- Remote state (S3): Gives the team a shared memory of what exists.
- Locking (DynamoDB or S3 lockfile): stops two applies from corrupting state at the same time.
- LockID: Because Terraform’s DynamoDB locking code expects that exact key name.
- use_lockfile = true , the modern, simplest path for S3-native locking.
- terraform plan, see surprises before they hit AWS. Plan before apply, always.
- terraform destroy, lab resources shouldn’t linger. Learn the full lifecycle.
Closing
This lab was more than “run three commands.” It was state management, locking, HCL gotchas, and learning to read Terraform’s output as a safety net.
I started with DynamoDB locking to understand why locks exist and what LockID means. Then I switched to use_lockfile = true when Terraform’s deprecation warning pointed me toward the simpler, modern approach.
I deployed a VPC, verified it in the console, and tore it all down with destroy.
If you’re working through this lab: learn DynamoDB locking first, then consider S3 native locking for day-to-day work.
Fix the small main.tf mistakes, trust plan before you touch AWS, and always destroy when you’re done.
Same architecture as the lab we’re working through in our program. Different region, same lessons.
On to the next module.
메타데이터
- post_id
- e963eee097cb
- slug
- terraform-state-locks-and-lessons-from-the-trenches-e963eee097cb
- url
- https://medium.com/@ecoufalleano/terraform-state-locks-and-lessons-from-the-trenches-e963eee097cb
- canonical_url
- https://medium.com/@ecoufalleano/terraform-state-locks-and-lessons-from-the-trenches-e963eee097cb
- author_url
- https://medium.com/@ecoufalleano
- status
- ok
- fetched_at
- 2026-06-11 15:16:29