Input Variables
Input variables are the parameters of a Terraform configuration — the values you pull out of the body so the same code can stand up the Hatch ingest pipeline in dev, staging, and prod by changing inputs, not source. You declare a variable once with a variable "region" {} block, reference it everywhere as var.region, and supply its value through one of several channels that resolve in a fixed precedence order.
Until now the Hatch config carried "hatch-pipeline-dev" and "us-central1" as literals scattered through resource blocks. Parameterizing them is the first real step toward reuse: the .tf files become the logic, and a small set of inputs become the only thing that changes between environments. Getting the precedence order right — and knowing which variables to leave required — is what keeps a dev value from silently landing in prod.
Declaration and Reference
A variable block declares an input: it names it, gives it a type, and optionally a default and a description. Everywhere else in the configuration you write var.region, and Terraform substitutes the resolved value at plan time. The declaration is the only place the variable's shape is defined; every reference just reads it.
variable "region" { type = string description = "GCP region for the Hatch pipeline's regional resources" default = "us-central1" } resource "google_storage_bucket" "raw" { name = "hatch-events-raw" location = var.region # reads the resolved value at plan time }
That is the whole mechanism: one declaration, any number of var. references. The variable has no value of its own until Terraform resolves it from the channels below, and a reference to an undeclared variable is an error — Terraform will not silently treat var.regon as a new input.
Defaults and Required Variables
A variable with a default is optional: the default applies whenever nothing overrides it. A variable with no default is required, and Terraform errors before it plans if the value is unset. That distinction is a deliberate design lever, not a formality — leaving off a default forces the caller to make a choice the configuration cannot safely make for them.
For the Hatch pipeline, region can reasonably default to us-central1 because applying to the wrong region is recoverable and cheap. The GCP project id cannot: a wrong project means resources land in the wrong place entirely. So project gets no default, and an apply without it fails loudly instead of guessing.
The .tfvars File
A .tfvars file holds the actual values for one environment. terraform.tfvars and any *.auto.tfvars are loaded automatically; a file named anything else — prod.tfvars, dev.tfvars — must be passed explicitly with -var-file. The canonical pattern is one .tfvars per environment selecting the project, region, and bucket names, while the .tf source stays byte-for-byte identical across all of them.
# applied with: terraform apply -var-file=prod.tfvars project = "hatch-pipeline-prod" region = "us-central1" raw_bucket_name = "hatch-events-raw-prod"
The same configuration applied with -var-file=dev.tfvars stands up the dev pipeline; with prod.tfvars, the prod one. The diff between environments lives entirely in those small files, which is exactly where a reviewer wants to see it.
The Precedence Order
When the same variable is set in more than one place, Terraform resolves it in a strict order, highest priority first: -var and -var-file flags on the command line, then auto-loaded *.auto.tfvars files (in lexical order), then terraform.tfvars, then TF_VAR_ environment variables, and finally the declared default. Within multiple -var-file flags, the last one on the command line wins. Knowing this order is what lets you predict which value an apply will actually use.
The subtle trap is the environment-variable tier. A stale TF_VAR_region exported in your shell sits just above the declared default — so if no .tfvars file and no -var flag sets region, the apply silently uses that env var instead of the default, and the diff gives no hint why. Reserve TF_VAR_ for CI injection and secrets, never for values that belong in a reviewable file.
When a Variable Should Have No Default
Anything environment-specific and dangerous to guess wrong should be required. The GCP project id is the canonical example: give it a default of hatch-pipeline-dev and a prod apply with the project left unset will quietly create resources in the dev project instead of failing. A required variable converts that silent, expensive mistake into a loud pre-plan error. The same logic applies to prod bucket names and the environment name itself.
Descriptions and Self-Documentation
The description field is not decoration. It surfaces in interactive plan prompts when a required variable is unset, and it becomes the documentation for a module's inputs the moment the Hatch pipeline is packaged as one. On a shared module, the difference between a variable with a real description and one without is the difference between a usable input and a guessing game for the next engineer.
Input variable — a value the caller supplies, resolved through the precedence chain. Use it for anything that differs between dev, staging, and prod: project, region, bucket names. The whole point is one config, many environments.
Hardcoded literal — a constant baked into a resource block. Fine for a value that genuinely never varies, but a literal that should differ between environments is the bug a variable exists to prevent. If you would ever change it per environment, it is an input.
- Giving
projecta default likehatch-pipeline-devand then running an apply meant for prod — the default silently lands every resource in the dev project instead of failing before plan. - Leaving a stale
TF_VAR_regionexport in your shell while no.tfvarsfile or-varflag setsregion— the env var outranks the declared default, and the apply uses a value nobody can see in the diff. - Naming a file
prod.tfvarsand expecting it to auto-load — onlyterraform.tfvarsand*.auto.tfvarsload automatically;prod.tfvarsmust be passed with-var-fileor it is ignored. - Referencing
var.regonwith a typo — Terraform errors on the undeclared reference, which is at least loud, where a defaulted variable would have hidden the mistake. - Declaring a variable and never referencing it — Terraform stays silent, so the dead input lingers as noise that implies a knob that does nothing.
- Leave off the
defaultfor any value unsafe to guess — project id, environment name, prod bucket names — so an unset input fails before plan instead of applying a dev default into prod. - Give every variable both a
typeand adescription, since both surface in plan output and module documentation. - Use one
*.tfvarsfile per environment and select it with-var-file, keeping the.tfsource identical across dev, staging, and prod. - Reserve
TF_VAR_environment variables for secrets and CI injection, never for values that belong in a reviewable file, since env vars are invisible in the diff. - Keep environment-specific values in their
.tfvarsand out of resource blocks, so the source is the logic and the inputs are the only difference.
Knowledge Check
When the same variable is set in several places, which source wins?
- A
-varor-var-fileflag on the command line outranksterraform.tfvars, which outranks aTF_VAR_env var, which outranks the declared default - The declared
defaultalways wins because it is hard-coded inside the variable block itself terraform.tfvarswins because Terraform loads it last and last write wins- Whichever source supplies the longest string value takes precedence over every shorter assignment that happens to target the same variable name
Why is leaving project required (no default) safer than giving it a default of hatch-pipeline-dev?
- An unset value fails loudly before plan, instead of silently landing a prod apply in the dev project
- Required variables apply faster because Terraform skips the default lookup on every single evaluation of the variable
- A default on
projectwould block the variable from being read asvar.projectelsewhere - A required variable is automatically marked
sensitiveand kept out of plan output
Which .tfvars files does Terraform load automatically?
terraform.tfvarsand any*.auto.tfvars; others likeprod.tfvarsneed-var-file- Every
.tfvarsfile in the working directory, merged together in alphabetical order - Only the
.tfvarsfile whose name matches the current workspace - None — every
.tfvarsfile must be passed with an explicit-var-file
A teammate sets region in terraform.tfvars but the apply uses a different region. What is the most likely cause?
- A
-varflag or a*.auto.tfvarsfile setsregion, and both outrankterraform.tfvarsin the precedence order - A stale
TF_VAR_regionexport left in their shell session silently outranks the region value interraform.tfvars - The variable's declared
defaultalways overrides whatever the.tfvarsfile sets - The
regionargument cannot be driven by an input variable at all
You got correct