Cloud  /  Terraform

IaC Terraform 50 guides · updated 2026

Infrastructure as code done right — providers, state, reusable modules, and the workflow patterns that keep multi-cloud deployments sane in 2026.

Terraform Default Values

A variable’s default value is the fallback used when the caller doesn’t provide a value. Good defaults make configurations work out-of-the-box for the common case while still allowing customization — the principle of least surprise applied to infrastructure.


Setting Default Values

variable "region" {
type = string
default = "us-east-1" # Used when no value is provided
}
variable "instance_type" {
type = string
default = "t3.micro"
}
variable "enable_monitoring" {
type = bool
default = true
}
variable "tags" {
type = map(string)
default = {} # Empty map — callers can add tags without breaking anything
}

Variables Without Defaults (Required)

Omitting default makes a variable required — Terraform will fail if no value is provided:

variable "environment" {
description = "Required: deployment environment (dev, staging, production)"
type = string
# No default — caller MUST provide this
}
variable "database_password" {
description = "Required: RDS master password"
type = string
sensitive = true
# No default — never hardcode a default password!
}

Use required variables for:


Null Default (Explicitly Optional)

variable "existing_security_group_id" {
description = "Optional: attach an existing SG instead of creating a new one"
type = string
default = null # Caller omits this to use the created SG
}
# Usage
resource "aws_instance" "app" {
vpc_security_group_ids = var.existing_security_group_id != null ? [
var.existing_security_group_id
] : [
aws_security_group.app.id
]
}

Complex Defaults

# Default list
variable "availability_zones" {
type = list(string)
default = ["us-east-1a", "us-east-1b", "us-east-1c"]
}
# Default map
variable "resource_limits" {
type = map(number)
default = {
cpu_units = 256
memory_mib = 512
max_replicas = 10
}
}
# Default object
variable "logging_config" {
type = object({
enabled = bool
retention_days = number
log_group_prefix = string
})
default = {
enabled = true
retention_days = 30
log_group_prefix = "/app"
}
}
# List of objects — common for dynamic blocks
variable "environment_variables" {
type = list(object({
name = string
value = string
}))
default = [] # Empty list — no env vars by default
}

Environment-Specific Defaults via tfvars

The pattern that avoids the “one size fits all” trap — default to cheap/safe, override for production:

# variables.tf — safe/cheap defaults
variable "instance_type" {
type = string
default = "t3.micro" # Works for dev/testing without any configuration
}
variable "min_capacity" {
type = number
default = 1
}
variable "max_capacity" {
type = number
default = 3
}
variable "deletion_protection" {
type = bool
default = false # Easy to delete in dev
}
# environments/production.tfvars — override defaults for production
instance_type = "t3.large"
min_capacity = 3
max_capacity = 20
deletion_protection = true
Terminal window
# Dev: uses all defaults
terraform apply
# Production: overrides key values
terraform apply -var-file="environments/production.tfvars"

Module Defaults: The Interface Contract

When writing reusable modules, defaults define the expected usage pattern:

modules/ecs-service/variables.tf
variable "cpu" {
description = "CPU units for the ECS task (256 = 0.25 vCPU)"
type = number
default = 256 # Quarter vCPU — minimal for most services
}
variable "memory" {
description = "Memory in MiB for the ECS task"
type = number
default = 512
}
variable "desired_count" {
description = "Initial number of task instances"
type = number
default = 1
}
variable "health_check_path" {
description = "Path for ALB health checks"
type = string
default = "/health" # Standard across all services
}
variable "enable_autoscaling" {
type = bool
default = false # Off by default, opt-in per service
}
variable "log_retention_days" {
type = number
default = 14
}

A caller can use the module with minimal configuration:

# Minimal — uses all module defaults
module "auth_service" {
source = "./modules/ecs-service"
name = "auth-service" # Only required variable
image = "mycompany/auth-service:v1.2.3"
}
# Customized for production
module "payment_service" {
source = "./modules/ecs-service"
name = "payment-service"
image = "mycompany/payment-service:v2.0.1"
cpu = 1024
memory = 2048
desired_count = 3
enable_autoscaling = true
}

Dynamic Defaults with Locals

When a default needs to be computed from other variables, use locals:

variable "environment" {
type = string
}
variable "log_retention_days" {
type = number
default = null # null = "compute from environment"
}
locals {
# Use provided value OR compute a sensible default based on environment
log_retention = var.log_retention_days != null ? var.log_retention_days : (
var.environment == "production" ? 90 :
var.environment == "staging" ? 30 :
7 # dev
)
}
resource "aws_cloudwatch_log_group" "app" {
name = "/app/${var.environment}"
retention_in_days = local.log_retention
}

This pattern gives the caller full control while providing environment-appropriate defaults when they don’t specify.