Terraform Variables and Precedence

A Beginner’s Guide to Simplify Your Workflow

Introduction:

When working with Terraform, variables are like building blocks that make your infrastructure code more flexible and reusable. Instead of hard coding values, you can use variables to easily customize configurations for different environments or scenarios.

Precedence, on the other hand, determines the order in which Terraform reads and overrides these variables when they’re defined in multiple sources. Grasping how variables work and understanding precedence rules is crucial for writing clean, predictable, and efficient code. In this blog, I’ll guide you through these concepts in a clear and straightforward way to help you use them effectively.

1. Variable Types & Declarations

🔑 Key Point: Terraform supports both basic and complex data types. Choose the right type for your use case.

Basic Types:

Basic types are most commonly used and should be your first choice unless you need complex structures.

1.1. String Variables:

String variables are used for text values like names, descriptions, or any textual configuration.

1.2 Number Variables

Number variables are used for quantities, sizes, or any numerical configuration like instance counts or port numbers.

1.3 Boolean Variables

Boolean variables are used for yes/no or true/false configurations, perfect for enabling/disabling features.

1.4 List Variables

List variables store multiple values of the same type in an ordered list, great for multiple similar items like subnet CIDR blocks.

1.5 Map Variables

Map variables store key-value pairs, perfect for lookup tables or environment-specific configurations.

Example:


# String: For text values like names, regions, etc.
variable "environment" {
  type    = string
  default = "development"
}

# Number: For counts, sizes, ports
variable "instance_count" {
  type    = number
  default = 2
}

# Boolean: For yes/no, true/false decisions
variable "enable_monitoring" {
  type    = bool
  default = true
}

# List: For ordered collections of values
variable "availability_zones" {
  type    = list(string)
  default = ["us-west-2a", "us-west-2b"]
}

# Map: For key-value pairs, like tags or configurations
variable "instance_types" {
  type = map(string)
  default = {
    dev  = "t2.micro"
    prod = "t2.large"
  }
}

Complex Types:

Complex types are powerful but make your code harder to maintain. Use them only when necessary.

1.6 Object Variables

Object variables are complex types combining multiple values of different types, useful for grouped configurations.

Use case:

Object Variable for VPC Configuration: An object variable can define cidr_block (string), dns_support (boolean), and instance_count (number) to configure a VPC with its CIDR block, DNS support setting, and the number of instances.

1.7 Tuple Variables

Tuple variables are complex types combining multiple values of varying types in a specific order, ideal for positional configurations.

Use case:

Tuple Variable for Instance Configuration: A tuple variable can specify [“t2.micro”, “ami-0c55b159cbfafe1f0”, “my-key-pair”] to define the instance type, AMI ID, and key pair for an EC2 instance.

1.8 Set Variables

Set variables are collections of unique values of the same type, perfect for unordered configurations without duplicates.

Use case:

Set Variable for Allowed Ports: A set variable can contain [22, 80, 443] to define the allowed ports for a security group.

Example:


# Object: For structured data with different types
# Use Case: VPC Configuration
variable "vpc_settings" {
  description = "Object variable for VPC configuration"
  type = object({
    cidr_block     = string
    dns_support    = bool
    instance_count = number
  })
  default = {
    cidr_block     = "10.0.0.0/16"
    dns_support    = true
    instance_count = 3
  }
}

# Tuple: For fixed-size lists with different types
# Use Case: Instance Configuration
variable "instance_config" {
  description = "Tuple variable for instance configuration"
  type        = tuple([string, string, string])
  default     = ["t2.micro", "ami-0c55b159cbfafe1f0", "my-key-pair"]
}

# Set: For unique values only
# Use Case: Allowed Ports
variable "allowed_ports" {
  description = "Set variable for defining allowed ports"
  type        = set(number)
  default     = [22, 80, 443]
}


Terraform Variable Precedence

Terraform uses a specific order of precedence when determining a variable’s value. Terraform will use the highest precedence value if the same variable is assigned multiple values, overriding any other values.

Order of Precedence (Low to High)

  1. Default variable values in variable declarations
  2. Environment variables (TF_VAR_*)
  3. terraform.tfvars file
  4. *.auto.tfvars files (loaded alphabetically)
  5. -var-file command line flags
  6. Individual -var command line flags

1. Default Variable Values (variables.tf)

  • Lowest precedence
  • Used when no other values are provided
  • Good for safe defaults

variable "environment" {
  default = "dev"
}

2. Environment Variables (TF_VAR_*):

  • Prefix variables with TF_VAR_
  • Use export TF_VAR_name="value" for sensitive values
  • Environment variables work across multiple Terraform runs
  • Useful for CI/CD pipelines

export TF_VAR_db_password="secret123"
export TF_VAR_api_key="abc123"

3. terraform.tfvars:

  • Single file automatically loaded
  • Project-specific defaults
  • Don’t commit sensitive values

4. .auto.tfvars (alphabetical order):

  • Multiple files, loaded alphabetically
  • Good for environment-specific values
  • Example: production.auto.tfvars

5. Named .tfvars files (-var-file flag):

  • Explicitly loaded via CLI
  • Example: terraform plan -var-file=”prod.tfvars”

6. Command Line Flags (-var):

  • Highest precedence
  • Overrides all other values
  • Example: terraform plan -var=”region=us-east-1″


Terraform Variable Precedence: A Practical Scenario

Scenario: Web Application Deployment

Imagine you’re deploying a web application with different configurations across environments. You have common settings and environment-specific settings but need to make a quick adjustment to the instance count during deployment.

Project Structure:


webapp/
├── main.tf
├── variables.tf
├── common.tfvars
└── dev.tfvars

