Terraform Large State Refactor Prompt
Refactor large Terraform state — moved/removed blocks, splitting state files, extracting modules without destroy.
- Target user
- Senior Terraform engineers managing large infrastructure
- Difficulty
- Advanced
- Tools
- Claude, ChatGPT
The prompt
You are a senior Terraform engineer who has refactored monolithic state files — splitting into smaller, extracting modules, renaming resources without destroy/recreate.
I will provide:
- The state size
- Refactor goal
- Symptom (slow plan, blast radius too wide)
Your job:
1. **For `moved` block** (1.1+):
- Declarative refactor
- In code, not CLI
- `moved { from = ..., to = ... }`
- Auto-applied; state automatically updated
2. **For `removed` block** (1.7+):
- Stop managing without destroy
- `removed { from = ..., lifecycle { destroy = false } }`
- Replaces `state rm` in code
3. **For splitting state**:
- `terraform state pull` → split JSON manually
- Or: import into new state, remove from old
- Or: use `terraform state mv` with `-state-out`
4. **For extracting module**:
- Move resources into module dir
- `moved` blocks pointing from old addresses to `module.x.<addr>`
- Run plan; should show "moved"
5. **For consolidating**:
- Rare; usually split, not merge
6. **For renaming**:
- `moved { from = aws_instance.web, to = aws_instance.app }`
- State updated; cloud unchanged
7. **For massive moves**:
- Generate moved blocks programmatically
- Test in non-prod
8. **For removing moved blocks** (after migration complete):
- Wait one apply cycle
- Then delete from code
Mark DESTRUCTIVE: refactor without backup, moved block to wrong address (orphan), removed block without verifying cloud resource preserved.
---
State size: [DESCRIBE]
Refactor goal: [DESCRIBE]
Symptom: [DESCRIBE]
Why this prompt works
Refactor without downtime is high-value. This prompt walks patterns.
How to use it
- Use
movedblock for refactor. - Use
removedfor de-management. - Backup state before.
- Plan + verify carefully.
Patterns
Rename via moved
# OLD: resource "aws_instance" "web" { ... }
# NEW:
resource "aws_instance" "app" {
ami = "ami-12345"
instance_type = "t3.medium"
}
# Migration block
moved {
from = aws_instance.web
to = aws_instance.app
}
Run terraform plan:
Terraform will perform the following actions:
# aws_instance.web has moved to aws_instance.app
resource "aws_instance" "app" {
...
}
Plan: 0 to add, 0 to change, 0 to destroy.
After apply, the moved block can stay (idempotent) or be removed in a future cycle.
Module extraction
Before:
# main.tf (flat)
resource "aws_vpc" "main" { cidr_block = "10.0.0.0/16" }
resource "aws_subnet" "public" { vpc_id = aws_vpc.main.id ... }
resource "aws_internet_gateway" "main" { vpc_id = aws_vpc.main.id }
After:
# main.tf
module "network" {
source = "./modules/network"
vpc_cidr = "10.0.0.0/16"
}
# modules/network/main.tf (extracted)
resource "aws_vpc" "main" { cidr_block = var.vpc_cidr }
resource "aws_subnet" "public" { vpc_id = aws_vpc.main.id ... }
resource "aws_internet_gateway" "main" { vpc_id = aws_vpc.main.id }
Migration:
# In root main.tf, alongside module call
moved {
from = aws_vpc.main
to = module.network.aws_vpc.main
}
moved {
from = aws_subnet.public
to = module.network.aws_subnet.public
}
moved {
from = aws_internet_gateway.main
to = module.network.aws_internet_gateway.main
}
Removing without destroy
# 1.7+: declarative remove
removed {
from = aws_s3_bucket.legacy
lifecycle {
destroy = false # keep cloud resource
}
}
Resource removed from state; bucket stays alive in cloud. Useful when:
- Resource migrated to another tool / state
- Imported elsewhere
- Manually managed going forward
State splitting (multi-state)
# Backup
terraform state pull > monolith.tfstate
# In OLD state: remove resources to move out
terraform state rm aws_vpc.main
terraform state rm 'module.network.*'
# Create NEW state dir + backend
cd ../new-state-dir/
terraform init # new backend
# Import resources individually
terraform import aws_vpc.main vpc-0abc123
# Or use import block + plan -generate-config-out
Generate moved blocks for many resources
# Old addresses → new addresses
cat <<EOF > moved.txt
aws_instance.web1 module.web.aws_instance.this[0]
aws_instance.web2 module.web.aws_instance.this[1]
aws_instance.web3 module.web.aws_instance.this[2]
EOF
# Generate moved blocks
while read OLD NEW; do
cat <<HCL >> moved.tf
moved {
from = $OLD
to = $NEW
}
HCL
done < moved.txt
Verification
# Plan should show "moved", not destroy/create
terraform plan | grep -i "moved\|destroy\|create"
# Diff before / after state
terraform state pull > after.tfstate
diff <(jq -S . monolith.tfstate) <(jq -S . after.tfstate) | head
Common findings this catches
- Module extraction without moved block → destroy + create (data loss).
- Wrong target address → state mismatch.
removed { destroy = true }→ unintended destroy.- State pull during apply → inconsistent.
- Refactor + functional change in same commit → noise; split.
- Moved blocks left forever → cleanup after stable.
- Cross-state moves require careful re-import.
When to escalate
- Production state split — staged with comms.
- Large refactor across many MRs — coordinate.
- Recovery from bad refactor — restore from backup.
Related prompts
-
Terraform Module Composition Prompt
Design Terraform modules — input/output contracts, composition, versioning, public vs private registry, when to abstract.
-
Terraform State Backend Design Prompt
Design Terraform state backend — S3+DynamoDB, GCS, Azure Blob, encryption, locking, versioning, cross-account access.
-
Terraform State Surgery & Import Prompt
Perform Terraform state operations — terraform state mv/rm/import, replace, large-scale imports via import block.