Terraform State
Terraform is declarative: you describe the infrastructure you want, and Terraform works out what to create, change, or destroy. To do that it needs to know what already exists and which real-world object corresponds to each resource in your code. That mapping lives in the state: a JSON document recording every managed resource, its IDs, its attributes, and the dependencies between them.
State is the part of Terraform that most often goes wrong in teams. A state file on a laptop, two engineers applying at once, or secrets sitting in plain text in a bucket can each cause outages or security incidents. Getting state right (remote, locked, encrypted, split sensibly) is the foundation of running infrastructure as code safely.
TL;DR
- State maps resources in code to real infrastructure IDs and caches their attributes for planning.
- Store state in a remote backend (S3, GCS, Azure Blob, HCP Terraform) with locking and encryption, never in Git.
- State can contain secrets in plain text. Restrict access to it as tightly as to production credentials.
- Use
importblocks to adopt existing resources andmovedblocks to refactor without destroying anything. terraform state list/show/mv/rminspect and surgically edit state. Back it up first.- Split state by environment and blast radius; one giant state file is slow and dangerous.
Quick Example
A remote S3 backend with native locking, plus refactoring and import blocks:
terraform plan now shows an import and a move, not a destroy-and-create.
Core Concepts
What State Contains
For each resource instance, state records its address in code (module.vpc.aws_subnet.private[0]), the provider's ID for the real object, a snapshot of its attributes, and dependency information. Terraform uses it to:
- Know which real object to update or delete.
- Compute diffs in
planwithout re-deriving everything. - Destroy resources in the correct dependency order.
- Expose outputs to other configurations.
During plan, Terraform refreshes state by reading current attributes from provider APIs, then compares that with your configuration.
Remote Backends and Locking
Locking prevents two apply runs from modifying the same state at once, which could corrupt it or cause conflicting changes. Enable versioning on the bucket so you can roll back a bad state write.
Sensitive Data in State
State stores attribute values, and some of them are secrets: database passwords, private keys generated by the tls provider, access tokens. Marking a variable sensitive = true hides it in CLI output but not in state. Mitigations:
- Encrypt state at rest (backend encryption, KMS keys) and restrict read access.
- Prefer generating secrets outside Terraform, or use ephemeral resources and write-only arguments (Terraform 1.10–1.11+), which keep values out of state entirely.
- Reference existing secrets from a manager rather than creating their values in Terraform. See secrets management.
OpenTofu additionally supports client-side state encryption.
Import and Moved Blocks
importblocks (Terraform 1.5+) adopt existing infrastructure declaratively, reviewed inplanlike any change.terraform plan -generate-config-out=generated.tfcan even draft the resource configuration for you.movedblocks tell Terraform a resource's address changed (a rename, moving into a module,count→for_each), so it updates state instead of destroying and recreating the resource.removedblocks (1.7+) stop managing a resource without destroying it.
Structuring State
One state file per environment × component keeps plans fast and failures contained:
- A mistake in the app stack can't touch the network stack.
- Plans refresh fewer resources, so they're faster.
- Different teams can own different states with different permissions.
Share values between states through outputs read with the terraform_remote_state data source, or better, through data sources that look up real resources (for example aws_vpc by tag) or a parameter store. That avoids tight coupling to another state's internals. Tools such as Terragrunt or Terraform workspaces help manage many similar states.
Best Practices
Never Commit State to Git
State contains secrets and changes on every apply, and Git offers no locking. Add *.tfstate* and .terraform/ to .gitignore. Do commit .terraform.lock.hcl, the provider dependency lockfile.
Apply From CI, Not Laptops
Running plan and apply in a pipeline gives one controlled identity with state access, an audit trail, and consistent tool versions. See GitOps and CI/CD.
Detect Drift Regularly
Resources changed by hand or by other tools drift from state and code. Run scheduled terraform plan -detailed-exitcode jobs and alert on non-empty plans. Then either codify the change or revert it.
Back Up Before Surgery
Before terraform state mv, rm, or manual edits, run terraform state pull > backup.tfstate. Prefer moved, import, and removed blocks, which are reviewed and repeatable, over imperative state commands.
Common Mistakes
Renaming Resources Without moved
Deleting a Lock Without Checking
terraform force-unlock exists for locks left behind by crashed runs. Using it while another apply is genuinely running can corrupt state. Confirm no run is in progress first.
Local State on a Shared Project
With local state, whoever applied last holds the only accurate copy. Teammates' plans try to recreate everything. Migrate to a remote backend with terraform init -migrate-state.
FAQ
What happens if I lose my state file?
Terraform no longer knows it manages those resources. A plan will propose creating everything again, which conflicts with existing names or duplicates resources. Recover from backend versioning or backups. Failing that, re-import resources one by one with import blocks. This is why versioned remote backends matter.
Is Terraform state a security risk?
It can be. State often holds secrets in plain text and a complete map of your infrastructure. Encrypt it at rest, restrict who and what can read it, audit access, and prefer ephemeral values and external secret managers so fewer secrets end up there.
Should I use terraform_remote_state?
It works, but it grants read access to the entire other state (including secrets) and couples configurations tightly. Many teams prefer publishing specific values to a parameter store, or looking resources up with provider data sources.
What's the difference between refresh and plan?
plan refreshes by default: it reads current real-world attributes, then compares them with the configuration. terraform plan -refresh-only shows what changed outside Terraform without proposing config-driven changes, and apply -refresh-only accepts those changes into state.
Related Topics
- Terraform — The tool overview
- Terraform Workspaces — Multiple states from one configuration
- Terraform Modules — Structuring reusable infrastructure code
- Infrastructure as Code — The practice behind Terraform
- OpenTofu — The open-source fork with state encryption
- Secrets Management — Keeping secrets out of state