Terraform Input Variables

Terraform has become a staple for Infrastructure as Code (IaC), allowing teams to define and manage infrastructure with declarative configuration files. A core component of this flexibility and power lies in input variables. They are the mechanism through which you can parameterize your configurations, making them reusable, adaptable, and easier to manage across different environments and teams.

This post will dive into what Terraform input variables are, how to declare and use them, explore their various types, and discuss best practices for managing them effectively.

Table of Contents

  1. What Are Terraform Input Variables?
  2. Declaring Input Variables
  3. Understanding Data Types
  4. Assigning Values to Variables
  5. Advanced Variable Features
  6. Why Effective Variable Management is Crucial
  7. Summary Table: Key Variable Concepts
  8. Conclusion

What Are Terraform Input Variables?

At their core, Terraform input variables are like function arguments in a programming language. They allow you to pass values into your Terraform modules or root configurations, customizing their behavior without modifying the underlying code.

Key Benefits:

  • Reusability: Write a generic module once (e.g., for a web server) and reuse it for different applications or environments by simply changing the input variable values (like instance size or image ID).
  • Modularity: Build complex infrastructure by combining smaller, self-contained modules that communicate and are configured via input variables.
  • Maintainability: Centralize configuration changes. Instead of hunting through code, you update variable files or settings, reducing the risk of errors.
  • Consistency: Enforce standards by parameterizing common settings, ensuring deployments follow established patterns.

Input variables decouple the data (your specific settings) from the logic (how resources are defined). This separation is fundamental to scalable and maintainable IaC.

Declaring Input Variables

Input variables are declared using variable blocks, typically in a dedicated variables.tf file for better organization.

Basic Syntax

variable "variable_name" {
  # Arguments defining the variable's properties
}

The "variable_name" is how you'll refer to this variable within your Terraform code (using var.variable_name) and when assigning values to it.

Key Arguments

While several arguments can be used within a variable block, three are essential for well-defined variables:

default (Optional): Provides a default value for the variable. If a default is set, the variable becomes optional; Terraform will use this value if no other is provided.

variable "enable_monitoring" {
  description = "Flag to enable detailed monitoring."
  type        = bool
  default     = true
}

The default value must be a literal and cannot reference other objects.

type (Optional but Highly Recommended): Specifies the data type of the variable (e.g., string, number, bool, list(string), object(...)). If omitted, the type is any. Explicitly defining types enables Terraform to validate inputs early.

variable "instance_count" {
  description = "Number of instances to create."
  type        = number
  default     = 1
}

description (Optional but Highly Recommended): A string that explains the purpose of the variable. This is crucial for documentation and helps others (and your future self) understand how to use the module.

variable "image_id" {
  description = "The ID of the machine image (AMI) to use for the server."
  type        = string
  default     = "ami-default123"
}

Understanding Data Types

Terraform supports a range of data types for input variables:

Primitive Types

  • string: A sequence of Unicode characters (e.g., "my-instance-name").
  • number: Numeric values, including integers and floating-point numbers (e.g., 3, 10.5).
  • bool: Boolean values, either true or false.

Collection Types

  • set(<TYPE>): An unordered collection of unique elements of the same specified type.

map(<TYPE>): A collection of key-value pairs, where keys are strings and all values are of the same specified type.

variable "common_tags" {
  description = "A map of tags to apply to resources."
  type        = map(string)
  default = {
    Environment = "dev"
    Project     = "alpha"
  }
}

list(<TYPE>): An ordered sequence of elements, all of the same specified type.

variable "subnet_ids" {
  description = "A list of subnet IDs."
  type        = list(string)
  default     = ["subnet-abcde012", "subnet-fghij345"]
}

Structural Types

These allow for more complex, custom data structures:

  • tuple(): An ordered sequence where elements can have different specified types.

object({<ATTR_NAME> = <TYPE>, ...}): Defines a structure with named attributes, each potentially having a different type. This is very useful for grouping related configuration settings.

variable "server_configuration" {
  description = "Configuration for a server instance."
  type = object({
    instance_type  = string
    ami_id         = string
    enable_ebs_opt = bool
    tags           = map(string)
  })
  default = {
    instance_type  = "t2.micro"
    ami_id         = "ami-0c55b31ad549f9278"
    enable_ebs_opt = false
    tags = {
      Name = "DefaultServer"
    }
  }
}

The any type can also be used if a variable needs to accept any type, but this bypasses type checking and should be used judiciously.

Assigning Values to Variables

Terraform offers several ways to set values for input variables declared in your root module:

Variable Definition Files (.tfvars)

