Terraform Drift & State Recovery
Someone changed AWS by hand and someone else deleted the state. Recover from both without rebuilding anything.
- Time
- 55 min
- Level
- Advanced
- Objectives
- 4 objectives
- Cost
- Low cost
Where this fits in the platform
Already built
This lab adds
- State recovered after the failure everyone hopes to avoid
Which lets you
—
Before you start
You will need
- Terraform >= 1.6
- AWS CLI v2, configured
You do not need these already — the lab environment below provides them.
You will be able to
- Detect drift and decide whether to adopt or revert it
- Import an existing resource into state
- Recover a state file from a versioned backend
Cost — Low cost
— the exercises use an S3 bucket and a `t3.micro`. Nothing here bills hourly beyond the instance.
Nothing to pay in the browser. Open the terminal runs this against a simulated cloud — the same API calls and the same commands, with no account and no bill. The figure above applies only if you build it in your own.
The scenario#
Someone widened a security group in the console during an incident. Someone else ran terraform apply a week later and closed it again, causing a second incident.
Then the state file was deleted.
All three are recoverable. None of them require rebuilding the infrastructure — which is what people do when they do not know these commands.
This lab deletes state and modifies resources on purpose. Use a scratch configuration, never a real environment.
Hands-on environment
Run this lab in a real terminal, free and in your browser. The environment is temporary and yours alone — break it as much as you like.
Open the terminalOpens in Killercoda, in a new tab — keep this page open for the steps.
Run it on AWS
This lab builds real cloud infrastructure, so it needs your own AWS account. Follow the cost and cleanup notes above — the resources are yours, and so is the bill.
Anything you tick here is your own record. EgyKode cannot see inside that terminal, so the success criteria stay self-assessed even when the environment checks your work for you.
Drift, detected
Step 1 of 5
What you are proving: You can detect a manual change and explain both ways to resolve it
Marking this settles success criterion 1.
terraform apply -auto-approve
aws ec2 authorize-security-group-ingress --group-id <sg> \
--protocol tcp --port 8080 --cidr 0.0.0.0/0 # the "incident fix"
terraform plan -detailed-exitcode
echo "exit: $?" # 2 = drift ~ resource "aws_security_group" "demo" {
~ ingress { - from_port = 8080 ... }
}Two legitimate responses, and choosing wrongly causes the second incident:
- Revert —
terraform applyremoves the rule. Correct when the manual change was a mistake. - Adopt — put the rule in the configuration. Correct when it was a real fix that must survive.
Never resolve it by running apply without reading the plan. That is what
closed the port again at the worst moment.
-detailed-exitcode returns 2 for drift, which is designed for a scheduled
job — finding drift on a Monday is much better than finding it mid-incident.
What you are proving: You can import a resource Terraform did not create, until plan reports no changes
Marking this settles success criterion 2.
aws s3api create-bucket --bucket tfstate-recovery-demo-$(date +%s) # by handAdding a matching resource block and applying fails: the bucket exists and
Terraform tries to create it. Import instead:
resource "aws_s3_bucket" "adopted" {
bucket = "tfstate-recovery-demo-1234567890"
}terraform import aws_s3_bucket.adopted tfstate-recovery-demo-1234567890
terraform plan # must report: No changesThat No changes is the test. If the plan wants to modify something, your
configuration does not match reality yet — keep editing until it does, and
resist the temptation to apply your way there.
Terraform 1.5+ can do this declaratively, which is reviewable:
import {
to = aws_s3_bucket.adopted
id = "tfstate-recovery-demo-1234567890"
}What you are proving: You can restore a deleted state file from versioning and confirm it matches reality
Marking this settles success criterion 3.
aws s3 rm s3://<state-bucket>/demo/terraform.tfstate # simulate it
terraform plan
# wants to create everything — it has lost all memoryDo not apply. That builds a second copy of everything you already have.
aws s3api list-object-versions --bucket <state-bucket> \
--prefix demo/terraform.tfstate \
--query 'Versions[].[VersionId,LastModified]' --output table
aws s3api get-object --bucket <state-bucket> \
--key demo/terraform.tfstate --version-id <id> restored.tfstate
aws s3 cp restored.tfstate s3://<state-bucket>/demo/terraform.tfstate
terraform plan # No changesThis is why the bootstrap stack enables versioning on the state bucket. A delete leaves a delete marker and the object is still there. Without versioning, the only recovery is importing every resource by hand.
What you are proving: You can move resources in state without destroying and recreating them
Marking this settles success criterion 4.
Renaming a resource in your configuration makes Terraform plan a destroy and a create — same infrastructure, different address:
terraform state mv aws_instance.web aws_instance.frontend
terraform plan # No changesstate mv updates the map only. No AWS API call touches the resource, so a
rename costs nothing and no downtime.
terraform state list
terraform state show aws_instance.frontend
terraform state rm aws_instance.frontend # forget it WITHOUT deleting itstate rm is the one to be careful with: Terraform forgets the resource and it
keeps running and billing, invisible to your configuration. It is the right tool
for handing a resource to another stack, and a good way to create an orphan by
accident.
What you are proving: You can name the habits that keep state recoverable before you need to recover it
This step settles no success criterion on its own.
- Versioning and locking on the state bucket, always.
-detailed-exitcodeon a schedule, so drift finds you rather than the reverse.plan -outthenapplythat file, so what runs is what was reviewed.- Before anything risky:
terraform state pull > backup.tfstate.
import says the resource already exists in state
It is already tracked under some address. terraform state list to find it.
plan still shows changes after import
Your configuration does not match the real resource. Edit until the plan is empty — do not apply your way there.
No versions in the state bucket
Versioning was not enabled. There is no recovery beyond importing everything; enable it now on every state bucket you own.
state mv reports the address does not exist
Addresses are exact, including index keys such as [0] or ["us-east-1a"]. Copy them from terraform state list rather than typing them.
Clean up#
Run this even if you did not finish.
Destructive — This removes real resources. Check which environment you are in first.
terraform destroy -auto-approve
aws s3 ls | grep tfstate-recoveryCost of this lab: Free tier — the exercises use an S3 bucket and a t3.micro. Nothing here bills hourly beyond the instance.
Success criteria
0 of 4
The concept behind it
Next up
Lab 57 of 59 on the project path