Why Manipulate State?#
As infrastructure evolves, you need to:
- Import existing resources into Terraform management
- Rename resources without destroying them
- Move resources into or out of modules
- Remove resources from Terraform without destroying them
- Split large state files into smaller ones
All of these require state manipulation — changing what Terraform knows without changing the actual infrastructure.
Importing Existing Resources#
The Problem#
You have infrastructure created manually (or by another tool) that you now want Terraform to manage.
flowchart LR
Existing["Existing Resource<br>(created manually)"] --> Import["terraform import"]
Import --> State["Resource in State"]
State --> Managed["Terraform Manages It"]CLI Import#
# Syntax: terraform import <resource_address> <resource_id>
terraform import aws_instance.web i-0abc123def456
terraform import aws_s3_bucket.data my-data-bucket
terraform import aws_vpc.main vpc-0abc123
terraform import 'aws_security_group.web' sg-0abc123
# Import into indexed resources
terraform import 'aws_subnet.public[0]' subnet-aaa
terraform import 'aws_subnet.public[1]' subnet-bbb
# Import into for_each resources
terraform import 'aws_iam_user.team["alice"]' aliceAfter importing, you must write the corresponding configuration — import only adds to state, it doesn’t generate .tf files (unless using import blocks).
Import Blocks (Terraform 1.5+)#
Declarative import that can also generate configuration:
# import.tf
import {
to = aws_instance.web
id = "i-0abc123def456"
}
import {
to = aws_s3_bucket.data
id = "my-data-bucket"
}# Generate configuration from imported resources
terraform plan -generate-config-out=generated.tf
# Review generated.tf, clean it up, then:
terraform applyThe generated config is a starting point — you’ll need to clean it up (remove computed attributes, add variables, etc.).
Import Workflow#
flowchart TD
A["Identify resource to import"] --> B["Find resource ID<br>(AWS Console, CLI)"]
B --> C["Write resource block in .tf"]
C --> D["Run terraform import"]
D --> E["Run terraform plan"]
E --> F{"Plan shows changes?"}
F -->|"Yes"| G["Adjust .tf to match reality"]
G --> E
F -->|"No changes"| H["✅ Import complete"]Renaming Resources#
The Problem#
You want to rename aws_instance.web to aws_instance.api_server. Without state manipulation, Terraform sees this as “destroy web, create api_server.”
moved Block (Terraform 1.1+, Preferred)#
# Add this to your configuration
moved {
from = aws_instance.web
to = aws_instance.api_server
}On next terraform apply, Terraform updates state without destroying anything. After applying, you can remove the moved block (it’s only needed once).
State mv Command#
terraform state mv aws_instance.web aws_instance.api_serverSame effect, but imperative. Prefer moved blocks — they’re declarative, reviewable in MRs, and work in CI/CD.
Moving Resources Into/Out of Modules#
Into a Module#
# Before: resource at root level
# resource "aws_vpc" "main" { ... }
# After: resource inside module
# module "networking" { source = "./modules/networking" }
moved {
from = aws_vpc.main
to = module.networking.aws_vpc.main
}Out of a Module#
moved {
from = module.networking.aws_vpc.main
to = aws_vpc.main
}Between Modules#
moved {
from = module.old_module.aws_instance.web
to = module.new_module.aws_instance.web
}Removing Resources from State#
terraform state rm#
Terraform “forgets” the resource — it still exists in the cloud, but Terraform no longer manages it:
# Remove a single resource
terraform state rm aws_instance.legacy
# Remove an entire module
terraform state rm module.old_service
# Remove indexed resource
terraform state rm 'aws_subnet.public[2]'removed Block (Terraform 1.7+)#
removed {
from = aws_instance.legacy
lifecycle {
destroy = false # don't destroy the real resource
}
}Use cases:
- Handing off a resource to another team’s Terraform
- Resource will be managed by a different tool
- Decommissioning Terraform management without destroying infrastructure
Splitting State#
When a state file grows too large, split it into separate configurations:
flowchart TD
Before["Single State<br>(VPC + ECS + RDS + Lambda)"]
Before --> After1["State: Networking<br>(VPC, Subnets, NAT)"]
Before --> After2["State: Compute<br>(ECS, Services)"]
Before --> After3["State: Database<br>(RDS, ElastiCache)"]Process#
# 1. Move resources to new state
terraform state mv -state-out=networking.tfstate aws_vpc.main aws_vpc.main
terraform state mv -state-out=networking.tfstate aws_subnet.public aws_subnet.public
# 2. Create new Terraform project for networking
# 3. Initialize with the extracted state
# 4. Use terraform_remote_state to share outputs between projectsCross-State References#
# In the compute project: read networking outputs
data "terraform_remote_state" "networking" {
backend = "s3"
config = {
bucket = "terraform-state"
key = "networking/terraform.tfstate"
region = "eu-central-1"
}
}
resource "aws_ecs_service" "api" {
network_configuration {
subnets = data.terraform_remote_state.networking.outputs.private_subnet_ids
}
}Refactoring Patterns#
count to for_each Migration#
# Before: count-based
# resource "aws_subnet" "public" {
# count = 3
# ...
# }
# After: for_each-based
resource "aws_subnet" "public" {
for_each = toset(["a", "b", "c"])
# ...
}
# Move state entries
moved {
from = aws_subnet.public[0]
to = aws_subnet.public["a"]
}
moved {
from = aws_subnet.public[1]
to = aws_subnet.public["b"]
}
moved {
from = aws_subnet.public[2]
to = aws_subnet.public["c"]
}Renaming Module Instances#
# Before: module "web_service" { ... }
# After: module "api_service" { ... }
moved {
from = module.web_service
to = module.api_service
}State Inspection#
# List everything in state
terraform state list
# aws_vpc.main
# aws_subnet.public[0]
# aws_subnet.public[1]
# module.ecs.aws_ecs_cluster.main
# Show resource details
terraform state show aws_vpc.main
# resource "aws_vpc" "main" {
# arn = "arn:aws:ec2:eu-central-1:123456789:vpc/vpc-abc"
# cidr_block = "10.0.0.0/16"
# id = "vpc-abc"
# ...
# }
# Pull entire state as JSON (for debugging)
terraform state pull | jq '.resources | length'
# 47Safety Rules#
Always backup state before manipulation
terraform state pull > backup-$(date +%Y%m%d).tfstateRun
terraform planafter any state operation — verify no unexpected changesUse
movedblocks overstate mv— they’re reviewable, reversible, and work in CI/CDNever edit state JSON manually — use
terraform statecommandsTest in dev first — practice state operations on non-production environments
Key Takeaways#
- Import brings existing resources under Terraform management — use import blocks for declarative workflow
movedblocks are the modern way to refactor — rename, restructure, modularize without destroyingstate rmmakes Terraform forget — resource continues to exist, just unmanaged- Split large states — separate concerns for speed, safety, and team ownership
- Always plan after state changes — verify the operation did what you expected
- Backup before manipulating — state corruption is hard to recover from