Skip to content

Common variables

Drop-in variable blocks with type, description, sensible defaults, and validation rules. They work with Terraform ≥ 1.3 and OpenTofu ≥ 1.6.

Conventions used on this page

  • All variables have a description.
  • error_message is a complete sentence ending in a period.
  • Defaults are only set when there's a safe, common choice.
  • Optional values are typed string with default = null and nullable = true rather than empty strings, so missing values are explicit.

AWS

Region

variable "aws_region" {
  description = "AWS region to deploy into (e.g. us-east-1)."
  type        = string
  default     = "us-east-1"

  validation {
    condition     = can(regex("^(af|ap|ca|eu|me|sa|us|us-gov|cn)-[a-z]+-[0-9]+$", var.aws_region))
    error_message = "aws_region must look like a valid AWS region code (e.g. us-east-1, eu-west-2, ap-southeast-1)."
  }
}

Account ID

variable "aws_account_id" {
  type        = string
  description = "The AWS account ID (12-digit number)."

  validation {
    condition     = can(regex("^[0-9]{12}$", var.aws_account_id))
    error_message = "The aws_account_id must be exactly 12 digits (0-9), with no spaces, dashes, or other characters."
  }
}

Hard character limit

AWS account IDs are always exactly 12 numeric digits — anchoring with ^...$ rejects accidental whitespace or extra characters.

Why not number type

Keep them as string, not number — leading zeros are valid in account IDs and number would strip them.

List of Account IDs

variable "aws_account_ids" {
  type        = list(string)
  description = "A list of AWS account IDs (each a 12-digit number)."

  validation {
    condition     = alltrue([for id in var.aws_account_ids : can(regex("^[0-9]{12}$", id))])
    error_message = "Each entry in aws_account_ids must be exactly 12 digits (0-9)."
  }
}

Hard character limit

AWS account IDs are always exactly 12 numeric digits — anchoring with ^...$ rejects accidental whitespace or extra characters.

Why not number type

Keep them as string, not number — leading zeros are valid in account IDs and number would strip them.

Environment

variable "environment" {
  description = "Deployment environment. Used in resource names, tags, and conditional logic."
  type        = string

  validation {
    condition     = contains(["dev", "stg", "prod"], var.environment)
    error_message = "environment must be one of: dev, stg, prod."
  }
}

Project / application name

variable "project" {
  description = "Short project identifier used as a prefix for resource names. Lowercase letters, digits, and hyphens only; 2–24 characters."
  type        = string

  validation {
    condition     = can(regex("^[a-z][a-z0-9-]{1,23}$", var.project))
    error_message = "project must start with a lowercase letter and contain only lowercase letters, digits, or hyphens (2–24 chars)."
  }
}

Tags (with required keys)

Validates the map and enforces that specific keys are present — useful for governance / cost-allocation tags.

variable "tags" {
  description = "Tags applied to every resource. Must include Owner, Environment, and CostCenter."
  type        = map(string)
  default     = {}

  validation {
    condition = alltrue([
      for k in ["Owner", "Environment", "CostCenter"] : contains(keys(var.tags), k)
    ])
    error_message = "tags must include the keys: Owner, Environment, CostCenter."
  }

  validation {
    condition     = alltrue([for v in values(var.tags) : length(v) > 0 && length(v) <= 256])
    error_message = "Every tag value must be a non-empty string of at most 256 characters."
  }
}

CIDR block

variable "vpc_cidr" {
  description = "IPv4 CIDR block for the VPC. Must be a /16–/28 RFC 1918 range."
  type        = string
  default     = "10.0.0.0/16"

  validation {
    condition     = can(cidrnetmask(var.vpc_cidr))
    error_message = "vpc_cidr must be a valid IPv4 CIDR block (e.g. 10.0.0.0/16)."
  }

  validation {
    condition     = tonumber(split("/", var.vpc_cidr)[1]) >= 16 && tonumber(split("/", var.vpc_cidr)[1]) <= 28
    error_message = "vpc_cidr prefix length must be between /16 and /28."
  }
}

List of CIDRs (allowlist)

