Input Variables
The idea in one sentence
Section titled “The idea in one sentence”An input variable is a named, typed parameter that lets the same Terraform configuration produce different Azure infrastructure depending on who calls it and with what values.
Declaring a variable block
Section titled “Declaring a variable block”A variable block has four parts worth knowing well: type constrains what shape of value is acceptable, default supplies a fallback when nobody sets a value, description documents intent for the next reader, and sensitive redacts the value from most CLI output.
variable "environment" { type = string description = "Deployment environment name, e.g. dev, staging, prod" default = "dev"}
variable "vm_count" { type = number description = "Number of virtual machines to launch" default = 2}
variable "enable_monitoring" { type = bool description = "Whether to enable Azure Monitor diagnostics on the virtual machines" default = true}
variable "allowed_ports" { type = list(number) description = "TCP ports allowed inbound on the network security group" default = [22, 443]}
variable "resource_tags" { type = map(string) description = "Tags applied to every resource in this configuration" default = { project = "payments" team = "platform" }}
variable "db_config" { type = object({ sku_name = string storage_mb = number backup_retention_days = number }) description = "Structured configuration for the PostgreSQL Flexible Server" default = { sku_name = "B_Standard_B1ms" storage_mb = 32768 backup_retention_days = 7 }}
variable "sql_admin_password" { type = string description = "Administrator login password for the Azure SQL server" sensitive = true}object({...}) is worth calling out: it is a typed, fixed-shape structure — every key you list is required (unless you give it a default with optional()), and Terraform rejects a caller-supplied value that has the wrong shape at plan time, well before any Azure API call happens.
The sensitive = true argument on sql_admin_password only affects display. Terraform masks the value in plan/apply output and in the CLI as (sensitive value). It does not encrypt or omit the value from the state file — the state-management module already covered this: state stores every attribute of every managed resource in plain text inside the JSON, including values that came from a sensitive variable. Treat sensitive = true as a display protection for terminals and CI logs, not as a substitute for a properly access-controlled remote backend.
Validating a variable with a validation block
Section titled “Validating a variable with a validation block”A default or a type constraint only checks the shape of a value, not whether it makes sense. A validation block adds a custom condition and a specific error_message, so a caller gets immediate, actionable feedback at plan time instead of a confusing error from deep inside the azurerm provider.
variable "location" { type = string description = "Azure region to deploy resources into, e.g. eastus, westeurope"
validation { condition = can(regex("^(eastus|eastus2|westeurope|southeastasia)$", var.location)) error_message = "location must be one of: eastus, eastus2, westeurope, southeastasia." }}
variable "environment" { type = string description = "Deployment environment name"
validation { condition = contains(["dev", "staging", "prod"], var.environment) error_message = "environment must be one of: dev, staging, prod." }}Without that first validation block, passing location = "mars" would sail through terraform plan and only fail once Terraform actually calls the Azure Resource Manager API to create a resource group in that region — a much slower feedback loop, and a much less specific error message than “location must be one of: eastus, eastus2, westeurope, southeastasia.” The can() wrapper turns a regex() failure into false instead of an outright error, which is exactly what a condition expression needs: it must always evaluate to true or false, never raise.
Setting variable values, and who wins
Section titled “Setting variable values, and who wins”Terraform lets you set a value for the same variable from several places at once, and it resolves conflicts with a strict, well-defined precedence. From highest priority (wins) to lowest:
-varand-var-fileon the command line, in the order given — the most explicit, most temporary way to set a value.*.auto.tfvars/*.auto.tfvars.jsonfiles, auto-loaded from the working directory in lexical (alphabetical) order of filename.terraform.tfvars/terraform.tfvars.json, auto-loaded if present.TF_VAR_<name>environment variables — useful for CI pipelines and for values you do not want sitting in a file at all.- The variable’s own
default— used only if nothing above supplied a value.
# Highest precedence: an explicit CLI flag for this one runterraform apply -var="vm_size=Standard_D8s_v3"
# Or point at a specific tfvars fileterraform apply -var-file="prod.tfvars"
# Environment variable equivalent of setting vm_sizeexport TF_VAR_vm_size="Standard_D2s_v3"terraform applyIf vm_size is set in terraform.tfvars, again in prod.auto.tfvars, and again via -var on the command line, the -var value wins — Terraform does not merge scalar values across sources, it picks the highest-priority one that exists. Collection types like maps and objects are the exception worth remembering later: some tooling merges nested values rather than fully overriding, but for the plain sources above, a defined value at a higher-priority source simply replaces a lower-priority one for that variable.
flowchart LR a["-var / -var-file (CLI flag)"] --> r["Resolved value of var.vm_size"] b["*.auto.tfvars (alphabetical)"] --> r c["terraform.tfvars"] --> r d["TF_VAR_vm_size (env var)"] --> r e["default in the variable block"] --> r r --> res["azurerm_linux_virtual_machine.web"]