Variables, Custom Type Constraints & Validation Rules
Enforce strings, numbers, booleans, lists, and structural object constraints.
Use 'validation {}' blocks with regex and bounds checks to block invalid deployments.
Assign computed formulas and merged objects to 'locals {}' for readable code.
The Big Idea
Hardcoding values like CIDR blocks, instance sizes, and environment names directly inside your resource declarations makes your code brittle, impossible to reuse across environments, and prone to copy-paste drift.
Input Variables serve as the parameters of your infrastructure modules. Like function arguments in programming, they define what callers can customize while establishing strict type constraints, default fallbacks, and human-friendly documentation.
Modern Terraform allows you to build defensive infrastructure through Custom Validation Rules. Instead of waiting for a 15-minute AWS API call to fail because an AMI ID was invalid or an environment name had uppercase letters, you can enforce validation rules that fail instantaneously during the local planning phase.
Jargon Buster: Plain-English Translations
- Type Constraint: Specifying the data type a variable accepts (e.g., 'type = list(string)' or 'type = object({...})').
- Validation Block: A block inside a variable declaration containing a boolean 'condition' and an 'error_message' string.
- Precedence Order: The hierarchy Terraform uses to resolve variable values: CLI flags (-var) override .tfvars, which override environment variables (TF_VAR_*).
- Locals Block: Named values computed within a module that cannot be directly overridden by external callers.
Production Variable Validation & Computed Locals
# variables.tf: Defensive input variable with custom validation
variable "environment" {
type = string
description = "Deployment environment tier (dev, staging, or prod)"
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "The environment variable must be exactly one of: dev, staging, or prod."
}
}
variable "vpc_cidr" {
type = string
default = "10.0.0.0/16"
description = "IPv4 CIDR block for the VPC"
validation {
condition = can(cidrhost(var.vpc_cidr, 0)) && endswith(var.vpc_cidr, "/16")
error_message = "VPC CIDR must be a valid IPv4 address block with a /16 prefix."
}
}
# locals.tf: Compute derived values and standard tagging policies
locals {
is_production = var.environment == "prod"
# Standard tagging contract
standard_tags = {
Environment = var.environment
ManagedBy = "terraform"
BillingCode = local.is_production ? "CORP-PROD-01" : "CORP-DEV-NONPROD"
}
}
Beginner Traps & Gotchas
1. Confusing variable default values with required arguments
The Gotcha: Leaving out a default on a variable you intended to be optional, forcing callers to supply dummy values.
The Fix: If an input has a sensible fallback, provide a default. If an input is mandatory (like database credentials), omit default.
2. Exposing secret credentials via standard variables without masking
The Gotcha: Declaring 'variable "db_password" {}' without the 'sensitive = true' flag, printing secrets in terminal logs.
The Fix: Always add 'sensitive = true' to any variable holding tokens, passwords, or private keys.