State is how Terraform remembers what it built. Inspect and surgically edit it, store it remotely with locking for teams, and import resources that already exist.
Why: after an apply, Terraform records what it created in a state file (terraform.tfstate) — the map between your HCL and the real objects. It is how plan knows what already exists. Note: state can hold secrets in plaintext, so never commit it to git; the remote-state topic below is the real answer for any shared project.
your .tf files terraform.tfstate real world
────────────── ───────────────── ──────────
desired state ◀──▶ what Terraform ◀──▶ actual
(what you want) last created/knew resourcesWhy: you read state, never hand-edit the file. state list shows every tracked resource address; state show prints one resource's full attributes; the graph and output round it out. These are safe, read-only, and your first stop when reconciling config with reality.
List every resource Terraform is tracking
terraform state listShow one resource's full state
terraform state show local_file.helloAll output values, as JSON
terraform output -jsonWhy: sometimes config and state drift apart — you renamed a resource, or want Terraform to forget one without destroying it. state mv renames/moves an entry (avoiding a needless destroy-recreate); state rm stops tracking a resource while leaving the real thing in place. Powerful and sharp — back up state first.
Rename in state to match a renamed resource (no recreate)
terraform state mv local_file.hello local_file.greetingStop managing a resource WITHOUT destroying it
terraform state rm local_file.greetingWhy: a local state file cannot be shared and can be clobbered when two people apply at once. A remote backend (S3, Azure Blob, GCS, Terraform Cloud) stores state centrally and, crucially, LOCKS it during an apply so only one run mutates it at a time. This is mandatory for any team. Run init after adding it to migrate existing state.
terraform {
backend "s3" {
bucket = "my-tf-state"
key = "prod/terraform.tfstate"
region = "eu-west-1"
dynamodb_table = "tf-locks" # the lock table -> prevents concurrent applies
encrypt = true
}
}Why: infrastructure created by hand (or another tool) is not in state, so Terraform does not know about it. An import block (with matching HCL) brings it under management without recreating it — apply then writes it into state. This is how you adopt brownfield infrastructure. Note: older workflows use the terraform import command instead.
# 1) Write the resource block that should manage it
resource "local_file" "existing" {
filename = "already-here.txt"
content = "created outside terraform"
}
# 2) Tell Terraform which real object it maps to
import {
to = local_file.existing
id = "already-here.txt"
}
# Then run: terraform plan, then terraform apply