1. Variable Declarations:


# variables.tf
variable "environment" {
  type        = string
  description = "Environment name"
}

variable "instance_type" {
  type        = string
  description = "EC2 instance type"
}

variable "instance_count" {
  type        = number
  description = "Number of instances to launch"
}

variable "region" {
  type        = string
  description = "AWS region"
}

variable "app_version" {
  type        = string
  description = "Application version to deploy"
}

variable "enable_monitoring" {
  type        = bool
  description = "Enable detailed monitoring"
}

2. Common Variables:


# common.tfvars
region            = "us-west-2"
app_version       = "1.2.0"
enable_monitoring = true
instance_count    = 1     # Default instance count

3. Development Environment Variables:


# dev.tfvars
environment       = "development"
instance_type     = "t2.small"
instance_count    = 3     # Development environment typically needs 3 instances
enable_monitoring = false # Override monitoring for dev

4. Main Configuration:


# main.tf
resource "aws_instance" "web_server" {
  count = var.instance_count

  ami           = data.aws_ami.ubuntu.id
  instance_type = var.instance_type

  tags = {
    Name        = "${var.environment}-web-${count.index + 1}"
    Environment = var.environment
    AppVersion  = var.app_version
  }

  monitoring = var.enable_monitoring
}

5. Deployment Command:


terraform apply Thank you for reaching out.
  -var-file="common.tfvars" \
  -var-file="dev.tfvars" \
  -var="instance_count=2"

6. What Actually Happens?

Let’s track how each variable gets its final value based on precedence:

6.1 First Load: common.tfvars:


region            = "us-west-2"      # ✓ KEPT (not overridden)
app_version       = "1.2.0"          # ✓ KEPT (not overridden)
enable_monitoring = true             # Will be overridden
instance_count    = 1                # Will be overridden

6.2 Then Load: dev.tfvars:


environment       = "development"     # ✓ KEPT (not overridden)
instance_type     = "t2.small"       # ✓ KEPT (not overridden)
instance_count    = 3                # Will be overridden
enable_monitoring = false            # ✓ KEPT (not overridden)

6.3 Finally Apply: Command Line Variable:


instance_count = 2                   # ✓ KEPT (highest precedence)

7. Final Effective Configuration:


# Final values used by Terraform
region            = "us-west-2"      # From common.tfvars
app_version       = "1.2.0"          # From common.tfvars
environment       = "development"     # From dev.tfvars
instance_type     = "t2.small"       # From dev.tfvars
enable_monitoring = false            # From dev.tfvars
instance_count    = 2                # From command line

8. Result:

  • 2 EC2 instances will be created (not 1 from common.tfvars or 3 from dev.tfvars)
  • Instances will be of type t2.small
  • Monitoring will be disabled
  • Instances will be in us-west-2
  • Instances will be tagged with app version 1.2.0
  • Names will be “development-web-1” and “development-web-2”

9. Important Notes:

1. Precedence Order: Command line > dev.tfvars > common.tfvars

2. Overrides:

  • Values in dev.tfvars override common.tfvars
  • Command-line variables override both tfvars files

3. Unspecified Values:

  • Variables defined in earlier files remain if not overridden
  • Each level only overrides what it explicitly specifies

4. Best Practices:

  • Use common.tfvars for shared configurations
  • Use environment tfvars for environment-specific settings
  • Use command-line variables sparingly, mainly for temporary overrides

5. Documentation:

  • Always document when using command-line overrides
  • Consider adding comments in tfvars files to indicate expected override patterns

10. Variable Processing Flow Chart:


Other Best Practices

  1. Sensitive Values:
  • Use environment variables
  • Never commit to version control
  • Example: export TF_VAR_db_password=”secret”

2. Environment Management:

  • Use separate .tfvars files per environment
  • Store in environments/ directory
  • Example: environments/prod.tfvars

3. Default Values:

  • Always provide safe defaults
  • Document expected values
  • Use validation blocks

Common Interview Questions

Q1: How would you handle different environments in Terraform?


A: Best practices include:
- Separate .tfvars files per environment
- Store in environments/ directory
- Use workspaces for isolation
- Example structure:
  environments/
  ├── dev.tfvars
  ├── staging.tfvars
  └── prod.tfvars

Q2: How do you manage sensitive variables in Terraform?


A: Multiple approaches:
1. Environment variables (TF_VAR_*)
2. Encrypted .tfvars files
3. Secret management services
4. Never commit sensitive values

Q3: What’s the difference between terraform.tfvars and *.auto.tfvars?


A: 
- terraform.tfvars: Single file, automatically loaded
- *.auto.tfvars: Multiple files, loaded alphabetically
- Both are automatic, but auto.tfvars allows multiple files

Q4: How would you override a variable for a specific run?


A: Use command line flags:
terraform plan \
  -var-file="environments/prod.tfvars" \
  -var="instance_type=t2.large"

Conclusion


In Terraform, mastering variables and their precedence ensures your configurations are dynamic, adaptable, and easy to maintain. By knowing which source of variables takes priority, you can avoid unexpected behavior in your code. This not only saves time but also helps you write efficient and reliable infrastructure-as-code. With these concepts in hand, you’re well-equipped to handle any Terraform project with confidence!


Your Thoughts Matter!

I’d love to hear what you think about this article — feel free to share your opinions in the comments below (or above, depending on your device!). If you found this helpful or enjoyable, a clap, a comment, or even a highlight of your favorite sections would mean a lot.

For more insights into the world of technology and data, visit subbutechops.com. There’s plenty of exciting content waiting for you to explore!

Thank you for reading, and happy learning! 🚀

Leave a Comment