Terraform State
Terraform can create a server in a few lines of configuration, but those lines alone are not enough for Terraform to know which real server belongs to which resource declaration. That missing piece is state. State is what lets Terraform connect your configuration to the infrastructure it manages and determine what needs to change.
In this chapter, you’ll build a mental model of Terraform state rather than treating terraform.tfstate as just a mysterious JSON file.
Why Does Terraform Need State?
Terraform works with three important things:
Configuration
β
β
βΌ
Terraform
/ \
/ \
βΌ βΌ
State Real InfrastructureConfiguration describes what you want.
Real infrastructure is what actually exists at the provider.
State records Terraform’s understanding of the relationship between your configuration and those real objects.
Suppose you declare:
resource "some_provider_server" "web" {
name = "web-server"
}Terraform needs to answer questions such as:
- Does
some_provider_server.webalready exist? - Which real provider object corresponds to it?
- What attributes does Terraform know about that object?
- Has something changed since the last operation?
- Should Terraform create, update, replace, or remove it?
State gives Terraform the information it needs to answer those questions efficiently and consistently.
Note
State is not simply a cache of whatever exists in your cloud account. It is Terraform’s record of the objects it is managing and the information Terraform needs to reason about those objects.
Configuration, State, and Infrastructure
It helps to separate the three concepts.
Your configuration might say:
resource "some_provider_server" "web" {
name = "web-server"
size = "small"
}The provider might currently have a server whose real identifier is something like:
server-8f31Terraform’s state connects the Terraform resource address to that real object:
Terraform configuration
β
β resource "some_provider_server" "web"
βΌ
Terraform resource address
β
β state mapping
βΌ
Real provider object
β
βΌ
server-8f31This mapping is one of the most important ideas in Terraform.
The configuration does not contain the provider’s generated identifier simply because you wrote a resource block. The provider may generate that identifier when the object is created. Terraform records the relationship in state.
Resource Addresses
The resource declaration has an address:
some_provider_server.webThat address identifies the resource from Terraform’s point of view.
When you introduce multiple instances with count or for_each, addresses become even more important. For example:
some_provider_server.web[0]
some_provider_server.web[1]or:
some_provider_server.web["frontend"]
some_provider_server.web["backend"]These are resource instance addresses.
Terraform needs to know not just that a resource called web exists, but exactly which instance it is talking about.
What Is a State Snapshot?
Terraform state is a snapshot of Terraform’s current understanding of managed infrastructure.
With local state, Terraform normally stores that snapshot in:
terraform.tfstateA small state file might contain information conceptually similar to:
resource address
β
βββ provider
βββ real object identifier
βββ resource attributes
βββ other Terraform-managed informationThe actual file is JSON, but you generally should not edit it by hand.
A state file can contain much more information than the resource’s obvious configuration. Providers may return attributes that you never explicitly wrote, and Terraform may need those values when calculating future changes.
Warning
Never treat terraform.tfstate as an ordinary configuration file. Manual edits can break Terraform’s understanding of your infrastructure and can lead to incorrect plans.
How Does Terraform Use State During Planning?
When you run:
terraform planTerraform compares information from several sources.
Conceptually:
Configuration
β
βΌ
βββββββββββββ
β Terraform β
β Engine β
βββββββ¬ββββββ
β
βββββββββββ΄ββββββββββ
βΌ βΌ
State Provider/API
β β
βββββββββββ¬ββββββββββ
βΌ
PlanTerraform uses state to know what it previously associated with each resource instance. It can then interact with the provider to reconcile its knowledge with the current remote object and compare that situation with the desired configuration.
For example, imagine the configuration says:
size = "small"but the real object has been changed outside Terraform to:
size = "large"Terraform can detect that the object associated with the resource has drifted from the desired configuration and produce an appropriate plan.
This is why state and the provider API work together.
Why Not Just Query the Provider Every Time?
It is tempting to ask:
“If the cloud provider knows what exists, why doesn’t Terraform simply query everything and reconstruct its state every time?”
Because Terraform needs more than a list of objects.
The provider API may tell Terraform that an object exists, but Terraform also needs to know which configuration resource address corresponds to that object.
For example, a provider could contain:
server-101
server-102
server-103But Terraform needs to know something like:
some_provider_server.web β server-101
some_provider_server.worker β server-102
some_provider_server.monitor β server-103That relationship is Terraform’s responsibility.
There can also be objects that exist in the provider account but are not managed by the current Terraform configuration at all.
State therefore represents Terraform’s managed-world view rather than being a complete inventory of the provider account.
State and Reconciliation
Terraform is continuously trying to reconcile three views:
Desired
configuration
β
βΌ
Terraform
/ \
βΌ βΌ
State Remote
β β
βββββ¬ββββ
βΌ
Difference
β
βΌ
PlanThis is why Terraform can notice changes made outside Terraform.
For example:
- Terraform creates a server.
- Terraform records the server in state.
- Someone changes the server manually.
- Terraform later refreshes information from the provider.
- Terraform discovers a difference.
- The next plan can propose bringing the infrastructure back toward the configuration.
This is often called drift.
Tip
When debugging an unexpected plan, ask three questions: “What does my configuration declare?”, “What does Terraform’s state associate with it?”, and “What does the provider currently report?”
Inspecting Local State
You will eventually need to inspect state, but you should normally do that through Terraform rather than opening the JSON file directly.
A useful command is:
terraform showIt displays a human-readable representation of the current state.
You can also use:
terraform state listto see the resource instance addresses Terraform currently has in state.
For example:
some_provider_network.main
some_provider_server.webThese commands become especially useful when you are trying to understand why Terraform believes a particular object is managed.
Deeper state manipulation is covered in Chapter 14.
State is Valuable β And Potentially Sensitive
State deserves protection for two separate reasons.
First, losing state can make Terraform lose its record of which real objects correspond to which Terraform resources.
Second, state can contain sensitive information.
Depending on the provider and resources involved, state may contain values that you would not want casually exposed. Marking a Terraform value as sensitive affects how Terraform displays it in normal output, but it does not magically remove sensitive data from state.
That means you should treat state as sensitive infrastructure data.
Warning
Never assume that “sensitive” means “not present in state.” Protect the state storage itself.
Why You Shouldn’t Commit State to Git
A local Terraform project may look like this:
terraform-project/
βββ main.tf
βββ variables.tf
βββ outputs.tf
βββ terraform.tfstateThe .tf files are configuration. The state file is operational data.
Committing state to Git creates several problems:
- It can expose sensitive values.
- It creates a permanent history of state contents in Git.
- Multiple users can accidentally work with conflicting copies.
- State changes do not belong in normal source-code review in the same way configuration changes do.
- A Git repository is not automatically a safe state-management system.
For local experiments, keep state outside version control:
# .gitignore
terraform.tfstate
terraform.tfstate.*
.terraform/The .terraform/ directory contains Terraform’s local working data and provider installations; it should generally not be committed either.
Back Up State
State is important enough that losing it can become a serious operational problem.
With local state, Terraform can maintain a backup state file during state-changing operations. You should still think about backups deliberately rather than treating an automatically generated local backup as a complete backup strategy.
The important principle is simple:
Terraform configuration β source control
Terraform state β protected state storage + backupLater, when you work with teams and remote state, state storage, locking, and collaboration become major topics. Those belong to the production-state portion of the course.
The Mental Model to Keep
Do not leave this chapter thinking:
“Terraform has a JSON file called terraform.tfstate.”
The more useful mental model is:
State is Terraform’s record of the relationship between resource instances in your configuration and the real infrastructure Terraform manages.
That relationship lets Terraform reason about what already exists, detect changes, and construct a plan instead of blindly creating everything again.
Once you understand that, the next question becomes natural:
How does Terraform decide the order in which resources should be created or changed?
That is where the dependency graph comes in.