You run terraform plan and see 47 resources being destroyed and recreated that should only be updated in-place. What happened and how do you fix it?
Quick Answer
Usually caused by a provider upgrade changing resource schemas, a backend state key change, or a module source change. Fix by checking the plan output for 'forces replacement' reasons and using lifecycle blocks or targeted applies.
Detailed Answer
Common Causes of Unexpected Destroy/Recreate
1. Provider upgrade: New provider version changes a resource attribute from optional to ForceNew. Example: changing aws_instance.ami forces replacement. 2. State key mismatch: Refactoring module structure (renaming modules, moving resources between modules) causes Terraform to see new resources instead of existing ones 3. ForceNew attributes changed: Some attributes can't be updated in-place (e.g., aws_rds_instance.engine, aws_launch_template.name) 4. Backend migration issue: State file doesn't match the current backend configuration 5. Count/for_each index shift: Changing from count to for_each or reordering count indices
Diagnosis
`` # Check WHY each resource is being replaced terraform plan -out=plan.tfplan terraform show plan.tfplan | grep 'forces replacement' ``
Fixes
1. State move for refactoring: terraform state mv 'module.old.aws_instance.web' 'module.new.aws_instance.web' 2. Import for orphaned resources: terraform import 'aws_instance.web' i-abc123 3. Lifecycle ignore for computed attributes: ``hcl lifecycle { ignore_changes = [tags["LastModified"]] } ` 4. Pin provider version: required_providers { aws = { version = "~> 5.30" } }` 5. Use moved blocks (Terraform 1.1+) for declarative state refactoring
Prevention
- Always review plan output carefully before apply - Pin provider versions in production - Use moved blocks when refactoring - Test provider upgrades in dev first
Code Example
# Terraform moved block for safe refactoring
moved {
from = module.old_name.aws_instance.web
to = module.new_name.aws_instance.web
}
# State move command
terraform state mv \
'module.vpc.aws_subnet.private[0]' \
'module.vpc.aws_subnet.private["us-east-1a"]'
# Check what forces replacement
terraform plan -no-color 2>&1 | grep -B5 'forces replacement'
# Pin provider version
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.30"
}
}
}Interview Tip
Show that you check the 'forces replacement' reason in the plan output before taking any action. Mention the moved block as the modern solution for refactoring — it's declarative and reviewable in PRs, unlike imperative state mv commands.