Skip to main content

Input Variables

Exam guide§2.4

The terms page introduced variables as one of the config primitives; this page is the full treatment. So far you have hardcoded resource argument values. Input variables parameterize a configuration so you can standardize the code but still customize attributes at run time - the same .tf files run with different values without editing the source. Variables separate source code from value assignments.

Take a bucket whose name, location, and storage_class are hardcoded. Declare any of them as a variable and supply the value at run time instead:

resource "google_storage_bucket" "mybucket1" {
name = "my-project-name" #Required argument
location = "US"
storage_class = "standard"
}

Declaring an input variable

A variable is declared in a variable block. Best practice is to keep all declarations in a separate file named variables.tf. The label after the variable keyword names it.

variable "bucket_region" {
type = string
description = "Region for the bucket"
default = "US"
sensitive = true
}
GotchaTwo naming rules - and the block can be empty

A variable name must be unique within a module, and it cannot be a keyword. There are no required arguments, so a variable block can be empty - Terraform then deduces the type and default from usage.

Referencing a variable

Access a declared variable with the expression var.<variable_name>. The name in the variable block must match the reference in the resource block:

variable "bucket_storage_class" {
type = string
default = "REGIONAL"
}
 
resource "google_storage_bucket" "mybucket1" {
name = "unique_bucket_name"
location = "US"
storage_class = var.bucket_storage_class
}

Variable arguments

Four meta-arguments configure a variable: type, default, description, and sensitive.

type

type restricts the accepted value types. Terraform supports three primitive types:

NumbersPrimitive variable types
  1. bool - a binary value, true or false (no quotes).
  2. number - a numeric value.
  3. string - a sequence of Unicode characters.

default

default assigns a fallback value, used when no value is set by any other method. The default shows up in the execution plan when you run terraform plan:

Terminal
$ terraform plan
Terraform will perform the following actions:
# google_storage_bucket.mybucket1 will be created
+ resource "google_storage_bucket" "mybucket1" {
+ location = "US"
+ name = "unique_bucket_name"
+ project = (known after apply)
+ storage_class = "REGIONAL"
}
Plan: 1 to add, 0 to change, 0 to destroy.
terraform plan with a default: the storage_class value comes from the variable's default and appears in the execution plan

description

description documents the variable's purpose and expected value. Terraform displays it at run time whenever the variable has no assigned value and it prompts you on the CLI. Because the string often lands in generated documentation, write it from the user's perspective, not the maintainer's - use comments for maintainer notes.

sensitive

sensitive = true hides the value from the output of terraform plan and terraform apply, and from log files. Use it for database credentials, API tokens, and other secrets to eliminate accidental exposure.

variable "user_information" {
type = object({
name = string
address = string
})
sensitive = true
}
 
resource "some_resource" "foo" {
name = var.user_information.name
address = var.user_information.address
}
GotchaSensitivity is contagious

When a resource uses a variable marked sensitive, the attributes fed by it are also redacted in plan/apply output. In the example, foo's name and address are shown as (sensitive) because user_information is sensitive.

Assigning values at run time

Once a variable is declared, there are several ways to set its value. Each overrides the default.

NumbersWays to assign a value
  1. .tfvars files - version and switch between whole sets of variables (recommended).
  2. CLI -var option - good for quick examples and automation.
  3. Environment variables (TF_VAR_<name>) - useful in scripts and pipelines.
  4. CLI prompt - the fallback when a required variable was not set by any of the above.
# .tfvars file (Recommended method)
tf apply -var-file my-vars.tfvars
 
# CLI options
tf apply -var project_id="my-project"
 
# environment variables
TF_VAR_project_id="my-project" \
tf apply
 
# If using terraform.tfvars
tf apply

Definition files (.tfvars)

When there are too many values to pass on the command line, put them in a definitions file with a .tfvars or .tfvars.json extension. Contents use HCL syntax but hold only name assignments:

mybucket_storage_class = "REGIONAL"
bucket_region = "US"
GotchaWhich `.tfvars` files load automatically

Terraform auto-loads a definitions file only when it is named exactly terraform.tfvars, terraform.tfvars.json, *.auto.tfvars, or *.auto.tfvars.json. Any other name - including a .tf extension or a custom name like my-vars.tfvars - must be passed explicitly with -var-file on the command line.

Precedence

If the same variable is set by multiple methods, the -var (and -var-file) option wins over everything else. This is what lets you reuse a .tfvars file and still override individual values at deploy time.

