Variables & Outputs
Inputs, outputs, locals, and how modules speak.
Variables & Outputs
Variables let your config accept inputs from the outside. Outputs let it expose values to the outside (other configs, CI, you on the CLI).
Input variables
variable "region" {
type = string
default = "us-east-1"
description = "AWS region to deploy to."
}
variable "instance_count" {
type = number
default = 1
}
variable "tags" {
type = map(string)
default = {
Environment = "dev"
Owner = "platform"
}
}
Then reference with var.<name>:
provider "aws" {
region = var.region
}
resource "aws_instance" "web" {
count = var.instance_count
tags = var.tags
}
Setting variable values
Five ways, in increasing override precedence:
- The variable's own
defaultvalue. - A
terraform.tfvars(or*.auto.tfvars) file. - A specific
-var-file=...on the command line. -var foo=baron the command line.- Environment variable:
TF_VAR_foo=bar.
The most common pattern: default for the local-dev value, terraform.tfvars (gitignored) for per-developer overrides, environment variables in CI.
Variable types
Five primitive + three structural types:
| Type | Example value |
|---|---|
string |
"us-east-1" |
number |
42 |
bool |
true |
list(T) |
["a", "b"] |
set(T) |
toset(["a", "b"]) |
map(T) |
{ "a" = 1, "b" = 2 } |
object({ ... }) |
{ name = "x", age = 30 } |
tuple([...]) |
["x", 30, true] |
object is for fixed-shape structures (preferred over map when the keys are known up front). tuple is for fixed-length, mixed-type lists.
Validation
A validation block guards against bad inputs at plan time:
variable "instance_type" {
type = string
validation {
condition = contains(["t3.micro", "t3.small", "t3.medium"], var.instance_type)
error_message = "instance_type must be one of: t3.micro, t3.small, t3.medium."
}
}
Catch the misconfiguration before any resource gets touched.
Sensitive variables
Mark variables that hold secrets:
variable "db_password" {
type = string
sensitive = true
}
sensitive = true keeps the value out of terraform plan / terraform apply output and out of terraform output. The value still ends up in state — protect that with a backend that has restricted access.
Outputs
Outputs expose values from a config:
output "bucket_arn" {
value = aws_s3_bucket.logs.arn
description = "ARN of the logs bucket."
}
output "instance_ips" {
value = aws_instance.web[*].public_ip
}
output "db_password" {
value = aws_db_instance.main.password
sensitive = true
}
After apply:
terraform output # all outputs
terraform output bucket_arn # one specific output
terraform output -json # machine-readable
Outputs are how modules talk
When you write a Terraform module (next lessons), outputs are its return values. The parent that calls the module reads module.<name>.<output_name>:
module "vpc" {
source = "./modules/vpc"
cidr = "10.0.0.0/16"
}
resource "aws_instance" "web" {
subnet_id = module.vpc.public_subnet_ids[0] # reads the module's output
}
Locals
When you compute the same expression in two places, factor it into a locals block:
locals {
common_tags = {
Environment = var.environment
Project = "myapp"
ManagedBy = "terraform"
}
}
resource "aws_s3_bucket" "logs" {
tags = merge(local.common_tags, { Purpose = "logs" })
}
resource "aws_s3_bucket" "uploads" {
tags = merge(local.common_tags, { Purpose = "uploads" })
}
local.<name> reads them. Locals are evaluated once per plan and never overridable from the outside — they're for internal computation, unlike variables.