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):
- Command line:
terraform apply -var="environment=staging" - Variable file: Create
terraform.tfvars - Environment variables:
TF_VAR_environment=dev - 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.tfvarsif it contains non-sensitive defaults (region, environment names). Add*.auto.tfvarsor local override files to.gitignoreif 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!