Skip to content
ExponaBLOG
TERRAFORM, IAC & CLOUD AUTOMATION · DAY 006 / 100

Terraform variables in Terraform, IaC & Cloud Automation

Make configurations reusable.

Expona Admin8 min read

Introduction

Terraform variables are the foundation of infrastructure-as-code reusability. Without them, you'd be hardcoding values across dozens of files, copying and pasting configurations for each environment and duplicating effort every time you deploy to a new region or account. This article covers how to declare variables properly, set their values safely, protect sensitive data, and validate inputs before they cause production outages.

Why Variables Matter in Infrastructure Code

When you're managing infrastructure through Terraform, configuration drift and maintainability become real concerns fast. A variable-driven approach lets you write your infrastructure once and reuse it across dev, staging, and production environments without touching the core logic. You define the structure, then inject different values depending on where you're deploying.

This is especially critical in teams. When a junior engineer needs to deploy a new database instance to staging, they shouldn't need to understand or modify your core Terraform modules. They should set a few variables and let the infrastructure do the work.

Declare: Type, Description and Default

Every variable starts with a declaration block. You specify the type, add a description for teammates, and optionally provide a default value. If you don't provide a default, Terraform will prompt you for a value at runtime—or require it via other input methods.

hcl
variable "environment" {
  type        = string
  description = "Deployment environment"
  default     = "dev"
}

Types matter. Terraform supports string, number, bool, list, map, set, object, and tuple. Using the right type prevents mistakes at the Terraform level rather than downstream in your infrastructure.

hcl
variable "instance_count" {
  type        = number
  description = "Number of web server instances"
  default     = 3
}

variable "tags" {
  type        = map(string)
  description = "Common tags for all resources"
  default = {
    Team       = "platform"
    CostCenter = "engineering"
  }
}

variable "allowed_cidrs" {
  type        = list(string)
  description = "CIDR blocks allowed for SSH access"
  default     = ["10.0.0.0/8"]
}

The description field is not just documentation. It appears in terraform plan output and variable UI in Terraform Cloud. Write descriptions your team will actually read and understand.

Validation: Catch Mistakes Before Plan

Validation blocks let you enforce constraints on variables. Instead of catching bad input deep in your infrastructure provisioning—or worse, in production—you catch it immediately when Terraform reads the variable.

hcl
variable "environment" {
  type        = string
  description = "Deployment environment"
  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Use dev, staging or prod."
  }
}

This validates before any resource is created. If someone sets environment = "production" instead of prod, Terraform fails immediately with a clear message. You can chain multiple validation blocks for complex rules.

hcl
variable "database_size" {
  type        = number
  description = "Database instance size in GB"
  
  validation {
    condition     = var.database_size >= 10
    error_message = "Database must be at least 10 GB."
  }
  
  validation {
    condition     = var.database_size <= 5000
    error_message = "Database cannot exceed 5000 GB."
  }
}

Real-world scenario: You have a naming convention that all resources must follow. Validate it at the variable level.

hcl
variable "resource_prefix" {
  type        = string
  description = "Prefix for all resource names"
  
  validation {
    condition     = can(regex("^[a-z][a-z0-9-]{2,}$", var.resource_prefix))
    error_message = "Prefix must start with lowercase, contain only lowercase/numbers/hyphens, and be at least 3 chars."
  }
}

Validation blocks are a guardrail your team will thank you for. They shift errors left and make infrastructure code more predictable.

Set Values: Three Ways to Inject Variables

Method 1: .tfvars Files

The most common approach in teams is using .tfvars files. These are Terraform-specific variable files that you can version control and share.

hcl
environment        = "prod"
instance_count     = 5
database_size      = 100
allowed_cidrs      = ["203.0.113.0/24", "198.51.100.0/24"]
resource_prefix    = "api-svc"

When you run Terraform, load the file explicitly:

hcl
terraform plan -var-file="prod.tfvars"
terraform apply -var-file="prod.tfvars"

You can also follow Terraform's automatic loading convention: any file named *.auto.tfvars is loaded automatically without needing the -var-file flag. This works well in CI/CD pipelines where you want predictable behavior without extra flags.

Method 2: Command-Line Flags

For quick overrides or one-off deployments, use -var:

hcl
terraform plan -var="environment=staging" -var="instance_count=2"

This is handy in development but becomes unwieldy at scale. Your CI/CD pipeline shouldn't have dozens of -var flags in a command line.

