Skip to content
Pipelines and Pizza 🍕
Go back

Terraform Variables and Outputs Explained

4 min read

In our last Terraform post, we covered the basics — providers, resources, and state. If you followed along, you probably noticed we hardcoded a lot of values. That works for learning, but it’s not how you build real infrastructure.

Today we’re going deep on variables and outputs — the features that transform your Terraform code from a one-off script into something reusable and production-ready.


Why Variables Matter

Here’s a resource group without variables:

resource "azurerm_resource_group" "main" {
  name     = "rg-myapp-prod-eastus"
  location = "eastus"
}

This works, but what happens when you need a dev environment? With variables:

resource "azurerm_resource_group" "main" {
  name     = "rg-${var.app_name}-${var.environment}-${var.location}"
  location = var.location
  tags     = var.tags
}

Now the same code works for any environment.


Variable Types

Primitive Types

variable "environment" {
  type    = string
  default = "dev"
}

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

variable "enable_monitoring" {
  type    = bool
  default = true
}

Collection Types

variable "availability_zones" {
  type    = list(string)
  default = ["1", "2", "3"]
}

variable "tags" {
  type = map(string)
  default = {
    owner      = "platform"
    managed_by = "terraform"
  }
}

Structural Types

variable "vm_config" {
  type = object({
    size          = string
    disk_size_gb  = number
    enable_backup = bool
  })
}

Setting Variable Values

Multiple ways to set values (in order of precedence):

  1. Command line: terraform apply -var="environment=staging"
  2. Variable file: Create terraform.tfvars
  3. Environment variables: TF_VAR_environment=dev
  4. Default values: From the variable definition

Sensitive Variables

variable "db_password" {
  description = "Database administrator password"
  type        = string
  sensitive   = true
}

With sensitive = true:

  • Values are redacted in plan/apply output
  • Values are still stored in state — protect your state file!

Locals: Computed Values

Locals are computed within your configuration:

locals {
  resource_prefix = "${var.app_name}-${var.environment}"
  is_production   = var.environment == "prod"

  common_tags = merge(var.tags, {
    application = var.app_name
    environment = var.environment
    managed_by  = "terraform"
  })

  vm_size = local.is_production ? "Standard_D4s_v3" : "Standard_B2s"
}

Use them in resources:

resource "azurerm_resource_group" "main" {
  name     = "rg-${local.resource_prefix}"
  location = var.location
  tags     = local.common_tags
}

Outputs

Outputs expose values from your Terraform configuration:

output "resource_group_name" {
  description = "Name of the created resource group"
  value       = azurerm_resource_group.main.name
}

output "resource_group_id" {
  description = "ID of the created resource group"
  value       = azurerm_resource_group.main.id
}

Query outputs:

terraform output
terraform output resource_group_name
terraform output -raw resource_group_name
terraform output -json

Validation Rules

variable "environment" {
  description = "Deployment environment"
  type        = string

  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Environment must be dev, staging, or prod."
  }
}

Real-World Patterns

Environment-Based Configuration

locals {
  env_config = {
    dev = {
      vm_size        = "Standard_B2s"
      instance_count = 1
    }
    prod = {
      vm_size        = "Standard_D4s_v3"
      instance_count = 3
    }
  }

  config = local.env_config[var.environment]
}

Feature Flags

variable "features" {
  type = object({
    enable_monitoring = bool
    enable_backup     = bool
  })
}

resource "azurerm_monitor_diagnostic_setting" "main" {
  count = var.features.enable_monitoring ? 1 : 0
  # ...
}

Common Pitfalls

  • Don’t Overuse Defaults — Critical values shouldn’t have defaults
  • Be Intentional with tfvars — Commit terraform.tfvars if it contains non-sensitive defaults (region, environment names). Add *.auto.tfvars or local override files to .gitignore if they contain credentials or machine-specific values
  • Document Everything — Always include descriptions

Final Thoughts

Variables, locals, and outputs are what transform Terraform from “a script that creates resources” into “a reusable system your team can build on.” Start simple — a few input variables and an output or two. As your infrastructure grows, reach for locals to keep things DRY and validation rules to catch mistakes early.

Next up in the Terraform series: modules — how to package and share reusable infrastructure patterns across your team and organization.

Happy automating!


References