kevin.notes
← All field notesFROM THE NOTEBOOK

If My Infrastructure Is in Git, Why Does Terraform Need State?

A question from my Terraform learning sessions helped me understand configuration, state, and remote backends.

KKevinLearning in public

While learning Terraform, I got stuck on a question that seemed almost too basic to ask.

If my infrastructure is described in code, and that code is already in Git, why does Terraform need a separate state file? And if it does need one, why not commit that file alongside everything else?

This came up while I was learning how to store state in S3. I could follow the configuration, but I wanted to understand what the backend was actually solving.

Imagine an EC2 instance declared in Terraform. Its configuration specifies an AMI, an instance type, and some tags. Terraform also gives it an address, such as aws_instance.web.

Once AWS creates the instance, it returns an instance ID. That ID identifies the actual machine. The Terraform address identifies the resource in my configuration.

State connects the two.

Terraform state maps a resource address in configuration to an actual AWS resource. A backend stores the state; Git stores the configuration.

Conceptual diagram. The instance ID is illustrative. State records the mapping; it does not execute changes itself.

Alongside this mapping, state stores recorded resource attributes and metadata Terraform needs to manage dependencies. The next time I change aws_instance.web, Terraform knows which real object to inspect. HashiCorp explains this mapping in its state documentation.

During the conversation, I described state as a snapshot of infrastructure. That helped initially, but the analogy needs limits: state is Terraform’s record of the resources it manages. It is not a live inventory of my entire AWS account, or a backup of a server’s disk.

An update does not always mean a new resource

My next question was whether a resource gets a new ID every time I run apply.

The distinction is between updating an existing object and replacing it. Changing an EC2 tag usually keeps the same instance. A change that requires replacement creates another instance with a different ID. The plan shows which operation Terraform intends to perform.

So, updating state does not mean recreating everything. State keeps track of the relationship as the infrastructure changes.

Why Git is not enough

This was the part I needed to think through. Git already shares files and tracks versions. Why introduce another place to store one?

Consider this hypothetical situation:

  1. A teammate and I both pull the same configuration and state. Neither copy records an instance yet.
  2. I run Terraform. It creates the instance and records its ID in my local state.
  3. Before I commit and push that updated file, my teammate runs Terraform.
  4. Their copy lacks the mapping, so Terraform may plan to create another instance.

A later Git merge cannot undo an infrastructure change that has already happened.

I did not encounter this as a production incident. It was a thought experiment that helped me see the problem: sharing a file through Git depends on commits and pulls, while Terraform needs coordinated access to state during its operations.

A remote backend lets Terraform read and write shared state directly. When supported and enabled, state locking also coordinates concurrent operations against that state. HashiCorp’s remote state guide describes why this matters for teams.

S3 storage and state locking are separate choices

I now separate four questions:

Concept The question it answers
Configuration What should the infrastructure look like?
State Which actual objects correspond to these resource addresses?
Backend Where and how is the state stored and accessed?
State locking How are concurrent changes to the same state coordinated?

A local file is already a backend. Moving state to S3 changes where it is stored; it does not automatically enable locking.

The current S3 backend supports lock files through use_lockfile = true, which is disabled by default. It also needs the appropriate permissions and a compatible Terraform version. For a local AWS emulator, writing the setting is not proof that locking works: that behaviour still needs testing. S3 backend documentation.

There is another reason to keep state out of Git: it can contain sensitive values, depending on the resources being managed. I should not assume every state file contains secrets, but I should not treat it like ordinary source code either. Remote state and sensitive data.

The useful change in my understanding is that I can now explain what a remote backend is for. When I see an S3 backend configuration, I know to look beyond the bucket name: where is the state, who can access it, and is locking actually configured?

Those questions make the configuration much easier to reason about.

✳

Another question, a little clearer.

Back to the notebook ↗