Terraform — Part 5 — Variables, Outputs, and Locals
Every example so far has hard-coded its values directly into resource blocks. This part covers the three ways Terraform avoids that: variables for input, locals for internal reuse, and outputs for surfacing results back out.
An input parameter, set from outside the configuration — a CLI flag, a file, an environment variable, or a default.
A named expression computed once inside the configuration, used to avoid repeating the same calculation.
A value surfaced after apply — printed to the CLI, readable by other tooling, or consumed by another module.
Introduction
Every resource block in this series so far has had its values written directly into it — a fixed region, a fixed filename, a fixed pet-name length. That works for a single throwaway example. It stops working the moment the same configuration needs to run against two environments, or the moment someone other than you needs to run it without editing the file first.
The variable Block
A variable block declares an input parameter. Its single label is the variable’s name:
variable "pet_length" {
type = number
description = "Number of words in the generated pet name."
default = 2
validation {
condition = var.pet_length >= 1 && var.pet_length <= 3
error_message = "pet_length must be between 1 and 3."
}
}
type constrains what kind of value is acceptable (string, number, bool, and structural types like list and map). description documents intent for anyone else reading the configuration. default is used whenever no other value is supplied. A nested validation block can reject a bad value outright with a clear error message, rather than letting it silently reach a resource and fail there instead.
Reference a variable anywhere in the configuration with var.<NAME>:
resource "random_pet" "server" {
length = var.pet_length
}
Setting Variable Values
A variable can get its value from several places, and Terraform applies a fixed precedence when more than one source sets the same variable, from lowest to highest priority:
| Source | Example |
|---|---|
1. default argument |
Set inside the variable block itself. |
| 2. Environment variable | export TF_VAR_pet_length=3 |
3. terraform.tfvars |
pet_length = 3 in a file named exactly terraform.tfvars, loaded automatically. |
4. terraform.tfvars.json |
Same idea, JSON syntax, also loaded automatically. |
5. *.auto.tfvars |
Any file ending in .auto.tfvars, loaded automatically in lexical order. |
6. -var / -var-file CLI flags |
terraform apply -var="pet_length=3" — highest priority, applied in the order given. |
.tfvars file per environment (dev.tfvars, prod.tfvars) and pass it explicitly with -var-file rather than relying on autoloaded defaults — it makes exactly which values applied to a given run unambiguous from the command that was run.
Marking a Variable Sensitive
Set sensitive = true on a variable holding a password, API key, or other secret, and Terraform redacts it from CLI output — plan and apply summaries show (sensitive value) instead of the real value:
variable "db_password" {
type = string
sensitive = true
}
sensitive = true only hides the value from CLI output. It still gets written into the state file in plain text, which is one of the reasons state needs to be treated as sensitive data in its own right — covered in Part 6.
Local Values
A locals block assigns a name to an expression, computed once, for reuse elsewhere in the same configuration. Unlike a variable, a local never takes outside input — it’s purely a way to avoid repeating a calculation:
locals {
greeting = "Server name: ${random_pet.server.id}"
}
Reference it with local.<NAME> — singular, unlike the plural locals block that defines it:
resource "local_file" "hello" {
filename = "${path.module}/hello.txt"
content = local.greeting
}
Locals are most useful for naming conventions and derived values used in more than one place — computing a resource name pattern once and reusing local.resource_name everywhere, instead of repeating the same string interpolation in five different resource blocks.
The output Block
An output block surfaces a value after apply finishes — the piece of the workflow that answers “what did Terraform actually just build, and what do I need from it?”
output "pet_name" {
description = "The generated pet name used as the server identifier."
value = random_pet.server.id
}
value is required and accepts any valid expression — a resource attribute, a local, a computed expression. description documents what the output represents. Setting sensitive = true on an output redacts it from the normal CLI summary the same way it does for a variable, though it’s still visible via terraform output -json or -raw when explicitly requested.
Terraform prints every root module output automatically at the end of a successful apply. To read one later without re-running apply:
# Print every output
terraform output
# Print just one output's raw value, unquoted
terraform output -raw pet_name
Putting It Together
Extending Part 4’s example with a variable, a local, and an output:
variable "pet_length" {
type = number
description = "Number of words in the generated pet name."
default = 2
}
resource "random_pet" "server" {
length = var.pet_length
}
locals {
greeting = "Server name: ${random_pet.server.id}"
}
resource "local_file" "hello" {
filename = "${path.module}/hello.txt"
content = local.greeting
}
output "pet_name" {
description = "The generated pet name used as the server identifier."
value = random_pet.server.id
}
Nothing about the resources themselves changed. What changed is that the pet name’s length is now a parameter someone can override without editing the file, the greeting text is computed once instead of repeated, and the generated name is visible immediately after apply instead of requiring a manual look inside hello.txt.
Quick Reference Summary
| Term | Meaning |
|---|---|
variable |
Declares an input parameter, referenced with var.NAME. |
locals |
Declares internally computed values, referenced with local.NAME. |
output |
Surfaces a value after apply, readable via terraform output. |
TF_VAR_name |
Environment variable convention for setting a variable’s value. |
.tfvars |
File format for setting multiple variable values at once, typically one per environment. |
sensitive |
Argument on variables and outputs that redacts the value from normal CLI output (not from state). |
Final Thoughts
Variables, locals, and outputs don’t add any new infrastructure capability on their own — they’re entirely about making configuration reusable and legible instead of a pile of hard-coded values. That distinction matters more as configurations grow, which is exactly the direction the rest of this series heads.
Terraform — Part 6 — Terraform State Explained. We cover what the state file actually contains, why it exists, and why hand-editing it is almost always the wrong move.