Infrastructure State File
An infrastructure state file is the record a declarative infrastructure tool keeps of which real resources correspond to which declared ones. Terraform’s state is the canonical example.
The tool cannot work without it. Terraform stores state about the workspace’s managed infrastructure and uses it to map real-world resources to the configuration, track metadata, and improve performance for large infrastructures. The primary purpose is narrower and more important than a cache: state stores the bindings between objects in a remote system and the resource instances declared in the configuration.
State is Terraformโs normal authoritative record of resource bindings, but an import declaration can also identify an existing object and its intended address. If a plan unexpectedly proposes creation, stop writes and verify the backend, workspace, and resource address first. Restore a verified state snapshot or reconstruct supported bindings through import and review the resulting plan. A missing binding can lead to creation or a provider conflict; it does not guarantee a second database. Recovering bindings does not restore database contents.
For example, a proposed new orders database may mean the wrong workspace was selected, not that the production database disappeared. Confirm identity and ownership before importing; do not let two states manage the same object.
One boundary to draw before going further: a user-managed state file is Terraform’s design, not a requirement of declarative infrastructure. The binding has to exist somewhere, but it does not have to be a file you own and protect. Azure’s Bicep is the clean counterexample โ its documentation lists as a feature “no state or state files to manage: Azure stores all states,” and describes its preview operation as one that “checks the current state of your environment and eliminates the need to manage this state.” The service holds the record; there is no file to lose, no backend to configure, and no locking question of your own.
So everything below โ hand-editing, locking, sensitivity, recovery โ is about tools that hand you the record. When you evaluate such a tool, the question worth asking early is where does the binding live and who is responsible for it, because that single answer determines whether most of this page applies to you at all.
A readable operational record, not source code
This is the distinction that decides whether a team handles it well. Local state is readable JSON, which can invite treating it like source. A configured backend can store state remotely instead. It is closer to a production database: derived from reality, authoritative about reality, and damaging when wrong.
Five handling rules follow from that.
- Keep it out of version control. Avoid storing state in a version control system or other storage that does not support state locking and secure access control, because that can cause data loss or expose secrets stored in the state file. There are two risks: losing it, and leaking it.
- Do not hand-edit it. State is stored as a JSON text file, but it should not be edited directly. Use the tool’s own commands, which also insulate you from format changes within the state itself.
- Insist on locking. If the backend supports it, Terraform locks state for all operations that could write state, which prevents others from acquiring the lock and potentially corrupting the state. If locking fails, Terraform does not continue. Check before choosing where to keep state, because not all backends support locking.
- Treat it as sensitive data. State and plan files contain detailed information about the infrastructure, including resource attributes and metadata that can hold sensitive values such as initial database passwords or API tokens. Local state is a plaintext file that includes any secret values defined in the configuration.
- Prepare recovery. Back up state securely and test restoration of verified snapshots. If restoration is unavailable, supported imports may reconstruct bindings, but require identity and ownership checks. State recovery and recovery of application data are separate procedures.
Review who can read state and saved plans. They may contain credentials and sensitive infrastructure details, although the contents depend on resource, provider, and ephemeral or write-only support. Repository access should not automatically grant artifact access; apply least privilege to these records too.
The usual answer โ reference secrets rather than embedding them โ is right and incomplete, because it does not say which component ends up holding the value. If the tool reads a secret and passes it to a persistent resource argument, it can enter state regardless of its origin; supported ephemeral and write-only paths are exceptions. Terraform is explicit that marking something sensitive does not prevent storage: such values are kept “in both state and plan files, and anyone who can access those files can access your sensitive values.” What omits them is a purpose-built path โ ephemeral values are “available at runtime” but Terraform “omits them from state and plan files entirely,” with version and provider conditions attached.
Prefer a design where Terraform never receives plaintext when possible. Otherwise use supported omission mechanisms or protect state and saved plans with encryption, access controls, and retention limits. Rotate credentials after actual or suspected unauthorized exposure, and address retained copies; protected state storage alone is not evidence of compromise. See secrets management.
On force-unlock, where a tool offers it: the command looks like a fix, but the documentation cautions: “Be very careful with this command. If you unlock the state when someone else is holding the lock it could cause multiple writers.” Two concurrent writers is how a state file becomes wrong in ways nobody can reconstruct.
When the record is wrong
Because the record sits between the declaration and the reality, its failures present as three distinct symptoms โ and telling them apart is most of the diagnosis.
| Symptom | What has happened | Direction of the fix |
|---|---|---|
| The tool proposes to create something that already exists | A missing binding, wrong backend/workspace, or unmanaged existing object | Adopt the existing object into the record rather than letting it be recreated |
| The tool proposes to change something you did not change | Reality moved; a refresh has revealed the difference | Decide which side is right: apply the declaration, or update the declaration to match a deliberate change |
| The tool proposes to destroy something still in use | Several different causes share this symptom โ see below | Stop, and read the plan to find out which one it is before approving anything |
Terraform normally refreshes managed objects during planning and applying, but refresh can be disabled; it is not a prerequisite to every command. A refresh-only operation updates the record without changing configuration. When configuration drift is found, decide the intended state before changing either side.
The third row needs unpacking, because “a proposed destroy means the declaration is gone” is a diagnosis that will send you looking in the wrong place. A plan can propose to destroy an object for at least four different reasons, and they have different fixes.
- Replacement, from an attribute that cannot change in place. This is the common one and the declaration is entirely intact. “When Terraform must change a resource argument that cannot be updated in-place due to remote API limitations, Terraform destroys the existing object and then creates a new replacement object with the new configured arguments.” Read the plan to find which attribute forced it.
- The declaration was removed. Terraform “destroys the actual infrastructure during an apply operation if you remove the resource’s configuration” โ and note that this happens “even if
prevent_destroyis enabled,” because that rule is not recorded in state. - The address moved. Renaming a resource or restructuring modules without telling the tool reads as one object disappearing and another appearing.
- The object belongs to someone else. Two configurations managing one object, which is the case where stopping is the whole answer.
So the diagnostic step is to distinguish delete from replace in the plan output, and for a replacement to identify the attribute that triggered it. A replacement also has an ordering question attached: by default the old object is destroyed first, and create_before_destroy reverses that โ but it is “an opt-in behavior because many remote object types have unique name requirements or other constraints that must be accommodated for both a new and an old object to exist concurrently.”
Keep state and execution credentials separate across environments. Within an environment, multiple root configurations and states can follow ownership, lifecycle, or blast-radius boundaries. Give each managed object one authoritative owner and make dependencies between roots explicit. Separate states without appropriately scoped credentials do not establish the intended access boundary.
Finally, a note on the word. “State” here means the tool’s record of managed resources. It has nothing to do with the state a stream processor keeps between events, or with whether a service is stateless; those are different problems that happen to share a noun. How the record fits with review, module boundaries, and secrets is worked through in The File That Says What Exists, and the wider practice is infrastructure as code.
References
- Terraform documentation, State
- State Locking
- Terraform documentation, lifecycle meta-argument
- Microsoft Learn, What is Bicep?
- Manage sensitive data in your configuration
- Terraform, Import
- Terraform, terraform plan
- Terraform, Workspaces
Discover more from Insightful Data Lab
Subscribe to get the latest posts sent to your email.
