| name | state-management |
| type | skill |
| description | Manage Terraform remote state — backend setup, state isolation, locking, import, mv, and state surgery. |
| related-rules | ["state-management.md","iac-standards.md"] |
| allowed-tools | Read, Write, Edit, Bash |
Skill: Terraform State Management
Expertise: Remote backends, state isolation, import, mv, rm, state surgery, cross-stack references.
When to load
When setting up a new Terraform backend, debugging state lock, importing manually-created resources, or safely moving resources between state files.
Backend Setup Patterns
# AWS S3 + DynamoDB lock
terraform {
backend "s3" {
bucket = "mycompany-terraform-state"
key = "${var.environment}/${var.component}/terraform.tfstate"
region = "us-east-1"
encrypt = true
kms_key_id = "alias/terraform-state"
dynamodb_table = "terraform-state-lock"
}
}
# GCS (GCP) — built-in locking, no separate lock table needed
terraform {
backend "gcs" {
bucket = "mycompany-terraform-state"
prefix = "${var.environment}/${var.component}"
}
}
# Terraform Cloud / HCP Terraform
terraform {
cloud {
organization = "mycompany"
workspaces { name = "production-network" }
}
}
State Isolation (per environment × component)
state/
├── staging/
│ ├── network/terraform.tfstate
│ ├── k8s-cluster/terraform.tfstate
│ └── databases/terraform.tfstate
└── production/
├── network/terraform.tfstate
├── k8s-cluster/terraform.tfstate
└── databases/terraform.tfstate
Rule: Staging and production must use separate state files (separate key/prefix, ideally separate bucket).
Cross-Stack Values (avoid terraform_remote_state)
# ✅ Publish outputs to SSM Parameter Store
resource "aws_ssm_parameter" "vpc_id" {
name = "/${var.environment}/network/vpc_id"
type = "String"
value = aws_vpc.this.id
}
# ✅ Consume in another stack via data source
data "aws_ssm_parameter" "vpc_id" {
name = "/${var.environment}/network/vpc_id"
}
resource "aws_subnet" "app" {
vpc_id = data.aws_ssm_parameter.vpc_id.value
}
State Import (bring unmanaged resource under TF control)
resource "aws_s3_bucket" "legacy" {
bucket = "my-legacy-bucket"
}
terraform import aws_s3_bucket.legacy my-legacy-bucket
terraform plan
State Move (rename / reorganize)
# Safe rename using moved{} block (Terraform 1.1+) — no CLI required
moved {
from = aws_instance.web
to = aws_instance.this["web-01"]
}
# After apply, remove the moved{} block
terraform state pull > backup-$(date +%Y%m%d-%H%M%S).tfstate
terraform state mv \
'aws_security_group.old_name' \
'aws_security_group.new_name'
State Surgery (break-glass commands)
terraform state list
terraform state show 'aws_instance.this["web-01"]'
terraform state rm 'aws_s3_bucket.legacy'
terraform force-unlock <LOCK_ID>
terraform state pull | jq '.resources[] | {type: .type, name: .name}'
terraform apply -replace='aws_instance.this["web-01"]'
Debugging Lock Issues
aws dynamodb get-item \
--table-name terraform-state-lock \
--key '{"LockID": {"S": "mycompany-terraform-state/production/network/terraform.tfstate"}}'
terraform force-unlock <LOCK_ID_FROM_ERROR>