Skip to main content

Input Variables

Input variables let you parameterize your Terraform configurations, making them flexible and reusable across different environments.

Declaring Variables

Declare variables using variable blocks:
variable "<NAME>" {
  # Configuration options
}

Basic Example

variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t2.micro"
}

Variable Arguments

type
type constraint
Specifies what value types are accepted for the variable.
type = string
type = number
type = bool
type = list(string)
type = map(number)
default
any
Default value when no value is provided. Variables without defaults are required.
default = "t2.micro"
default = ["us-west-2a", "us-west-2b"]
default = null
description
string
Human-readable description of the variable’s purpose.
description = "The instance type for web servers"
sensitive
boolean
Marks the variable as containing sensitive data. Terraform will hide its value in output.
sensitive = true
nullable
boolean
Whether the variable can be null. Defaults to true.
nullable = false  # Variable must have a non-null value
validation
block
Custom validation rules for the variable value.
validation {
  condition     = length(var.name) > 3
  error_message = "Name must be longer than 3 characters."
}

Variable Types

Primitive Types

variable "name" {
  type = string
}

variable "count" {
  type = number
}

variable "enabled" {
  type = bool
}

Collection Types

variable "availability_zones" {
  type = list(string)
  default = [
    "us-west-2a",
    "us-west-2b",
    "us-west-2c"
  ]
}

variable "tags" {
  type = map(string)
  default = {
    Environment = "production"
    Team        = "platform"
  }
}

variable "unique_ids" {
  type = set(string)
}

Structural Types

variable "server_config" {
  type = object({
    instance_type = string
    ami_id        = string
    disk_size     = number
  })
  
  default = {
    instance_type = "t2.micro"
    ami_id        = "ami-12345678"
    disk_size     = 20
  }
}

variable "port_list" {
  type = tuple([string, number, bool])
}

Complex Types

variable "instances" {
  type = map(object({
    instance_type = string
    ami           = string
    tags          = map(string)
  }))
}

Any Type

variable "flexible" {
  type = any  # Accepts any type
}

Using Variables

Reference variables with var.<NAME>:
resource "aws_instance" "web" {
  ami           = var.ami_id
  instance_type = var.instance_type
  
  tags = var.tags
}

Assigning Values

Command Line

terraform apply -var="instance_type=t2.large"
terraform apply -var="tags={Environment=dev,Team=eng}"

Variable Files

Create terraform.tfvars:
instance_type = "t2.large"
region        = "us-west-2"

tags = {
  Environment = "production"
  ManagedBy   = "Terraform"
}
Or custom files:
terraform apply -var-file="production.tfvars"

Environment Variables

export TF_VAR_instance_type="t2.large"
export TF_VAR_region="us-west-2"

terraform apply

Variable Precedence

When multiple values are provided, Terraform uses this precedence (highest to lowest):
  1. Command-line -var flags
  2. *.auto.tfvars files (alphabetically)
  3. terraform.tfvars file
  4. Environment variables (TF_VAR_*)
  5. Default value in variable declaration

Variable Validation

Add custom validation rules:
variable "instance_type" {
  type        = string
  description = "EC2 instance type"
  
  validation {
    condition     = can(regex("^t[23]\\.", var.instance_type))
    error_message = "Instance type must be t2 or t3 series."
  }
}

Multiple Validations

variable "port" {
  type = number
  
  validation {
    condition     = var.port >= 1 && var.port <= 65535
    error_message = "Port must be between 1 and 65535."
  }
  
  validation {
    condition     = var.port != 22
    error_message = "Port 22 is reserved for SSH."
  }
}

Cross-Variable Validation

variable "name" {
  type = string
  
  validation {
    condition     = length(var.name) > 3
    error_message = "Name must be longer than 3 characters."
  }
}
From the source code (internal/configs/named_values.go:578-602):
func checkVariableValidationBlock(varName string, vv *CheckRule) hcl.Diagnostics {
    // The validation condition must include a reference to the variable itself
    for _, traversal := range vv.Condition.Variables() {
        ref, moreDiags := addrs.ParseRef(traversal)
        if !moreDiags.HasErrors() {
            if addr, ok := ref.Subject.(addrs.InputVariable); ok {
                if addr.Name == varName {
                    return nil
                }
            }
        }
    }
    
    return diags.Append(&hcl.Diagnostic{
        Severity: hcl.DiagError,
        Summary:  "Invalid variable validation condition",
        Detail:   fmt.Sprintf("The condition for variable %q must refer to var.%s...", varName, varName),
    })
}

Sensitive Variables

Mark variables containing secrets:
variable "database_password" {
  type      = string
  sensitive = true
}
When marked sensitive:
  • Value is hidden in terraform plan and apply output
  • Value is redacted in logs
  • Still stored in state file (encrypt state!)
resource "aws_db_instance" "database" {
  password = var.database_password  # Value hidden in output
}

Nullable Variables

Control whether null is allowed:
variable "optional_config" {
  type     = string
  nullable = true
  default  = null
}

variable "required_config" {
  type     = string
  nullable = false
  # Must provide a non-null value
}
From internal/configs/named_values.go:34-54:
type Variable struct {
    Name        string
    Description string
    Default     cty.Value
    Type        cty.Type
    
    Sensitive   bool
    Nullable    bool
    NullableSet bool
    
    // Const indicates that this variable can be used during early evaluation
    Const bool
}

Ephemeral Variables

Variables that exist only during operations:
variable "temp_token" {
  type      = string
  ephemeral = true
  sensitive = true
}
Ephemeral variables are not persisted in state files.

Deprecated Variables

Mark variables as deprecated:
variable "old_config" {
  type       = string
  deprecated = "Use new_config instead"
}

Variable Naming

Variable names must:
  • Start with a letter or underscore
  • Contain only letters, digits, underscores, and hyphens
  • Not conflict with reserved names
variable "instance_type" { }    # Valid
variable "instance-type" { }    # Valid
variable "_private" { }         # Valid
variable "2nd_instance" { }     # Invalid
Certain names are reserved and cannot be used as variable names:
  • source
  • version
  • providers
  • count
  • for_each
  • depends_on

Variable File Format

HCL Format

# terraform.tfvars
instance_type = "t2.large"
region        = "us-west-2"

availability_zones = [
  "us-west-2a",
  "us-west-2b",
]

tags = {
  Environment = "production"
  ManagedBy   = "Terraform"
}

JSON Format

{
  "instance_type": "t2.large",
  "region": "us-west-2",
  "availability_zones": [
    "us-west-2a",
    "us-west-2b"
  ],
  "tags": {
    "Environment": "production",
    "ManagedBy": "Terraform"
  }
}

Variable Examples from Source

From internal/configs/testdata/valid-files/variables.tf:
variable "foo" {
}

variable "bar" {
  default = "hello"
}

variable "baz" {
  type = list
}

variable "bar-baz" {
  default = []
  type    = list(string)
}

variable "sensitive_value" {
  default = {
    "a" = 1,
    "b" = 2
  }
  sensitive = true
}

variable "nullable" {
  type     = string
  nullable = true
  default  = "ok"
}

variable "nullable_default_null" {
  type     = map(string)
  nullable = true
  default  = null
}

Best Practices

Use Descriptions

Always provide clear descriptions for variables to help users understand their purpose.

Set Type Constraints

Specify explicit types to catch errors early and make intent clear.

Validate Input

Add validation rules to ensure variables meet requirements.

Use Sensitive Flag

Mark sensitive variables to prevent accidental exposure in logs.

Next Steps

Outputs

Export values from your configuration

Expressions

Use variables in expressions

Build docs developers (and LLMs) love