Terraform Variables: Input, Locals, and Outputs

Published on


Terraform variables decouple static definitions from infrastructure code, allowing modules to remain reusable and environment-agnostic across Dev, Staging, and Production.


1. Variable Types at a Glance

ConstructPurposeScope / Overridable
Input (variable)Parameterize execution; inject parametersOverridable via CLI, files, and env vars
Locals (locals)Internal computed expressions and constantsModule-internal only (fixed during run)
Output (output)Expose resource attributes and runtime IDsCLI readout or upstream module reference

2. Input Variables (variable)

Basic Definition

# variables.tf
variable "environment" {
  type        = string
  description = "Target deployment environment"
  default     = "dev"
}

variable "region" {
  type        = string
  description = "Target AWS Region"
  default     = "us-east-1"
}

variable "app_prefix" {
  type        = string
  description = "Unique resource prefix"
  default     = "ttwp"
}

Supported Type Constraints

  • Primitives: string, number, bool
  • Collections / Structural: list(<TYPE>), set(<TYPE>), map(<TYPE>), object({...}), tuple([...])
  • Dynamic / Special: any (inferred at runtime), null (omits default)

3. Local Values (locals)

Use locals to compute standardized naming conventions, merge common tags, or avoid duplicate expression logic.

# locals.tf
locals {
  # Composite resource names
  bucket_name = "${var.app_prefix}-bucket-${var.environment}-${var.region}"
  vpc_name    = "${var.environment}-vpc"

  # Standard organizational tags
  common_tags = {
    Environment = var.environment
    ManagedBy   = "Terraform"
  }
}
# main.tf
resource "aws_s3_bucket" "storage" {
  bucket = local.bucket_name

  tags = merge(local.common_tags, {
    Name = local.bucket_name
  })
}

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"

  tags = merge(local.common_tags, {
    Name = local.vpc_name
  })
}

locals cannot be overwritten from the outside (CLI or .tfvars). Keep them reserved for pure transformation logic.


4. Outputs (output)

Export provisioned IDs, connection strings, or generated values.

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

output "instance_id" {
  description = "EC2 Instance ID"
  value       = aws_instance.web.id
}

Retrieval Commands

# Query all exported outputs after apply
terraform output

# Extract single value in raw string format
terraform output -raw vpc_id

# Query as JSON
terraform output -json

Computed outputs (IDs, ARNs, DNS names) resolve during terraform apply and display as <known after apply> during the plan stage.


5. Variable Precedence Order

When the same variable is supplied via multiple vectors, Terraform resolves priority from lowest to highest:

  1. default inside the variable block
  2. Environment variables: TF_VAR_<variable_name>
  3. terraform.tfvars
  4. terraform.tfvars.json
  5. *.auto.tfvars or *.auto.tfvars.json (loaded alphabetically)
  6. CLI flags: -var or -var-file

Resolution Example

# 1. Default fallback: "dev"

# 2. Overridden via shell env:
export TF_VAR_environment="stage"

# 3. Overridden via tfvars file:
echo 'environment = "pre-prod"' > terraform.tfvars

# 4. Overridden via CLI runtime flag (highest priority):
terraform plan -var="environment=prod"

6. Common Gotchas

  • Interpolation Syntax: Direct concatenation like var.env-bucket will fail. Wrap it in curly braces: "${var.env}-bucket". Direct variable assignment needs no wrapping: var.env.
  • Resource Block Typos: In the AWS provider, EC2 is declared as resource "aws_instance" "name", not aws_ec2 or aws_ec2_instance.
  • Plaintext Secret Exposure: Avoid setting secret credentials via export TF_VAR_pass="..." or -var="pass=..." in shared CI runners, as values persist in ~/.bash_history and process inspection trees (ps aux).

Thank you for reading. This article is part of the PaperAstro starter template.