Terraform Variables, Outputs and Locals Explained with Examples

· 2 min read · Terraform & OpenTofu Tutorials

Input variables, output values and local values #

A Terraform or OpenTofu configuration becomes reusable when it stops having fixed values inside the resources. Three blocks of HCL take care of this:

  • variable: an input of the configuration or of a module.
  • output: a value that the configuration returns, shown after apply and available to other configurations.
  • locals: a named expression calculated inside the configuration, to avoid repeating it.

Input variables #

variables.tf
variable "environment" {
  type        = string
  description = "Environment name"
  default     = "dev"

  validation {
    condition     = contains(["dev", "pre", "pro"], var.environment)
    error_message = "The environment must be dev, pre or pro."
  }
}

variable "instance_count" {
  type    = number
  default = 1
}

variable "tags" {
  type    = map(string)
  default = {}
}

variable "db_password" {
  type      = string
  sensitive = true
}

Use the value with var.<name>:

main.tf
resource "aws_instance" "web" {
  count         = var.instance_count
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"
  tags          = merge(var.tags, { Environment = var.environment })
}

Types can be string, number, bool, list(...), set(...), map(...), object({...}) and tuple([...]). A variable without default is required.

Setting the values #

Terraform looks for values in this order, the last one wins:

  1. The default of the variable.
  2. Environment variables named TF_VAR_<name>.
  3. terraform.tfvars and terraform.tfvars.json.
  4. Files *.auto.tfvars and *.auto.tfvars.json, in alphabetical order.
  5. -var-file and -var on the command line.
pro.tfvars
environment    = "pro"
instance_count = 3
tags = {
  Project = "demo"
}
$ tofu plan -var-file=pro.tfvars
$ TF_VAR_db_password='S3cret' tofu apply

Local values #

Use locals for expressions that you repeat or to give a name to a calculation:

locals.tf
locals {
  name_prefix = "ditwl-${var.environment}"

  common_tags = merge(var.tags, {
    Environment = var.environment
    ManagedBy   = "opentofu"
  })
}

resource "aws_s3_bucket" "logs" {
  bucket = "${local.name_prefix}-logs"
  tags   = local.common_tags
}

Locals are not inputs: users of your module cannot change them. A good rule is to use variables for what the caller decides and locals for what is derived.

Output values #

outputs.tf
output "vpc_id" {
  description = "ID of the VPC"
  value       = aws_vpc.main.id
}

output "db_endpoint" {
  value     = aws_db_instance.main.endpoint
  sensitive = true
}

After apply the values are printed, and you can read them any time:

$ tofu output vpc_id
$ tofu output -json

In a module, the outputs are the only way to expose values to the caller: module.network.vpc_id. To read the outputs of another configuration use terraform_remote_state.

Variable validation and preconditions #

validation blocks check a variable before the plan. For checks that involve resources or data sources, use precondition and postcondition inside lifecycle (see lifecycle).

File organization #

By convention, a configuration has main.tf, variables.tf, outputs.tf, providers.tf and versions.tf. Terraform loads every .tf file in the directory, so the names are only a convention. See project structure.

#Terraform #OpenTofu #Hcl