The state file is the bridge between HCL code and real cloud resources. This episode covers the components of opentofu.tfstate, how to move it to remote backends like AWS S3, GCP GCS, and Azure Blob, and the state locking mechanism that keeps teams safe from concurrent apply conflicts.

In episode 4 we made configuration reusable with variables, locals, and outputs. As long as you run tofu apply on your own laptop, everything works — until the day you need to collaborate, switch laptops, or run a pipeline in CI. That's when the crucial question appears: where does OpenTofu store the record of what's been created?
The answer is the state file — and how you manage it determines whether your team's infrastructure is safe or conflict-prone. Episode 5 covers the components of opentofu.tfstate, remote backend options, and state locking for safe collaboration.
When OpenTofu creates a resource, it stores "proof" of its existence in the state file. This file contains the mapping between HCL code and the real cloud resource identities — resource IDs, regions, post-apply attributes, and dependencies between resources.
{
"version": 4,
"terraform_version": "1.9.0",
"resources": [
{
"type": "aws_instance",
"name": "web",
"instances": [
{
"attributes": {
"id": "i-0a1b2c3d4e5f67890",
"instance_type": "t3.micro"
}
}
]
}
]
}Without state, OpenTofu has no idea that the instance i-0a1b2c3d4e5f67890 in the cloud actually belongs to the aws_instance.web block in your code. If the state file is lost, tofu plan will assume that resource doesn't exist and try to create it again.
Warning
State stores post-apply values, including potential secrets like provider-generated passwords. Don't commit state to Git, and don't leave it on only one person's laptop — that's where remote backends come in.
By default, OpenTofu stores state in the opentofu.tfstate file in the project folder (a local backend). That's convenient for practice, but problematic for teams:
The solution: remote backends — storing state in centralized storage accessible to everyone.
A backend is where state is stored. OpenTofu supports many options; pick according to the cloud you use:
| Backend | Storage Location | Best For |
|---|---|---|
s3 | AWS S3 + DynamoDB | Teams running on AWS |
gcs | Google Cloud Storage | Teams in the GCP ecosystem |
azurerm | Azure Blob Storage | Teams in the Azure ecosystem |
http | Custom REST API | Self-managed storage |
Backend configuration is written in a backend block inside the terraform block:
terraform {
backend "s3" {
bucket = "devvnull-opentofu-state"
key = "learn-opentofu/episode-5/opentofu.tfstate"
region = "ap-southeast-1"
encrypt = true
dynamodb_table = "opentofu-state-lock"
}
}With this block in place, tofu init sets up the connection to S3, and every tofu apply writes state to the bucket — not to the local folder. All team members automatically work on the same state.
Tip
Use a unique key per project and per environment (e.g. staging/ vs prod/) so environments stay completely separate. For S3 buckets, also enable versioning as a state backup.
Imagine two people running tofu apply at the same time from the same state. Both read the old state, each makes changes, then both write their results — the first change gets overwritten and the result is inconsistent. That's what state locking prevents.
The principle is like a database mutex: before applying, OpenTofu locks the state; any other apply that comes in waits or fails; when done, the lock is released.
For the S3 backend, the lock lives in a DynamoDB table. For GCS, the mechanism uses object versioning and generation. The dynamodb_table block in the example above is where the lock lives — a DynamoDB table created once with LockID as the partition key.
aws_instance.web: Creating...
Error: Error acquiring the state lock
Error message: ConditionalCheckFailedException: The conditional request failedThat error is actually good news — it means locking is active and protecting you from conflicts.
Warning
Never use -lock=false to work around a lock error unless you fully understand the risk. Forcing an apply while the state is locked can wipe out someone else's changes. Always find out who's applying, or wait for the lock timeout.
Moving from local to remote state is as easy as tofu init -migrate-state — OpenTofu copies the existing state to the new backend automatically. The same transition was used when migrating from Terraform to OpenTofu in episode 2, so the concept is already familiar.
Summary of episode 5:
s3, gcs, azurerm, and http — choose according to your ecosystem.Your state is now stored safely and collaboratively. But that raises a follow-up question: is the state content in the backend protected if the bucket leaks? In the next episode, episode 6, we cover OpenTofu's first exclusive feature: native client-side state encryption — encrypting state on the client side before it's sent to storage. See you there!