By the end you'll know
- How to declare input variables, with types, descriptions and defaults
- Four ways to set a variable, and which one wins
- How to reject bad values with a validation rule
- What
sensitivehides, and what it doesn't - How to report useful values with outputs
Where you are
At the end of Lesson 4, your web server works, but its settings are written straight into web.tf: port 8080, image version 1.28, and the page's text. To run it on another port, or with a newer nginx, you'd have to edit the code. And if a colleague wanted the same setup with their own settings, they'd need their own copy of the code.
This lesson pulls those settings out into input variables, so the same code works with different values, and adds outputs, so Terraform tells you what you need to know when it's done. Lesson 4 ended with terraform destroy, so you'll build the web server again along the way.
Input variables
A form has the same questions for everyone, with blanks for the answers, and some blanks come filled in with the usual answer. Input variables are the blanks in your Terraform code.
Step 1. Create a file called variables.tf in your terraform-practice folder:
variable "external_port" {
description = "Port on your computer for the web server"
type = number
default = 8080
}
variable "nginx_version" {
description = "Which nginx image version to run"
type = string
default = "1.28"
}
variable "page_text" {
description = "Text to show on the web page"
type = string
}
Each variable block declares one blank:
descriptionsays what it's for. Terraform shows it when it asks you for a value, and it's the first thing the next person reads.typesays what kind of value it accepts:stringfor text,number, orboolfor true or false. There are alsolistandmaptypes for several values, which Lesson 6 uses.defaultis the pre-filled answer, used when nobody gives one.page_texthas no default, so it's required: Terraform won't run without it.
Step 2. Now use them. In web.tf, change three lines:
name = "nginx:${var.nginx_version}"
in docker_image "nginx",
content = "<h1>${var.page_text}</h1>"
in local_file "page", and
external = var.external_port
in the container's ports block. var.name is how you use a variable. Inside text, wrap it in ${ } to fill it in, the same idea as a Python f-string: "nginx:${var.nginx_version}" becomes "nginx:1.28".
Setting values
Run terraform plan. page_text has no default, so Terraform asks:
var.page_text
Text to show on the web page
Enter a value:
That's the description you wrote. Type Hello from a variable and press Enter, and the plan carries on. Being asked every time gets old quickly, though, so there are three other ways to give a value.
A tfvars file. Saved answers for the form. Create a file called terraform.tfvars:
page_text = "Hello from a tfvars file"
external_port = 8081
Terraform reads a file with exactly that name automatically. Run terraform apply and type yes: no question this time, and the web server comes back, now on port 8081. Open http://localhost:8081 to see your text.
On the command line, for a one-off:
terraform plan -var external_port=9000
The plan shows the port changing from 8081 to 9000. You're only planning, so nothing changes.
From the environment, like the settings in Python Lesson 9. Any environment variable named TF_VAR_ followed by a variable's name sets that variable:
export TF_VAR_page_text="Hello from the environment"
When the same variable is set in more than one way, the most specific wins: -var on the command line beats terraform.tfvars, which beats TF_VAR_ environment variables, which beat the default. So right now, with terraform.tfvars setting page_text, your export has no effect. Run unset TF_VAR_page_text to remove it, so it doesn't surprise you later.
Rejecting bad values
A form that asks for a date of birth can refuse "next Tuesday". Terraform can refuse bad values too, before anything is built. Ports below 1024 need special permissions on most computers, so add a validation rule inside the external_port variable, below default:
validation {
condition = var.external_port >= 1024 && var.external_port <= 65535
error_message = "Choose a port from 1024 to 65535."
}
condition must be true for the value to be accepted. && means "and", so the port must be at least 1024 and at most 65535, the highest port there is. If it isn't, Terraform stops and shows your error_message. The break-it section tries it.
Outputs
A receipt tells you what you got after you've paid. Outputs are Terraform's receipt: values it reports after an apply, like the address of the thing it just built.
Step 3. Create a file called outputs.tf:
output "url" {
description = "Where to see the web page"
value = "http://localhost:${var.external_port}"
}
Run terraform apply and type yes. Nothing in your infrastructure changes, but the end of the output now says:
Outputs:
url = "http://localhost:8081"
You can ask for outputs any time, without applying:
terraform output
terraform output -raw url
The first lists every output. The second prints just the value, with no quotes, which is handy in scripts: open $(terraform output -raw url) opens the page in your browser, because $( ) runs a command and uses what it prints.
Outputs also matter beyond your own screen. In bigger projects, one part's outputs become another part's inputs, which Lesson 9 shows with modules.
Sensitive values
Some values are secrets: a password, a token, a private key. Marking a variable sensitive stops Terraform printing it. Try it with a pretend secret. Add this to variables.tf:
variable "api_token" {
description = "A pretend secret, for practice"
type = string
default = "pretend-token-123"
sensitive = true
}
and this to main.tf, to use it:
resource "terraform_data" "token_demo" {
input = var.api_token
}
Run terraform plan. Where the value would be, the plan says:
+ input = (sensitive value)
Your screen, your logs and anyone looking over your shoulder are protected. But now run terraform apply, type yes, and search the state file:
grep pretend-token-123 terraform.tfstate
grep searches a file for some text, and prints each line that contains it. It finds the token, in plain text. That's Lesson 3's warning made real: sensitive hides values from output, not from state. Treat the state as the secret it is. Then delete the api_token variable and the token_demo resource, and terraform apply once more to remove it.
Checkpoint: your configuration so far
Your folder now has main.tf, versions.tf and web.tf from before, plus three new files. variables.tf:
variable "external_port" {
description = "Port on your computer for the web server"
type = number
default = 8080
validation {
condition = var.external_port >= 1024 && var.external_port <= 65535
error_message = "Choose a port from 1024 to 65535."
}
}
variable "nginx_version" {
description = "Which nginx image version to run"
type = string
default = "1.28"
}
variable "page_text" {
description = "Text to show on the web page"
type = string
}
outputs.tf, with the url output from step 3, and terraform.tfvars:
page_text = "Hello from a tfvars file"
external_port = 8081
Run terraform plan: No changes.
One habit worth starting now: a real terraform.tfvars often holds secrets, so it usually stays out of Git, with *.tfvars in .gitignore. Projects commit a terraform.tfvars.example instead, with made-up values, to show what needs setting.
In my real setup
CivicGrid's Terraform has about a dozen variables. The everyday ones have sensible defaults, like the region, nyc3, and the size and number of cluster nodes, so changing the cluster size is a one-line change. The secrets, like the DigitalOcean and Cloudflare tokens and the database connection string, have no defaults and are marked sensitive. Their values aren't in any file at all: they're set in HCP Terraform, where my runs happen, which Lesson 8 covers. The repository only has a terraform.tfvars.example, with placeholders, to show what needs setting.
Break it on purpose
Try each one. None of them changes anything, because they all fail before the plan is made.
A value the rule refuses.
terraform plan -var external_port=80
Error: Invalid value for variable
on variables.tf line 1:
1: variable "external_port" {
├────────────────
│ var.external_port is 80
Choose a port from 1024 to 65535.
Your rule caught it, and your message explains it. Without the rule, Terraform would have gone ahead, and the failure would have come later, from Docker, with a far less helpful message.
A value of the wrong type.
terraform plan -var external_port=abc
Error: Invalid value for input variable
followed by an explanation that ends with a number is required. You declared type = number, so Terraform refuses text before even checking your validation rule. Types are the first check; validation is the second.
A required value with nobody to ask. Rename terraform.tfvars to terraform.tfvars.off, so Terraform doesn't read it, and plan with -input=false, which tells Terraform not to ask questions, the way automated runs work:
mv terraform.tfvars terraform.tfvars.off
terraform plan -input=false
Error: No value for required variable
followed by the file and line of page_text, and a reminder to use -var or a variable file. With nobody there to type an answer, a variable with no default and no value is a dead end. Put the file back with mv terraform.tfvars.off terraform.tfvars.
Where you are now
Your configuration takes its settings from outside the code, checks them, protects secrets from your screen, and reports what you need afterwards. But it still makes one of everything: one container, one page. Lesson 6 makes several from one block, with count and for_each, and creates things only when you want them, with conditionals.
Check yourself
What does a variable's default do?
external_port is set in terraform.tfvars and with -var. Which value is used?
With the validation rule in place, what happens with external_port = 80?
A variable is marked sensitive. Where can its value still be seen?
What is an output for?
terraform plan -input=false fails with No value for required variable. Why?
Key terms
- Input variable: a value your configuration takes from outside, declared with a
variableblock and used asvar.name. - Default: a variable's value when nobody sets it. A variable with no default is required.
- terraform.tfvars: a file of variable values that Terraform reads automatically.
- TF_VAR_: the start of an environment variable's name that sets a Terraform variable.
- Validation: a rule that rejects bad values before anything is built.
- sensitive: hides a value from plans and output. It's still in state.
- Output: a value Terraform reports after an apply, read any time with
terraform output. - Interpolation: filling a value into text with
${ }, like"nginx:${var.nginx_version}".
Go deeper
- Input variables (opens in a new tab) in the Terraform documentation, including every way to set them.
- Output values (opens in a new tab).