higher precedence - overrides those below4 · CLI -var / -var-fileHighest precedence - wins over every other method.3 · .tfvars / .auto.tfvars filesOverrides the default and environment variables.2 · Environment variables (TF_VAR_name)Overrides the default; useful in scripts and pipelines.1 · default in the variable blockBase value, used only when nothing else sets it.
Variable precedence: each layer overrides the ones below it; a value set with -var / -var-file on the CLI takes the highest precedence
Gotcha`-var` always wins - and unset required variables prompt

-var / -var-file take the highest precedence over all other assignment methods, which is why they suit automation that sources values from the environment. If a required variable is not set by any method, Terraform prompts you on the CLI during the plan phase rather than failing.

Validating variable values

Add a validation sub-block inside the variable block to enforce a rule on the assigned value. It takes a condition (the rule) and an error_message shown when the condition is false. Here contains() checks the storage class is one of the accepted values:

variable "mybucket_storageclass" {
type = string
description = "Set the storage class to the bucket."
validation {
condition = contains(["STANDARD", "MULTI_REGIONAL", "REGIONAL"], var.storageclass)
error_message = "Allowed storage classes are STANDARD, MULTI_REGIONAL and REGIONAL."
}
}

Supplying a disallowed value (ZONAL) fails the plan with your error message:

Terminal
var.mybucket_storageclass
Set the storage class to the bucket.
Enter a value: ZONAL
Error: Invalid value for variable
on main.tf line 1:
1: variable "mybucket_storageclass" {
Allowed storage classes are STANDARD, MULTI_REGIONAL and REGIONAL.
This was checked by the validation rule at main.tf:4,3-13.
A disallowed value (ZONAL) fails the validation rule at plan time and prints the error_message

Best practices

Once you know how variables parameterize a configuration, a handful of conventions keep them maintainable: expose only what genuinely varies, feed values through a committed .tfvars file, name variables clearly (with units), and always describe them. These are the habits the exam expects from a well-authored module.

Parameterize only when necessary

Only parameterize values that vary for each instance or environment. Before exposing a variable, make sure you have a concrete use case for changing it - if there's only a small chance it might be needed, don't expose it. Every variable is surface area that future maintainers must understand.

GotchaAdding a variable is safe; removing one is a breaking change

Changing or adding a variable with a default value is backward-compatible - existing callers keep working. Removing a variable is not backward-compatible: anything that set it now fails. Bias toward the smallest set of variables you can, because you can add later far more easily than you can take away.

Provide values in a .tfvars file

For root modules, provide values by using a .tfvars variables file rather than passing them on the command line. A default variables file lives beside your configuration and is checked into source control, so applies are predictable and reproducible.

-- server/
-- main.tf
-- outputs.tf
-- terraform.tfvars <- values committed here
-- variables.tf
Do - values in terraform.tfvars
mybucket_storage_class = "REGIONAL"
bucket_region = "US"
Avoid - values on the command line
cd /server
terraform apply -var="mybucket_storage_class=REGIONAL"
terraform apply -var="bucket_region=US"
tf apply -var-file my-vars.txt
GotchaDon't alternate between var-files and command-line options

Command-line options are ephemeral and easy to forget, and they cannot be checked into source control. Mixing -var/-var-file flags with a default variables file makes it unclear where a value actually came from. Keep values in the .tfvars file.

Give variables descriptive names

Give variables descriptive names relevant to their usage or purpose. Two rules make names unambiguous:

  • Numeric values must carry their unit in the name - for example disk or RAM sizes. Google Cloud APIs don't have standard units, so a name like ram_size_gb tells the maintainer exactly what to pass.
  • Boolean variables get positive names to simplify conditional logic, for example enable_external_access (not disable_external_access).
Do - unit in the name
variable "ram_size_gb" {
type = number
description = "RAM size in GB."
}
Avoid - unit is ambiguous
variable "ram_size" {
type = number
description = "RAM size in GB."
}

Provide meaningful descriptions

Variables must have descriptions. Descriptions are automatically included in the generated documentation and give new developers the context to use a variable correctly. A vague description paired with a vague name (myregion, "Specify the region.") leaves the reader guessing which region and why.

Do - clear name and description
variable "bucket_region" {
type = string
default = "US"
description = "Specify the bucket region."
}
Avoid - vague name and description
variable "myregion" {
type = string
default = "US"
description = "Specify the region."
}