These are the most common way to manage persistent variable values, especially for different environments.

  • *.auto.tfvars: Any files with the .auto.tfvars (or .auto.tfvars.json) extension are also automatically loaded in lexical order. This allows for splitting variable definitions across multiple files.
  • Custom .tfvars files: You can specify custom variable files using the -var-file command-line flag.

terraform.tfvars: If a file named terraform.tfvars (or terraform.tfvars.json) exists in the root directory, Terraform automatically loads it. Example terraform.tfvars:

instance_name_prefix = "web"
instance_count       = 3
common_tags = {
  Environment = "production"
  Team        = "Frontend"
}

Command-Line Flags

-var-file="<path_to_file>": Loads variables from a specified file.

terraform apply -var-file="production.tfvars"

-var="<variable_name>=<value>": Sets an individual variable.

terraform apply -var="instance_count=5" -var='common_tags={Environment="staging"}'

Environment Variables

Terraform reads environment variables prefixed with TF_VAR_.

export TF_VAR_instance_count=2
export TF_VAR_aws_region="us-east-1"
terraform apply

Order of Precedence

Terraform loads variables in the following order (later sources override earlier ones):

  1. default values in variable blocks.
  2. Environment variables (TF_VAR_...).
  3. terraform.tfvars file.
  4. terraform.tfvars.json file.
  5. *.auto.tfvars and *.auto.tfvars.json files (lexical order).
  6. -var-file options on the command line (order of specification).
  7. -var options on the command line (order of specification).

HCP Terraform workspace variables also have high precedence, typically acting like command-line options.

Advanced Variable Features

Terraform offers more advanced capabilities for variables:

Custom Validation Rules

You can define validation blocks within a variable to enforce custom rules beyond basic type checking.

variable "image_id" {
  type        = string
  description = "The ID of the machine image (AMI) to use for the server."

  validation {
    condition     = length(var.image_id) > 4 && substr(var.image_id, 0, 4) == "ami-"
    error_message = "The image_id value must be a valid AMI ID, starting with \"ami-\"."
  }
}

This helps catch errors early and ensures inputs meet specific requirements.

Sensitive Variables

For confidential data like passwords or API keys, you can mark a variable as sensitive = true.

variable "db_password" {
  description = "Database administrator password."
  type        = string
  sensitive   = true
  # It's good practice not to provide a default for sensitive variables.
}

Terraform will redact the values of sensitive variables from CLI output. However, these values are still stored in plain text in the Terraform state file, so state file security is paramount. For data that should not be in the state at all, ephemeral = true can be used.

Why Effective Variable Management is Crucial

As infrastructure complexity grows and more teams adopt IaC, managing variables effectively becomes critical. While Terraform provides the foundational tools, ensuring consistency, security, and ease of use across an organization requires a more structured approach.

  • Clarity and Consistency: Well-defined variables with clear descriptions and types make modules easier to understand and use, reducing misconfigurations.
  • Environment Management: Separating variables for development, staging, and production is essential. This is often handled with different .tfvars files.
  • Secrets Management: Handling sensitive data requires care. While sensitive = true helps, integrating with dedicated secrets management tools or using platforms that offer secure variable storage is a best practice.
  • Governance and Collaboration: In larger organizations, ensuring that teams use variables correctly, adhere to naming conventions, and manage environment-specific configurations can be challenging.

This is where platforms built on top of or around Terraform, like Scalr, can provide significant value. Scalr offers features such as hierarchical variable management, environment-specific variable sets, built-in secrets management, and policy enforcement (Open Policy Agent integration). This allows organizations to centralize variable definitions, enforce standards, and provide a more streamlined and secure operational experience for Terraform users, especially when managing multiple workspaces, teams, and complex cloud environments. By abstracting some of the manual overhead, such platforms help teams focus on defining infrastructure rather than wrestling with the intricacies of variable organization at scale.

Summary Table: Key Variable Concepts

Feature/Argument

Purpose

Example Usage in variable block

variable "name"

Declares an input variable.

variable "instance_type" { ... }

description

Documents the variable's purpose.

description = "The EC2 instance type."

type

Specifies the expected data type for the variable's value.

type = string

default

Provides a default value, making the variable optional.

default = "t2.micro"

sensitive

Marks the variable's value as sensitive, redacting it from CLI output.

sensitive = true

validation

Defines custom rules to validate the variable's value.

validation { condition = ..., error_message = ... }

var.name

References the variable's value in configuration.

instance_type = var.instance_type

Conclusion

Terraform input variables are fundamental to creating flexible, reusable, and maintainable infrastructure as code. By understanding how to declare them, leveraging the type system, and following best practices for assigning and managing their values, you can significantly improve your IaC workflows. For organizations looking to scale their Terraform operations, considering how to manage variables across teams and environments becomes paramount, and leveraging platforms designed for this purpose can offer substantial benefits in terms of efficiency, governance, and security.