11.1 The Local Backend
Key Takeaways
- If you omit both a backend block and a cloud block, Terraform 1.12 uses the local backend and writes terraform.tfstate in the working directory
- The local backend also writes terraform.tfstate.backup with the previous snapshot and locks with local system APIs, which does not protect two laptops
- An explicit backend "local" block can set path and workspace_dir; omitting the block is the same default backend
- Local state is fine for learning and a single engineer; HashiCorp recommends HCP Terraform or a remote backend for teams
- Never commit terraform.tfstate; changing the backend requires terraform init again, usually with -migrate-state or -reconfigure
11.1 The Local Backend
Quick Answer: Omit
backendandcloudand Terraform 1.12 uses the local backend. It writesterraform.tfstate(current snapshot) andterraform.tfstate.backup(previous snapshot) in the working directory, and it locks with local system APIs. That is enough for a tutorial on one laptop. It is not a team backend. Do not commit the state file. Changing the backend later requiresterraform initagain.
Objective 6a on Terraform Associate (004) asks you to describe the local backend. Chapter 3.4 already explained why state exists (bindings, metadata, cached attributes). This section is the default storage engine for those snapshots. Official references: Backend Type: local and State.
Default backend if you configure nothing
HashiCorp's backend overview is explicit: Terraform uses a backend called local by default. You do not have to write a backend block to get it. A root module that only has required_providers and resource blocks still has a backend — the local one.
The local backend is documented as Kind: Enhanced. That label means three things at once:
- It stores state on the local filesystem.
- It locks that state using system APIs.
- It performs operations locally (plan and apply run on your machine, not on a remote runner).
Those three jobs are what a backend is. The local backend is not a special exception; it is the built-in implementation you get when you have not selected another store.
A cloud block (HCP Terraform / Terraform Enterprise) is an alternative to any backend block, including local. If the configuration has no cloud block and no backend block, the engine is still local. You cannot combine cloud and backend — that pairing is illegal HCL and is tested in 6c.
The two files on disk
HashiCorp's state page states the default filenames:
terraform.tfstate— the current workspace snapshot, JSON text, next to your.tffilesterraform.tfstate.backup— the previous snapshot, rewritten when Terraform writes a new current snapshot
| File | What it is | When it appears | Commit? |
|---|---|---|---|
terraform.tfstate | Current bindings, metadata, cached attributes | After the first successful write (usually the first apply) | No |
terraform.tfstate.backup | Previous local snapshot | After the second successful write | No |
.terraform/terraform.tfstate | Backend configuration metadata for this working directory, not the infrastructure snapshot | After terraform init | No |
.terraform.lock.hcl | Pinned provider versions and checksums | After init | Yes |
Do not confuse .terraform/terraform.tfstate with terraform.tfstate. The file under .terraform/ records which backend this directory is using. The file in the working directory is the infrastructure state when the backend is local. After you migrate to S3 or HCP Terraform, the real snapshot lives remotely; the leftover local terraform.tfstate is no longer the source of truth.
You can name the snapshot something else with an explicit block:
terraform {
backend "local" {
path = "relative/path/to/terraform.tfstate"
workspace_dir = "relative/path/to/workspaces"
}
}
Official arguments:
path(optional) — path to the tfstate file. Default isterraform.tfstaterelative to the root module.workspace_dir(optional) — directory that holds non-default CLI workspaces.
With the defaults, the default workspace uses terraform.tfstate in the working directory. Other CLI workspaces (terraform workspace new staging) use a per-workspace file under terraform.tfstate.d/<name>/. Setting workspace_dir relocates that non-default tree. Switching CLI workspaces does not change your .tf files; it changes which local snapshot the next plan reads.
Local locking is real, and it is limited
The local backend locks with system APIs (file locks on that machine and that filesystem). Two terraform apply processes in the same directory on the same host will not both write. That is better than no lock at all.
It does not protect a team:
- Ava's laptop and Ben's laptop each have their own
terraform.tfstate. Local locks never see each other. - A copy of the file on a USB stick or in email is a second writer with no shared lock.
- Network filesystems and some container mounts implement file locks poorly; HashiCorp does not present local locking as a collaboration feature.
terraform force-unlock is documented as unable to unlock a local state file from another process. If a local lock is stuck, it is a local-process problem, not a remote lock ID you can force from a teammate's machine.
Fine for learning, not for teams
HashiCorp's state page says storing state locally requires no extra configuration, but it limits collaboration and risks losing the workspace if the file is lost. The recommendation is HCP Terraform or a remote backend.
Use local state when:
- You are learning the write → init → plan → apply loop on one machine.
- The objects are disposable lab resources.
- Nobody else will apply the same configuration.
Do not use local state when:
- Two engineers can apply.
- A CI runner and a laptop can apply.
- Losing the laptop would lose the only copy of the bindings.
- The snapshot contains production secrets (it usually does).
Avoid storing state in Git or any other store that lacks Terraform locking and access control. Local state is plaintext JSON. Providers persist attributes there, including values that sensitive = true only redacts from CLI display.
.terraform/
*.tfplan
terraform.tfstate
terraform.tfstate.backup
terraform.tfstate.d/
crash.log
Changing the backend requires re-init
The moment you add backend "s3" (or cloud) and save, the working directory is out of date. HashiCorp: when you change a backend's configuration you must run terraform init again before you can plan, apply, or run state commands.
On Terraform 1.12, re-init after a backend change requires either -migrate-state or -reconfigure:
| Flag | What happens to existing local state |
|---|---|
terraform init -migrate-state | Copies the existing snapshot into the new backend (may prompt per workspace) |
terraform init -force-copy | Same copy, answers yes to prompts; implies -migrate-state |
terraform init -reconfigure | Ignores the old backend configuration; does not copy state |
Back up first. Official backend docs: before migrating, copy terraform.tfstate somewhere safe. After a successful migrate, the remote object is the live snapshot; delete or ignore the leftover local file so nobody applies it by accident.
Legacy CLI flags (do not treat as the modern design)
For local (or default-local) configurations only, older commands still accept -state, -state-out, and -backup. They override which filename Terraform reads and writes. HashiCorp preserves them for wrapper scripts that predated built-in remote state, and does not recommend them for new systems — including automation. They also disable the usual per-workspace filename selection if you set all three. They have no effect when a non-local backend is selected.
004 still expects you to recognize the names. The modern answer for shared state is a backend or cloud block, not a wrapper that copies files around -state.
flowchart TD
Start["Root module with no backend or cloud block"] --> Local["Local backend is selected"]
Local --> Files["terraform.tfstate + terraform.tfstate.backup"]
Local --> Lock["System API lock on this machine only"]
Files --> Solo["One laptop / lab: acceptable"]
Files --> Team["Two writers: split-brain"]
Team --> Remote["Add backend or cloud, then terraform init -migrate-state"]
Scenario: Jordan's first week
Jordan clones a tutorial, runs terraform init and terraform apply, and sees terraform.tfstate appear. That is 6a working as designed. On Friday Jordan emails the repo — including the state file — to a teammate. Both apply Monday morning. Each laptop locks its own copy, both writes succeed, and AWS now has objects that neither file fully describes. The exam answer is not "enable a better local lock." The exam answer is: local state is not a team backend; move to HCP Terraform or a remote backend and re-init.
004 traps for objective 6a
- No
backendblock still means the local backend, not "no backend." terraform.tfstateis the snapshot;.terraform/terraform.tfstateis backend metadata.- Local locking exists and is still insufficient for two laptops.
- Do not commit
terraform.tfstateorterraform.tfstate.backup. - Switching backends is an
initproblem (-migrate-state/-reconfigure), not anapplyproblem. -state/-state-out/-backupare legacy local-only flags, not the current team design.
A Terraform 1.12 root module has required_providers and resource blocks but no backend block and no cloud block. Where does Terraform store state?
Why does HashiCorp treat the local backend as a poor default for a team that applies the same configuration from two laptops?
You add a backend "s3" block to a configuration that previously used default local state. What must you do before plan or apply can run against the new store?