HCL backend blocks — partial config and init-time injection
A backend block is HCL that does not evaluate like the rest of the file. Learn the grammar that init understands.
A backend block is HCL that does not evaluate like the rest of the file. Learn the grammar that init understands.
§I — Frame
The last HCL-depth fire taught replace_triggered_by and terraform_data as address-sensitive lifecycle grammar. Today the grammar is quieter and earlier: the terraform { backend "s3" { ... } } block.
Ops shows why environments inject bucket and key at init. This lesson stays inside the language: which arguments are strings only, which may be omitted for partial configuration, how -backend-config merges, and why required_version sits in a sibling terraform block rather than inside the backend.
§II — The backend block is a closed dialect
terraform {
required_version = ">= 1.6, < 2.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
backend "s3" {
bucket = "acme-tfstate"
key = "network/dev.tfstate"
region = "us-east-1"
dynamodb_table = "acme-tf-locks"
encrypt = true
}
}
Arguments take literal values. No var., no local., no module., no function calls. Booleans are bare true/false. Strings are quoted. That is the entire dialect.
Partial configuration omits arguments (or leaves empty strings, as Lab 31 does) so init can supply them:
terraform {
backend "s3" {
encrypt = true
}
}
# backend.hcl (not imported by the graph; consumed by init)
bucket = "acme-tfstate"
key = "network/dev.tfstate"
region = "us-east-1"
dynamodb_table = "acme-tf-locks"
Merge rule: values in -backend-config files or key=value flags overlay the partial block. Arguments set in both places: the init overlay wins for that init. Arguments only in the HCL remain.
§III — Illegal shapes that look like normal HCL
variable "environment" {
type = string
}
locals {
state_key = "network/${var.environment}.tfstate"
}
terraform {
backend "s3" {
bucket = "acme-tfstate"
key = local.state_key
region = "us-east-1"
}
}
This is ordinary HCL elsewhere. Inside backend it is invalid. Lab 04 encodes that failure. Write the key in backend.hcl or pass -backend-config="key=network/dev.tfstate".
String templates with ${} are still expressions. They fail the same way. Format the path in the shell or in the generator that writes backend.hcl, not in the backend block.
§IV — Multiple overlays and reconfigure
terraform init \
-backend-config=backend-common.hcl \
-backend-config="key=network/dev.tfstate"
You may pass -backend-config more than once. Later flags override earlier keys. This is useful when a shared file holds bucket/region/dynamodb_table and the pipeline adds only key.
When the working directory already has backend state under .terraform/, switching keys requires an explicit posture:
terraform init -backend-config=backend-prod.hcl -reconfigure
-reconfigure discards the prior backend settings and adopts the new ones without migrating the object. Use -migrate-state when you intend to copy state from the old backend address to the new one. Name the intent in the pipeline; do not rely on a prompt.
§V — Documentation locals versus backend keys
Lab 31 keeps a local hint:
locals {
expected_backend_key = format("network/%s.tfstate", var.environment)
}
output "backend_key_hint" {
value = local.expected_backend_key
}
output "init_example" {
value = "terraform init -backend-config=backend.hcl"
}
That local is ordinary HCL. It evaluates at plan time. It does not configure the backend. Use it to fail tests when a human-supplied backend.hcl disagrees with the expected path, or to print operator guidance. Never assign it into backend.key.
§VI — Closing
Treat backend as a sealed map of literals with optional init overlays. Keep required_version and required_providers in the same terraform block family, outside the sealed map. When you want environment-specific keys, write HCL files for init, not expressions for the graph. Open any root that still interpolates inside backend and delete the interpolation today.
Related
- Paired Ops:
- Paired Cert:
- Prior HCL (replace_triggered_by):
- Lab 31:
- Language hub: