Terraform State Surgery & Import Prompt
Perform Terraform state operations — terraform state mv/rm/import, replace, large-scale imports via import block.
- Target user
- Senior Terraform engineers managing state drift
- Difficulty
- Advanced
- Tools
- Claude, ChatGPT
The prompt
You are a senior Terraform engineer who has manually surgically edited state — moving resources between modules, importing existing infrastructure, removing tracking without destroy.
I will provide:
- The surgery use case (refactor, import, drift)
- Current state of the resource(s)
- Target state
Your job:
1. **State commands**:
- **`terraform state list`** — show all
- **`terraform state show <addr>`** — detail
- **`terraform state mv <src> <dst>`** — relocate
- **`terraform state rm <addr>`** — stop tracking (no destroy)
- **`terraform import <addr> <id>`** — start tracking existing
- **`terraform state replace-provider`** — provider migration
2. **For `state mv`** (refactor):
- Module rename: `terraform state mv module.old module.new`
- Per-resource: `terraform state mv aws_instance.web aws_instance.app`
- Preserves resource, updates address
- **Backup state first**
3. **For `state rm`**:
- Resource stays in cloud, leaves state
- For "this resource is now managed elsewhere"
- For drift you want to ignore
4. **For `import`**:
- Existing resource → state
- Doesn't modify cloud
- Subsequent plan shows mismatch (config vs real)
- Need matching config
5. **For `import` block (1.5+)**:
- Declarative import in `.tf` file
- Survives plan/apply cycle
- Better for many imports
6. **For large-scale import**:
- Generate config via `terraform plan -generate-config-out=`
- Iterate to refine
7. **For backup**:
- `terraform state pull > backup.tfstate`
- Or use S3 versioning
8. **For `moved` block** (1.1+):
- In code instead of CLI
- `moved { from = ..., to = ... }`
- Auto-applied; no state mv needed
Mark DESTRUCTIVE: state rm of resources that need destroying, import without config (next apply destroys), state mv without backup, replacing state with stale copy.
---
Use case: [refactor / import / drift]
Resource(s):
```hcl
[PASTE current config]
```
Target state: [DESCRIBE]
Why this prompt works
State surgery is high-stakes. This prompt walks safe patterns.
How to use it
- Backup first always.
- Use
movedblock when refactoring. - Import + verify plan matches.
- Document changes.
Useful commands
# Backup
terraform state pull > backup-$(date +%F).tfstate
# List
terraform state list
# Detail
terraform state show aws_instance.web
# Move
terraform state mv aws_instance.web aws_instance.app
terraform state mv 'module.old' 'module.new'
# Remove (stop tracking; keep cloud resource)
terraform state rm aws_instance.legacy
# Import
terraform import aws_instance.imported i-0123456789abcdef0
terraform import 'module.network.aws_vpc.main' vpc-0abc123
# Replace provider (e.g., when forking)
terraform state replace-provider \
registry.terraform.io/hashicorp/aws \
registry.opentofu.org/opentofu/aws
Patterns
Refactoring with moved block (preferred)
# main.tf
# OLD code (delete):
# resource "aws_instance" "web" { ... }
# NEW code:
resource "aws_instance" "app" {
ami = "ami-12345"
instance_type = "t3.medium"
}
# Migration directive (1.1+):
moved {
from = aws_instance.web
to = aws_instance.app
}
Run terraform plan — shows “moved” rather than destroy/create.
Import block (1.5+)
# Declarative import
import {
to = aws_vpc.main
id = "vpc-0abc123def456"
}
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
# Config must match the real resource for clean plan
}
# Generate config from imported resource
terraform plan -generate-config-out=generated.tf
# Review generated.tf; refine
Bulk import (script)
#!/bin/bash
# Import all EC2 instances tagged Environment=production
aws ec2 describe-instances \
--filters "Name=tag:Environment,Values=production" \
--query "Reservations[*].Instances[*].[InstanceId,Tags[?Key=='Name'].Value | [0]]" \
--output text | \
while read -r INSTANCE_ID NAME; do
ADDR="aws_instance.${NAME//-/_}"
terraform import "$ADDR" "$INSTANCE_ID"
done
State mv example (modularize)
# Before: flat resources
# resource "aws_vpc" "main" { ... }
# resource "aws_subnet" "public" { ... }
# After: under network module
# module "network" { source = "./modules/network" }
terraform state mv aws_vpc.main module.network.aws_vpc.main
terraform state mv aws_subnet.public module.network.aws_subnet.public
Provider replacement (OpenTofu migration)
# Backup
terraform state pull > pre-migration.tfstate
# Replace
terraform state replace-provider \
registry.terraform.io/hashicorp/aws \
registry.opentofu.org/opentofu/aws
# Verify
terraform plan # should be no changes
Common findings this catches
- State rm on resource you wanted destroyed — destroy it from cloud manually too.
- Import config doesn’t match — plan shows changes; refine config.
- Bulk import without naming strategy — addressing collisions.
- moved block missing for production rename — state mv before apply.
- Backup not made before surgery — risky.
- State surgery during active apply → wait for lock.
- Provider replacement leaves duplicate registries → cleanup.
When to escalate
- Cross-state moves (state pull / push) — coordinated.
- Major refactor across many modules — staged.
- Production state corruption — restore from backup.
Related prompts
-
Terraform Drift Detection Prompt
Detect Terraform drift — scheduled plans, refresh, drift reporting, alerting, distinguishing manual changes from external mutations.
-
Terraform Large State Refactor Prompt
Refactor large Terraform state — moved/removed blocks, splitting state files, extracting modules without destroy.
-
Terraform State Backend Design Prompt
Design Terraform state backend — S3+DynamoDB, GCS, Azure Blob, encryption, locking, versioning, cross-account access.