variable "allowed_cidrs" {
  description = "Source CIDR blocks allowed to reach the service. Use [\"0.0.0.0/0\"] only deliberately."
  type        = list(string)
  default     = []

  validation {
    condition     = alltrue([for c in var.allowed_cidrs : can(cidrnetmask(c))])
    error_message = "Every entry in allowed_cidrs must be a valid IPv4 CIDR block."
  }
}

Instance type

variable "instance_type" {
  description = "EC2 instance type, e.g. t3.micro or m6i.large."
  type        = string
  default     = "t3.micro"

  validation {
    condition     = can(regex("^[a-z][a-z0-9-]+\\.[a-z0-9]+$", var.instance_type))
    error_message = "instance_type must look like a valid EC2 instance type (family.size, e.g. t3.micro)."
  }
}

Domain name

variable "domain_name" {
  description = "Fully qualified domain name (e.g. api.example.com). Lowercase, no trailing dot."
  type        = string

  validation {
    condition     = can(regex("^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,}$", var.domain_name))
    error_message = "domain_name must be a lowercase FQDN such as api.example.com (no trailing dot)."
  }
}

Email address

variable "contact_email" {
  description = "Operational contact email used for alerts."
  type        = string

  validation {
    condition     = can(regex("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$", var.contact_email))
    error_message = "contact_email must be a valid email address."
  }
}

Optional string (nullable)

Prefer null over "" so "unset" is explicit:

variable "kms_key_arn" {
  description = "Optional KMS key ARN for encryption. When null, an AWS-managed key is used."
  type        = string
  default     = null
  nullable    = true

  validation {
    condition     = var.kms_key_arn == null || can(regex("^arn:aws[a-zA-Z-]*:kms:[a-z0-9-]+:[0-9]{12}:key/[a-f0-9-]+$", var.kms_key_arn))
    error_message = "kms_key_arn must be null or a valid KMS key ARN."
  }
}

Boolean feature flag

variable "enable_logging" {
  description = "Whether to enable verbose access logging. Disable in cost-sensitive environments."
  type        = bool
  default     = true
}

Numeric range

variable "desired_capacity" {
  description = "Desired number of instances in the autoscaling group (1–100)."
  type        = number
  default     = 2

  validation {
    condition     = var.desired_capacity >= 1 && var.desired_capacity <= 100 && floor(var.desired_capacity) == var.desired_capacity
    error_message = "desired_capacity must be an integer between 1 and 100."
  }
}

Object with optional attributes

Uses optional() from Terraform 1.3+ / OpenTofu so consumers only specify what they care about:

variable "logging" {
  description = "Logging configuration. Any field not specified falls back to defaults."
  type = object({
    enabled            = optional(bool, true)
    retention_in_days  = optional(number, 30)
    log_group_name     = optional(string)
  })
  default = {}

  validation {
    condition     = contains([1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, 3653], var.logging.retention_in_days)
    error_message = "logging.retention_in_days must be a CloudWatch-supported retention value."
  }
}

Map of objects

variable "subnets" {
  description = "Map of subnet name to its CIDR and AZ suffix (a/b/c)."
  type = map(object({
    cidr = string
    az   = string
  }))
  default = {}

  validation {
    condition     = alltrue([for s in values(var.subnets) : can(cidrnetmask(s.cidr))])
    error_message = "Every subnets[*].cidr must be a valid IPv4 CIDR block."
  }

  validation {
    condition     = alltrue([for s in values(var.subnets) : contains(["a", "b", "c", "d", "e", "f"], s.az)])
    error_message = "Every subnets[*].az must be one of: a, b, c, d, e, f."
  }
}

Secrets / sensitive values

Never commit secret values

Provide via TF_VAR_* env vars, a secrets manager, or a .auto.tfvars file that is .gitignore-d. The validation below only enforces a minimum length.

variable "db_password" {
  description = "Database admin password. Provide via TF_VAR_db_password or a secrets manager — do not commit."
  type        = string
  sensitive   = true

  validation {
    condition     = length(var.db_password) >= 16
    error_message = "db_password must be at least 16 characters."
  }
}

References