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:
- Values that are always deployment-specific (environment name, account ID)
- Secrets that should never have a default
- Configuration that fundamentally changes behavior
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}
# Usageresource "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 listvariable "availability_zones" { type = list(string) default = ["us-east-1a", "us-east-1b", "us-east-1c"]}
# Default mapvariable "resource_limits" { type = map(number) default = { cpu_units = 256 memory_mib = 512 max_replicas = 10 }}
# Default objectvariable "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 blocksvariable "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 defaultsvariable "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 productioninstance_type = "t3.large"min_capacity = 3max_capacity = 20deletion_protection = true# Dev: uses all defaultsterraform apply
# Production: overrides key valuesterraform apply -var-file="environments/production.tfvars"Module Defaults: The Interface Contract
When writing reusable modules, defaults define the expected usage pattern:
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 defaultsmodule "auth_service" { source = "./modules/ecs-service" name = "auth-service" # Only required variable image = "mycompany/auth-service:v1.2.3"}
# Customized for productionmodule "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.