Most of the time, Terraform updates state automatically. You run terraform plan and terraform apply, and Terraform handles the internal bookkeeping for you.
But real infrastructure projects are messy. Resource names change. Modules get reorganized. Existing cloud resources need to be brought under Terraform management. Sometimes you need to inspect state directly to understand what Terraform thinks it owns.
That is where Terraform state commands come in.
These commands let you inspect or carefully adjust Terraform's record of managed infrastructure without always changing the actual cloud resources themselves.
This is powerful, but it also means you must use these commands with care. Many of them affect Terraform's memory of infrastructure, not the infrastructure itself.
State commands are most useful when you need to:
For day-to-day Terraform usage, you may not need them often. But when you do need them, they are essential.
terraform state listThe terraform state list command shows all resource addresses currently tracked in state.
terraform state listExample output:
aws_vpc.main
aws_subnet.public[0]
aws_subnet.public[1]
aws_internet_gateway.main
aws_security_group.web
aws_instance.web
module.network.aws_route_table.publicThis command answers the question: What does Terraform think it manages right now?
That is helpful when:
Suppose you refactored code and Terraform says a resource will be destroyed and recreated. terraform state list can help you confirm the existing state address before using terraform state mv.
terraform state showThe terraform state show command prints the state details for a single resource.
terraform state show aws_instance.webExample output might include:
# aws_instance.web:
resource "aws_instance" "web" {
ami = "ami-0abcdef1234567890"
arn = "arn:aws:ec2:us-east-1:123456789012:instance/i-0123456789abcdef0"
associate_public_ip_address = true
id = "i-0123456789abcdef0"
instance_type = "t3.micro"
private_ip = "10.0.1.24"
public_ip = "54.12.34.56"
}state show helps you inspect what Terraform currently knows about a resource.
Common uses:
This is state data, not necessarily a complete live API response from the provider at that exact second. It reflects Terraform's recorded or refreshed view.
terraform state mvThe terraform state mv command changes a resource's address in the state file.
It does not recreate the cloud resource. It tells Terraform, âThis existing object should now be tracked under a different address.â
terraform state mv aws_instance.web aws_instance.appImagine you rename this resource in code:
resource "aws_instance" "web" {to:
resource "aws_instance" "app" {If you do nothing, Terraform sees:
aws_instance.web is gone -> destroy itaws_instance.app is new -> create itThat is not what you want. It is the same real instance; only the logical Terraform name changed.
So you run:
terraform state mv aws_instance.web aws_instance.appNow Terraform tracks the same real instance under the new address.
Suppose a resource used to live in the root module:
aws_security_group.webAfter refactoring, it now belongs inside a module:
module.network.aws_security_group.webYou can move the state address:
terraform state mv aws_security_group.web module.network.aws_security_group.webThis is extremely useful during code refactoring.
terraform state rmThe terraform state rm command removes a resource from Terraform state without destroying the actual infrastructure object.
terraform state rm aws_instance.webAfter this command:
Common scenarios include:
state rm only changes Terraform's memory. It does not delete the real object.
That sounds safe, but it can cause confusion. If the configuration still contains the resource block, the next terraform plan may try to create a new resource because Terraform no longer sees the old one in state.
So use state rm intentionally and understand what the next plan will do.
terraform importterraform import brings an existing real infrastructure object under Terraform management by associating it with a resource address in state.
This is crucial when resources already exist before Terraform is introduced.
Suppose an S3 bucket already exists in AWS:
company-audit-logsYou write the Terraform resource block first:
resource "aws_s3_bucket" "audit_logs" {
bucket = "company-audit-logs"
}Then import it:
terraform import aws_s3_bucket.audit_logs company-audit-logsNow Terraform tracks that bucket in state.
Many real environments are not built from scratch. They contain existing networks, buckets, IAM roles, or databases. Import lets Terraform adopt them gradually.
Import adds the object to state, but you still need your configuration to match the real resource accurately. After importing, run terraform plan and expect to refine the .tf code until Terraform no longer shows unexpected changes.
terraform refreshHistorically, terraform refresh updated state to reflect the current real infrastructure without changing resources.
terraform refreshTerraform queries the provider APIs, reads current infrastructure details, and updates the state accordingly.
This can help when you suspect drift or when you want Terraform's stored view to reflect the latest real-world information.
In modern Terraform workflows, many teams prefer terraform plan or terraform apply -refresh-only because they make refresh behavior more explicit and easier to review. But it is still important to understand the refresh concept because state must periodically be reconciled with reality.
Think of refresh as: update Terraform's state to match what exists right now.
Here is a practical summary.
| Command | Use it when... |
|---|---|
terraform state list | You want to see every resource Terraform currently tracks |
terraform state show | You want detailed stored attributes for one resource |
terraform state mv | You refactored resource addresses and need to keep the same real object |
terraform state rm | You want Terraform to forget a resource without deleting it |
terraform import | A real resource exists already and must be brought under Terraform management |
terraform refresh | You want state updated to reflect real infrastructure, or you want to understand refresh behavior |
Imagine this original resource:
resource "aws_security_group" "web" {
name = "web-sg"
}Later, you realize app is a better name:
resource "aws_security_group" "app" {
name = "web-sg"
}If you run terraform plan immediately, Terraform may want to destroy and recreate the security group. To preserve the existing object, you use:
terraform state mv aws_security_group.web aws_security_group.appThis tells Terraform the object already exists; only the address changed.
State commands become even more valuable when you modularize Terraform.
Suppose a subnet resource used to live at:
aws_subnet.public[0]After creating a child module, the new address becomes:
module.network.aws_subnet.public[0]Instead of recreating the subnet, you move the state address:
terraform state mv aws_subnet.public[0] module.network.aws_subnet.public[0]That preserves the actual infrastructure while aligning state with the new code structure.
A realistic import workflow often looks like this:
terraform importterraform planFor example, importing an existing security group:
terraform import aws_security_group.web sg-0123456789abcdef0After import, Terraform knows about the security group, but your code may still be incomplete. Maybe ingress rules or tags differ. The follow-up terraform plan reveals that gap.
State commands are not everyday formatting tools. They change Terraform's understanding of infrastructure. Always know why you are running one.
State changes can be disruptive if done incorrectly. In production, versioned remote state and locking are valuable safety nets.
state rm but leaving the resource block in codeTerraform may then plan to recreate the resource because it still appears in configuration.
Import only updates state. It does not automatically write perfect .tf code for you. Expect to reconcile the configuration afterward.
state show is a live API browserIt shows Terraform's current recorded information, not necessarily a complete real-time console replacement.
terraform plan before and after significant state operations.state mv during refactors to avoid unnecessary recreation.state rm only when you intentionally want Terraform to forget an object.Terraform state commands are the advanced toolbox for managing Terraform's memory of infrastructure. They are not needed every day, but they become invaluable when you refactor code, adopt existing resources, or debug what Terraform thinks it owns.
A simple way to remember them is:
state list shows what Terraform tracksstate show shows what Terraform knows about one objectstate mv changes where Terraform tracks itstate rm makes Terraform forget itimport helps Terraform adopt itrefresh helps Terraform reconcile state with realityUsed carefully, these commands let you evolve Terraform code without unnecessary destruction and bring existing infrastructure under consistent management.
<MCQExercise exercise={{ id: 'tf-state-commands-q1', title: 'Question 1: state mv', description: 'When is terraform state mv most useful?', options: [ { id: 'a', text: 'When you want to rename or move a resource address in Terraform without recreating the real infrastructure' }, { id: 'b', text: 'When you want to destroy a resource immediately' }, { id: 'c', text: 'When you want to install a new provider plugin' }, { id: 'd', text: 'When you want Terraform to stop reading variables.tf' }, ], correctId: 'a', explanation: 'terraform state mv is designed for refactoring situations where the Terraform address changes but the real infrastructure object should remain the same.', wrongExplanations: { b: 'Destroying infrastructure is handled by configuration changes or terraform destroy, not by state mv.', c: 'Provider plugins are handled during terraform init, not by state mv.', d: 'state mv has nothing to do with variable files.', }, }} />
<MCQExercise exercise={{ id: 'tf-state-commands-q2', title: 'Question 2: state rm', description: 'What happens when you run terraform state rm aws_instance.web?', options: [ { id: 'a', text: 'Terraform deletes the EC2 instance from AWS immediately' }, { id: 'b', text: 'Terraform removes the resource from state but leaves the real EC2 instance running' }, { id: 'c', text: 'Terraform renames the instance in AWS' }, { id: 'd', text: 'Terraform converts the resource into a module output' }, ], correctId: 'b', explanation: 'terraform state rm makes Terraform forget the resource without destroying the actual infrastructure object. The cloud resource remains unless another action removes it later.', wrongExplanations: { a: 'state rm affects state, not the provider object itself.', c: 'It does not rename resources in the cloud.', d: 'It has nothing to do with outputs or modules.', }, }} />
<MCQExercise exercise={{ id: 'tf-state-commands-q3', title: 'Question 3: import', description: 'What is the main purpose of terraform import?', options: [ { id: 'a', text: 'To download Terraform modules from the registry' }, { id: 'b', text: 'To bring an existing real infrastructure resource under Terraform state management' }, { id: 'c', text: 'To copy state from one cloud provider to another automatically' }, { id: 'd', text: 'To enable remote backends inside variables.tf' }, ], correctId: 'b', explanation: 'terraform import associates an existing cloud resource with a Terraform resource address so Terraform can begin tracking it in state.', wrongExplanations: { a: 'Modules are downloaded by terraform init, not imported with terraform import.', c: 'terraform import does not migrate infrastructure between providers.', d: 'Backends are configured in the terraform block, not enabled through import.', }, }} />
Rename it in the configuration, then run terraform state mv aws_instance.old aws_instance.new so state points the existing object at the new address. Without that, Terraform sees the old address gone and a new one added, and plans a destroy and create. Newer Terraform supports moved blocks in configuration, which express the same intent declaratively, are reviewable in a pull request, and are generally the better option.
state rm removes the resource from Terraform's records but leaves the real infrastructure running. Terraform simply stops managing it. destroy deletes the actual resource. Use state rm when handing something over to another team or another configuration, or when Terraform is tracking something that no longer exists. Be careful: after state rm, a subsequent apply may try to create a fresh copy, since the configuration still declares it.
Write the resource block first, then run terraform import aws_instance.web i-0abc123 to bind it to the existing object. Import populates state but does not write configuration, so you must author the block yourself and run a plan afterwards to reconcile any differences. Expect several iterations until the plan is empty. Newer Terraform supports import blocks in configuration plus -generate-config-out, which drafts the resource block for you and makes adopting a large estate considerably less tedious.
Rarely as a standalone command. terraform plan and terraform apply refresh state automatically before computing changes, so the separate command is largely redundant and is deprecated in favour of terraform apply -refresh-only, which shows what refreshing would change and asks for approval. That variant is genuinely useful for reviewing drift deliberately without applying any configuration changes alongside it.
terraform state list enumerates every managed resource address, and terraform state show <address> prints all recorded attributes for one. Both are read-only and safe to run at any time. They are the fastest way to answer "does Terraform know about this" and "what does Terraform think this resource looks like", which between them explain most confusing plans.
Learn what Terraform modules are, when to write one, how to design a clean interface, version and source them, and move resources in without recreating them.
Learn how to call Terraform modules with a module block from local paths or the Registry, pass inputs, pin versions, run init and use module outputs.
Learn how to write a reusable Terraform module: structure main.tf, variables.tf and outputs.tf, build an S3 bucket module and call it locally.