Method 3: Environment Variables

Terraform reads environment variables prefixed with TF_VAR_. This integrates well with CI/CD systems and secrets management.

hcl
export TF_VAR_environment="prod"
export TF_VAR_instance_count="5"
terraform plan

In CI/CD (GitHub Actions, GitLab CI, Jenkins), you often set these from secrets:

hcl
env:
  TF_VAR_database_password: ${{ secrets.DB_PASSWORD }}
  TF_VAR_aws_region: us-east-1

Environment variables are powerful but less visible than files. Team members might not realize a value is being injected, leading to confusion. Use them for secrets and sensitive overrides, but keep your main configuration in .tfvars files.

Sensitive Variables: Hide Secrets from Output

When you run terraform plan or terraform apply, Terraform prints the values of variables in the output. For passwords, API keys, and other secrets, this is a security risk.

Mark sensitive variables with sensitive = true:

hcl
variable "database_password" {
  type        = string
  description = "Database root password"
  sensitive   = true
}

Now Terraform redacts that value in logs and output, displaying <sensitive> instead. This prevents accidental exposure in logs, CI/CD output, or Terraform Cloud runs.

hcl
variable "api_keys" {
  type        = map(string)
  description = "Third-party API keys"
  sensitive   = true
}

Important note: sensitive = true is a display-level protection. It doesn't encrypt the value in state files. You still need to protect your state with encryption and access controls. Keep state files in remote backends (S3, Terraform Cloud) with encryption enabled, and restrict who can read .tfstate files.

In practice, avoid storing raw secrets in Terraform at all. Use AWS Secrets Manager, HashiCorp Vault, or similar, then reference them in your variables:

hcl
data "aws_secretsmanager_secret_version" "db_password" {
  secret_id = "prod/database/password"
}

variable "database_password" {
  type        = string
  default     = data.aws_secretsmanager_secret_version.db_password.secret_string
  sensitive   = true
}

Variable Defaults and Optional Inputs

Defaults make your modules user-friendly. If a variable has a sensible default, most users won't need to specify it. But be careful: a bad default can cause silent failures.

hcl
variable "enable_monitoring" {
  type        = bool
  description = "Enable CloudWatch monitoring"
  default     = true  # Good: safe default
}

variable "log_retention_days" {
  type        = number
  description = "CloudWatch log retention in days"
  default     = 30  # Good: reasonable default
}

Avoid defaults that hide infrastructure decisions:

hcl
# Bad: defaults hide cost implications
variable "instance_type" {
  type        = string
  description = "EC2 instance type"
  default     = "t3.large"  # Risky default; should be explicit per environment
}

Better to require it explicitly or use environment-specific defaults via a map:

hcl
variable "instance_type" {
  type        = string
  description = "EC2 instance type"
  # No default; caller must provide
}

# Or use a local to vary by environment
locals {
  instance_types = {
    dev     = "t3.micro"
    staging = "t3.small"
    prod    = "t3.xlarge"
  }
  instance_type = local.instance_types[var.environment]
}

Common Mistakes

  • Not validating early: Skipping validation blocks means bad input gets far into your infrastructure before failing. Validate at the variable layer.
  • Hardcoding secrets: Never commit passwords or API keys to version control, even in .tfvars files. Use secret management systems and load them at runtime.
  • Missing descriptions: Variables without descriptions force teammates to dig through code. Descriptions are not optional.
  • Overusing complex types: A variable "config" { type = object(...) } with 15 nested fields is harder to maintain than breaking it into separate variables.
  • Conflicting input methods: Mixing environment variables, .tfvars files, and -var flags in the same workflow creates confusion. Standardize: use .auto.tfvars in CI/CD or a single .tfvars file per environment.
  • Forgetting about precedence: Terraform applies variables in order: defaults, then .tfvars files, then -var flags, then environment variables. Later values override earlier ones. Know this order.

Key Takeaways

  • Declare variables with explicit types and clear descriptions so your team understands what each value does.
  • Use validation blocks to catch configuration errors before Terraform creates or modifies infrastructure.
  • Store configuration in .tfvars files and inject sensitive values via environment variables or secret management systems.
  • Mark sensitive data with sensitive = true to redact values from logs, but remember this doesn't encrypt state files.
  • Provide sensible defaults for optional settings, but require explicit values for infrastructure decisions that affect cost or security.
  • Standardize how your team sets variables in CI/CD to avoid confusion and silent failures.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles