Skip to content
ExponaBLOG
TERRAFORM, IAC & CLOUD AUTOMATION · DAY 012 / 100

Terraform state commands: a hands-on guide for engineers

Fix state without editing files.

Expona Admin6 min read

Introduction

You're halfway through a refactor, and you realize you need to move a resource from one module to another without destroying and recreating it. Or you've imported infrastructure by hand and need to clean up orphaned state entries that no longer match your codebase. These are the moments when terraform state commands: a hands-on guide for engineers becomes essential knowledge. Rather than manually editing state files or risking production downtime, you can use Terraform's built-in state manipulation tools to keep your infrastructure declaration and reality in sync—safely and repeatably.

Why state commands matter in practice

State is Terraform's source of truth about what infrastructure exists. When your code and state drift—whether through refactoring, mistakes, or external changes—the consequences range from duplicate resources to unexpected deletions. Many engineers reach for risky workarounds: editing terraform.tfstate directly, destroying and recreating resources, or ignoring the problem entirely.

The state commands (state list, state mv, state rm) are designed to let you fix these situations without touching JSON files or burning down infrastructure. They're especially valuable in CI/CD pipelines where you need deterministic, auditable changes.

terraform state list: see what you're managing

Before you can manipulate state, you need to know what's in it. terraform state list outputs every resource Terraform is currently tracking.

hcl
$ terraform state list
aws_security_group.app
aws_instance.web[0]
aws_instance.web[1]
aws_s3_bucket.data
aws_s3_bucket_versioning.data
module.database.aws_rds_cluster.main
module.database.aws_rds_cluster_instance.main[0]
module.database.aws_rds_cluster_instance.main[1]

This command is your first diagnostic step. It shows you the full address of every resource, including those in modules and those using count or for_each. You can filter by pattern:

hcl
$ terraform state list 'module.database.*'
module.database.aws_rds_cluster.main
module.database.aws_rds_cluster_instance.main[0]
module.database.aws_rds_cluster_instance.main[1]

Real-world use case: you've just merged a large refactor that moved resources between modules. Before running terraform plan, use state list to verify the old addresses are gone and new ones are present. This prevents surprises during the plan phase.

terraform state mv: rename and reorganize without recreation

Moving a resource in Terraform code—renaming its local name, moving it to a different module, or changing its index—will normally cause Terraform to plan a destroy-and-recreate cycle. For stateful resources like databases or load balancers, that's unacceptable.

terraform state mv rewrites the state to point the old address to the new one, preserving the actual cloud resource. The syntax is straightforward:

hcl
terraform state mv SOURCE DESTINATION

Scenario 1: Simple rename

You have a security group called aws_security_group.app in your root module, but you're moving it into a network module and renaming it for clarity:

hcl
terraform state mv \
  'aws_security_group.app' \
  'module.network.aws_security_group.app'

After running this command, update your Terraform code to define the resource at the new location. When you run terraform plan, you'll see no changes—the state and code are back in sync, and your actual security group remains untouched.

Scenario 2: Moving indexed resources

You have instances created with count and need to switch to for_each for better management:

hcl
# Old code
resource "aws_instance" "web" {
  count = 3
  # ...
}

# New code
resource "aws_instance" "web" {
  for_each = var.instance_configs
  # ...
}

Move each instance individually:

hcl
terraform state mv 'aws_instance.web[0]' 'aws_instance.web["prod-1"]'
terraform state mv 'aws_instance.web[1]' 'aws_instance.web["prod-2"]'
terraform state mv 'aws_instance.web[2]' 'aws_instance.web["prod-3"]'

Again, the actual EC2 instances keep running; only the state mapping changes.

Scenario 3: Consolidating modules

You've split compute resources across two modules (module.compute_a and module.compute_b) and now want to merge them:

hcl
terraform state mv 'module.compute_b.aws_instance.app' 'module.compute_a.aws_instance.app_b'

The key insight: state mv is a surgical tool. It doesn't touch the cloud resource itself—only Terraform's knowledge of where it lives in your configuration.

terraform state rm: stop managing a resource

terraform state rm removes a resource from state without destroying it in the cloud. Use this when you want to stop letting Terraform manage something—typically because you're handing it off to another tool, another team, or switching to manual management.

hcl
terraform state rm 'aws_s3_bucket.old_temp_data'

Real-world scenario

Your team is migrating from Terraform to a custom orchestration script for certain infrastructure. You want to remove those resources from Terraform's state without destroying them:

hcl
terraform state rm 'module.legacy.aws_lambda_function.*'

The wildcard pattern removes all Lambda functions under module.legacy from state. The functions keep running in AWS, but Terraform no longer manages them. You can later import them back if needed, or leave them orphaned if they're being decommissioned soon.

Caution

Unlike state mv, there's no undo for state rm (without a backup). Always take a state backup before removing resources:

hcl
terraform state pull > state.backup
terraform state rm 'aws_instance.target'

moved blocks: the safer way to refactor

Since Terraform 1.1, there's a declarative alternative to manual state commands: the moved block. Instead of running commands, you declare the old and new addresses in your configuration, and Terraform applies the rename automatically during plan.

hcl
moved {
  from = aws_security_group.app
  to   = module.network.aws_security_group.app
}

resource "aws_security_group" "app" {
  # This resource no longer exists at the root level
  # It's now in module.network
}

When you run terraform plan, Terraform sees the moved block and knows that aws_security_group.app in state should now map to the module address. The plan shows no destructive changes. After you apply, the moved block can be removed—it's a temporary bridge.

Why moved blocks are better for most refactors

  • Declarative and auditable: Your intent is in code, reviewable in pull requests. Someone can see exactly which resources are being renamed and why.
  • Repeatable across teams: Instead of running commands manually, the moved block works the same way in every environment and CI/CD run.
  • Works with automated testing: You can validate the refactor with terraform plan in a test pipeline before applying to production.
  • Self-documenting: Future team members understand the structure change without tribal knowledge.

When to use state commands instead

Use terraform state mv or terraform state rm directly when:

  • You're fixing accidental state corruption or mismatches discovered after the fact.
  • You're working with imported or manually-created resources that need adjustment.
  • You need to quickly resolve a production incident without waiting for a pull request cycle.
  • You're updating state for a resource that's being dropped or whose code is already deleted.

Common mistakes

Forgetting to update code before running state mv

If you run terraform state mv 'aws_instance.web' 'aws_instance.app' but your code still defines aws_instance.web, Terraform will think the resource at the old address is unmanaged and plan to create a new one. Always update your code first, then run the state command, so code and state align.

Using state rm instead of state mv for a refactor

Removing a resource from state when you really want to rename it leaves the cloud resource orphaned and creates a duplicate in state when Terraform sees the new code. Use state mv to preserve the mapping.

Not backing up state before rm

terraform state rm is permanent (barring a backup). If you remove the wrong resource, you'll need to re-import it or restore from backup. Always run terraform state pull > backup.json first.

Relying on state commands in CI/CD without version control

If your CI/CD pipeline runs state mv as an undocumented step, future maintainers won't understand why state and code differ. Use moved blocks or document the reason for manual state changes in your runbook.

Key takeaways

  • terraform state list is your diagnostic tool—always use it to verify the current state before making changes.
  • terraform state mv renames resources in state without touching cloud infrastructure, essential for refactors and reorganization.
  • terraform state rm orphans a resource from Terraform without destroying it; always back up state first.
  • Prefer moved blocks for planned, auditable refactors; use state commands for fixes and incident response.
  • State commands are powerful precisely because they decouple your configuration from your infrastructure—use that power carefully and deliberately.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles