Purpose of this file
Configure where Terraform stores its state, using a shared Azure Storage location instead of a local file.
Terraform's state file is the single most important thing it manages besides your actual cloud resources — it's the record of what Terraform believes already exists. This file decides where that record lives.
The full file code sample
terraform/backend.tf# Backend location is supplied by the workflow or a local backend.hcl file.
# The bootstrap configuration has separate local state and is not destroyed here.
terraform {
backend "azurerm" {
use_azuread_auth = true
}
}
That's the entire file — and that's intentional. Most of the information a backend needs (which storage account, which container, which file name) is deliberately not written here.
Why not just use a local state file? the problem this solves
If a project doesn't configure a backend at all, Terraform quietly writes its state to a local terraform.tfstate file. That works fine for one person experimenting alone, but it breaks down the moment a second person, or an automated process like GitHub Actions, also needs to run Terraform — there's no single, current copy of the state everyone shares.
The azurerm backend type stores state as a blob inside an Azure Storage account instead. Whoever runs Terraform — a teammate locally, or the GitHub Actions workflow — reads and writes the exact same state file. Azure Storage also locks the state file while a run is in progress, so two runs can't corrupt it by writing at the same time.
This tells Terraform to authenticate to the storage account using your (or the workflow's) Azure identity, rather than a separate storage access key that would need to be generated, stored as a secret, and rotated on its own. One less secret to manage.
Why the file looks so empty partial configuration
A full azurerm backend configuration also needs a storage account name, a container name, and a file name (called the state key). None of those appear in backend.tf — they're supplied separately, every time Terraform is initialized. This pattern is called partial configuration.
terraform/backend.hcl.examplestorage_account_name = "COPY-TF_STATE_STORAGE_ACCOUNT-FROM-BOOTSTRAP"
container_name = "tfstate"
key = "metals.tfstate"
You copy this example file to backend.hcl, fill in the real storage account name, and run:
terraform -chdir=terraform init -backend-config backend.hcl
backend.hcl is listed in .gitignore and never committed — the storage account name isn't secret, but keeping the real file out of Git avoids every clone needing its own edit merged back in.
terraform -chdir=terraform init -input=false -lockfile=readonly \ -backend-config="storage_account_name=$TF_STATE_STORAGE_ACCOUNT" \ -backend-config="container_name=tfstate" \ -backend-config="key=metals.tfstate"
The workflow supplies the same three values as individual -backend-config flags instead of a file, pulling the storage account name from a GitHub Actions variable. Same backend, same partial-configuration pattern, just filled in a different way.
The storage account referenced here has to exist before this backend can use it — but this project's main Terraform configuration is what creates most Azure resources. That's exactly what bootstrap/main.tf is for: a separate, one-time setup that creates the state storage account first, with its own independent state.
Key terms for beginners
- State
- Terraform's record of exactly what it has created and how those pieces relate to each other. Terraform compares this record to your
.tffiles, and to real Azure, every time it runs. - Backend
- The place Terraform stores its state. The default is a local file; this project uses the
azurermbackend to store it remotely instead. - Partial configuration
- Writing only some backend settings in
backend.tf, and supplying the rest later atterraform inittime — useful when a value (like a storage account name) shouldn't be hardcoded into a committed file. - State locking
- A safety mechanism that prevents two Terraform runs from writing to the same state at the same time, avoiding a corrupted or conflicting record.