Terraform state is the record Terraform keeps of the infrastructure it manages. Every time Terraform creates, updates, or destroys resources, it writes information about those resources into state.
That state is usually stored in a file named terraform.tfstate when you are using local state. The file is machine-readable JSON, but the important idea is not the file format. The important idea is that Terraform needs a reliable memory of what it manages.
Without state, Terraform would have a serious problem: it could read your configuration, but it would not know whether the resources described in that configuration already exist, whether it created them before, or how those resources map to real objects in AWS, Azure, GCP, or another provider.
State solves that problem.
At a beginner level, it is tempting to think Terraform simply reads your .tf files and sends create or update requests to the cloud. In reality, Terraform constantly compares three things:
This comparison is what makes terraform plan possible.
Suppose your configuration contains this resource:
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
}On the first terraform apply, Terraform creates the VPC in AWS. AWS returns a real resource ID such as vpc-0abc123def456. Terraform then writes that mapping into state.
Now Terraform knows:
aws_vpc.mainvpc-0abc123def456On the next terraform plan, Terraform uses state to understand that aws_vpc.main already exists and should be compared, not recreated.
terraform.tfstate FileWhen using local state, Terraform stores state in a file named terraform.tfstate in your working directory.
A simplified example looks like this:
{
"version": 4,
"terraform_version": "1.9.0",
"serial": 12,
"lineage": "9f8c7d6e-1234-5678-9abc-def012345678",
"outputs": {
"vpc_id": {
"value": "vpc-0abc123def456",
"type": "string"
}
},
"resources": [
{
"mode": "managed",
"type": "aws_vpc",
"name": "main",
"provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
"instances": [
{
"attributes": {
"id": "vpc-0abc123def456",
"cidr_block": "10.0.0.0/16"
}
}
]
}
]
}You do not need to memorize every field, but it helps to understand what state generally contains.
Terraform state commonly includes:
aws_instance.webIn other words, state is the data structure Terraform uses to map your code to real infrastructure.
This mapping is the heart of Terraform.
Your Terraform configuration uses logical names:
resource "aws_instance" "web" {
ami = "ami-0abcdef1234567890"
instance_type = "t3.micro"
}Here, aws_instance.web is a Terraform address. AWS does not know that name. AWS only knows the actual EC2 instance ID, such as i-0123456789abcdef0.
State connects those two worlds.
| Terraform knows | Provider knows |
|---|---|
aws_instance.web | i-0123456789abcdef0 |
aws_vpc.main | vpc-0abc123def456 |
aws_s3_bucket.logs | bucket name / ARN |
Because of this mapping, Terraform can safely update an existing resource instead of creating a duplicate.
Suppose you change:
instance_type = "t3.micro"to:
instance_type = "t3.small"Terraform reads state, sees which real EC2 instance corresponds to aws_instance.web, and asks the provider whether that instance can be updated in place or must be replaced.
Without state, Terraform would not know which object in AWS should be changed.
Drift happens when real infrastructure changes outside Terraform.
For example, imagine someone manually changes a security group rule in the AWS console. Your .tf files still declare the original rule, but reality is now different.
During terraform plan, Terraform refreshes what it knows about the real infrastructure, compares that to state and configuration, and can detect the difference.
That is why Terraform is so valuable for infrastructure as code: your .tf files remain the source of truth, and state gives Terraform the context needed to identify drift.
By default, Terraform stores state locally in terraform.tfstate in the current working directory. This is called local state.
Local state is simple and great for learning, experimentation, or solo work. But it has important limitations.
For teams, most projects use remote state stored in a backend such as:
Remote state improves collaboration, durability, and access control.
Terraform state is sensitive. This is one of the most important beginner lessons.
Why? Because state may contain:
Even if your Terraform code marks a variable or output as sensitive, the actual value may still appear in state if the provider stores it.
Real terraform.tfstate files should not be committed to a repository.
Good .gitignore entries usually include:
.terraform/
*.tfstate
*.tfstate.*
crash.logIf state gets committed accidentally, you may expose secrets and internal infrastructure details to everyone with repository access.
In production, state should be protected with:
terraform.tfstate.backup FileTerraform often creates a file named terraform.tfstate.backup alongside the main state file.
This backup is a previous copy of the state, typically created before Terraform writes a new version. It exists to give you a fallback if state becomes corrupted or a write operation fails.
That backup is useful, but it has the same security concerns as the main state file.
Important reminders:
.terraform Directory vs the State FileBeginners often confuse the .terraform directory with the state file, but they are not the same thing.
.terraform directory is forThe .terraform directory stores Terraform's local working data, such as:
The state file stores Terraform's mapping of configuration to infrastructure.
.terraform/ = Terraform working directory dataterraform.tfstate = infrastructure state recordBoth are generated artifacts, but they serve very different purposes.
In most projects, .terraform/ should also be ignored in Git.
State locking prevents two Terraform operations from modifying the same state at the same time.
Imagine this scenario:
terraform applyterraform apply 10 seconds laterWithout locking, the state could become inconsistent or corrupted.
A remote backend with locking solves this by allowing only one writer at a time.
Locking is one of the biggest reasons teams move from local state to a remote backend.
Once multiple people work on the same infrastructure, state management becomes a team discipline, not just a local file concern.
Teams need state to be:
That is why remote backends are considered standard practice in production Terraform setups.
Terraform does query providers, but it still needs state to know which real objects belong to which Terraform resource addresses and how to reason about changes safely.
No. Sensitive settings mostly affect display in CLI output. State security is a separate concern.
.terraform folder is my state.âNo. The .terraform directory is local working data. The state file is a separate artifact.
Local state is fine for learning and solo demos. Real team environments usually require a remote backend.
.gitignore updated for .terraform/ and *.tfstate*.Terraform state is not just an implementation detail. It is one of the core reasons Terraform can plan changes accurately, detect drift, and manage infrastructure safely over time. The terraform.tfstate file gives Terraform the memory it needs to connect your code to real cloud resources.
For beginners, the most important lessons are simple:
Once you understand state, the next step is learning how backends store and protect it in real-world environments.
One final beginner tip: treat state as part of the Terraform system, not an optional side file. If state is missing, outdated, exposed, or changed carelessly, every later Terraform operation becomes harder to trust. Healthy Terraform workflows start with healthy state management.
<MCQExercise exercise={{ id: 'tf-state-overview-q1', title: 'Question 1: Purpose of State', description: 'Why does Terraform need state?', options: [ { id: 'a', text: 'So it can remember which real infrastructure objects correspond to Terraform resources' }, { id: 'b', text: 'So it can replace provider plugins with local scripts' }, { id: 'c', text: 'So it can avoid calling cloud APIs during plan' }, { id: 'd', text: 'So it can store only comments from .tf files' }, ], correctId: 'a', explanation: 'Terraform uses state to map logical resource addresses in code to the real objects created in the provider, such as VPC IDs, EC2 IDs, and bucket names. That mapping is essential for safe planning and updates.', wrongExplanations: { b: 'State is unrelated to replacing provider plugins. Provider plugins still interact with the cloud APIs.', c: 'Terraform still makes read calls to cloud APIs during planning. State does not remove that need.', d: 'State stores infrastructure metadata, not comments from Terraform configuration files.', }, }} />
<MCQExercise exercise={{ id: 'tf-state-overview-q2', title: 'Question 2: Security', description: 'Why should terraform.tfstate usually never be committed to Git?', options: [ { id: 'a', text: 'Because Git cannot store JSON files' }, { id: 'b', text: 'Because Terraform refuses to run if a state file is version-controlled' }, { id: 'c', text: 'Because state can contain sensitive data such as secrets and infrastructure details' }, { id: 'd', text: 'Because the file is only used on Windows systems' }, ], correctId: 'c', explanation: 'State may contain secrets, credentials, internal endpoints, and resource metadata. Committing it to Git can expose sensitive information to many people.', wrongExplanations: { a: 'Git can store JSON just fine. The problem is the sensitive content, not the file format.', b: 'Terraform does not automatically refuse to run just because the state file is tracked by Git.', d: 'terraform.tfstate is relevant on all operating systems, not just Windows.', }, }} />
<MCQExercise exercise={{ id: 'tf-state-overview-q3', title: 'Question 3: Locking', description: 'What is the purpose of state locking in Terraform?', options: [ { id: 'a', text: 'To prevent two Terraform operations from modifying the same state at the same time' }, { id: 'b', text: 'To stop users from reading variables.tf' }, { id: 'c', text: 'To permanently disable terraform destroy' }, { id: 'd', text: 'To encrypt every provider request automatically' }, ], correctId: 'a', explanation: 'State locking protects shared infrastructure workflows by ensuring that only one Terraform process can update the same state at a time.', wrongExplanations: { b: 'Locking is about protecting state updates, not restricting access to source files.', c: 'Locking does not disable terraform destroy. It only coordinates concurrent state access.', d: 'Locking and encryption are different concerns. Locking prevents simultaneous writes.', }, }} />
Yes, treat it as a secret. State stores resource attributes in plain text, which includes database passwords, generated keys and anything passed through a variable marked sensitive, that flag only redacts console output, not state. Never commit it to version control, store it in an encrypted remote backend with restricted access, and audit who can read it. A state file is often the single most valuable artifact in an infrastructure repository.
Terraform loses all knowledge of what it manages, and the next apply will try to create everything again. Producing duplicates or failing on name conflicts. Recovery means either restoring from a backup, which is why remote backends with versioning matter, or importing every existing resource back into a fresh state with terraform import, which is slow and error-prone for anything non-trivial. Enable versioning on the state bucket before you need it.
Drift is when real infrastructure no longer matches state, usually because someone changed something in a console or another tool. Terraform detects it by refreshing, querying the provider for current attributes, and comparing against state during a plan, then showing the difference as changes it intends to make. That is why an untouched configuration can produce a non-empty plan: Terraform is proposing to undo the out-of-band change.
.terraform/ is a local working directory holding downloaded provider binaries, fetched modules and backend configuration. It is disposable, machine-specific, recreated by terraform init, and belongs in .gitignore. The state file records what infrastructure exists and is irreplaceable. Losing .terraform costs you one init, while losing state can cost a rebuild of your entire environment.
Not strictly, but it is worth it early. Local state lives on one machine, so it is lost with that laptop, has no version history, and cannot be shared the moment a second person or a CI pipeline is involved. An S3 bucket with versioning and encryption takes ten minutes to set up and removes a whole category of future pain. Migrating later is possible but always less pleasant than starting that way.
Configure a Terraform remote backend on S3 with DynamoDB state locking, bootstrap it, compare Terraform Cloud and GCS, and migrate state safely.
Use the Terraform state commands with confidence: state list, show, mv and rm, plus terraform import and refresh, and when each is safe to run.
Learn what Terraform modules are, when to write one, how to design a clean interface, version and source them, and move resources in without recreating them.