Skip to content

2.04 Terraform Input Variables

Overview

Input variables allow Terraform configurations to be parameterised — separating values from resource logic so the same code can be reused across environments without modifying main.tf.

Abstract

Hardcoding argument values directly in resource blocks reduces reusability and makes configs brittle. Input variables declared in variables.tf decouple values from logic, enabling the same configuration to deploy different resources simply by changing variable values — without touching the resource definitions.


Why It Matters in Production

  • Reuse the same Terraform modules across dev, staging, and production by passing different variable values
  • Keep main.tf stable and reviewable — only variables.tf or .tfvars files change between deployments
  • Enables CI/CD pipelines to inject environment-specific values at runtime without modifying source code
  • Prevents accidental hardcoded secrets and environment-specific values from leaking into shared configs

Key Concepts

Concept Description
Input variable A parameterised value declared with the variable block
default Optional fallback value used when no value is explicitly passed
var.<name> Syntax to reference a variable inside a resource block
variables.tf Convention file where all variable declarations are stored
Hardcoded value A literal value written directly in a resource argument — avoid in production

Variable Block Syntax

variable "<name>" {
  default = "<value>"
}
  • The variable keyword is required
  • The name should match the argument it represents (e.g. filename, content)
  • default is optional but is the simplest way to assign a value
  • No quotes around var.<name> references in resource blocks

Example Configuration & Commands

Before — hardcoded values in main.tf

resource "local_file" "pet" {
  filename = "/root/pets.txt"
  content  = "We love pets!"
}

resource "random_pet" "my-pet" {
  prefix    = "Mrs"
  separator = "."
  length    = "1"
}

After — values moved to variables.tf

resource "local_file" "pet" {
  filename = var.filename
  content  = var.content
}

resource "random_pet" "my-pet" {
  prefix    = var.prefix
  separator = var.separator
  length    = var.length
}
variable "filename" {
  default = "/root/pets.txt"
}

variable "content" {
  default = "We love pets!"
}

variable "prefix" {
  default = "Mrs"
}

variable "separator" {
  default = "."
}

variable "length" {
  default = "1"
}

Applying the configuration

terraform plan
terraform apply

Updating values without touching main.tf

To change the file content and pet name length, update only variables.tf:

variable "content" {
  default = "My favorite pet is Mrs. Whiskers"
}

variable "length" {
  default = "2"
}

Then re-apply:

terraform apply

Note

Changing content or length forces Terraform to destroy and recreate the affected resources. The plan will show -/+ (destroy then create) for those resources.

apply output — forced replacement example

-/+ resource "local_file" "pet" {
      ~ content   = "We love pets!" -> "My favorite pet is Mrs. Whiskers" # forces replacement
        filename  = "/root/pet.txt"
    }

-/+ resource "random_pet" "my-pet" {
      ~ id        = "Mrs.Hen" -> (known after apply)
      ~ length    = 1 -> 2    # forces replacement
        prefix    = "Mrs"
        separator = "."
    }

Plan: 2 to add, 0 to change, 2 to destroy.

AWS EC2 example with variables

resource "aws_instance" "webserver" {
  ami           = var.ami
  instance_type = var.instance_type
}
variable "ami" {
  default = "ami-0edab43b6fa892279"
}

variable "instance_type" {
  default = "t2.micro"
}

Variable Block — Full Syntax

A variable block supports additional optional meta-arguments beyond default:

variable "instance_type" {
  description = "EC2 instance type to deploy"
  type        = string
  default     = "t2.micro"
  sensitive   = false
}
Meta-argument Description
description Human-readable explanation of the variable's purpose
type Enforces a type constraint: string, number, bool, list, map, object
default Fallback value when none is provided
sensitive Hides the value from plan/apply output and state logs

Best Practices

Best Practices

  • Always declare variables in a dedicated variables.tf file — keep resource logic in main.tf separate from values.
  • Add a description to every variable so collaborators and module users understand its purpose.
  • Add a type constraint to catch misconfigured inputs early, before apply.
  • Use sensitive = true for passwords, tokens, and private keys — this prevents the value from appearing in logs.
  • Use .tfvars files (e.g. dev.tfvars, prod.tfvars) to manage environment-specific values rather than changing default values per environment.
  • Never store .tfvars files with secrets in version control — add them to .gitignore.

Security Best Practices

Security

  • Mark sensitive variables (password, token, secret_key) with sensitive = true — Terraform will redact them in plan and apply output.
  • Do not use default for secrets — inject them via environment variables (TF_VAR_<name>) or a secrets manager at runtime.
  • Avoid committing terraform.tfvars files containing real credentials — use CI secret injection or Vault instead.
  • Sensitive values still appear in state — ensure remote state backends have encryption at rest enabled.

Do and Don't

✅ Do ❌ Don't
Declare all variables in variables.tf Hardcode environment-specific values directly in main.tf
Use var.<name> (no quotes) to reference variables Wrap var.<name> in quotes — it will be treated as a literal string
Add description and type to every variable Leave variables undocumented and untyped in shared modules
Use sensitive = true for secrets Log or output sensitive variable values in plaintext
Use .tfvars files for per-environment overrides Change default values in variables.tf to switch environments

Common Mistakes

Common Mistakes

  • Quoting variable references: Writing filename = "var.filename" treats it as a literal string. The correct form is filename = var.filename (no quotes).
  • Not expecting forced replacement: Changing a variable value used in an immutable argument (like content in local_file) will destroy and recreate the resource, not update it in place.
  • Missing default with no other value source: If default is omitted and no .tfvars or -var flag is provided, Terraform prompts interactively — which blocks CI pipelines.
  • Reusing the same variable name for different resources: Variable names are global within a configuration directory — name collisions cause unexpected value sharing.

Troubleshooting

# Check what value a variable resolves to in the current config
terraform console
> var.filename

# Pass a variable value directly on the command line (overrides default)
terraform apply -var="length=3"

# Pass all variables from a file
terraform apply -var-file="prod.tfvars"

# Set a variable via environment variable (useful in CI)
export TF_VAR_length=3
terraform apply

# Validate the configuration (catches type mismatches and missing variables)
terraform validate

Quick Recap

  • Variables remove hardcoded values from resource blocks, making configs reusable and environment-agnostic
  • Declare variables in variables.tf using the variable block; reference them with var.<name> (no quotes)
  • default provides a fallback value; variables without a default require a value at runtime
  • Changing a variable value may force resource replacement depending on which argument it affects
  • Use sensitive = true, .tfvars files, and TF_VAR_ environment variables for production-grade variable management

Keeping Variable Values Out of GitHub

Committing real values (passwords, tokens, paths, AMI IDs) inside variables.tf exposes them in version control history. Use the following pattern to keep declarations public and values private.

Pattern — separate declarations from values

# Declarations only — no default values
variable "filename" {
  description = "Path to the file"
  type        = string
}

variable "content" {
  description = "File content"
  type        = string
  sensitive   = true
}
filename = "/root/pets.txt"
content  = "We love pets!"

Add to .gitignore:

echo "*.tfvars" >> .gitignore

Inject values in CI/CD (GitHub Actions)

Store secrets in GitHub → Settings → Secrets and variables → Actions, then reference them in your workflow:

env:
  TF_VAR_content: ${{ secrets.TF_CONTENT }}
  TF_VAR_filename: ${{ secrets.TF_FILENAME }}

Terraform automatically picks up any environment variable prefixed with TF_VAR_.

What to commit vs what to ignore

File Commit to Git? Reason
variables.tf (no defaults) ✅ Yes Contains only shape/type — no values
terraform.tfvars ❌ No Contains real values — gitignore it
*.auto.tfvars ❌ No Auto-loaded by Terraform — same risk
.terraform.lock.hcl ✅ Yes Pins provider versions — safe and recommended
*.tfstate / *.tfstate.backup ❌ Never Contains all resource attributes in plaintext

Tip

For sensitive values like passwords and API keys, skip tfvars entirely and inject via TF_VAR_ environment variables from your CI secret store or HashiCorp Vault. Nothing sensitive ever touches disk.


Interview / Revision Notes

Revision

  • Why use input variables instead of hardcoded values? They enable code reuse across environments and separate configuration logic from values.
  • What syntax references a variable in a resource block? var.<variable_name> — no quotes.
  • What file conventionally holds variable declarations? variables.tf.
  • What happens if a variable has no default and no value is provided? Terraform prompts interactively, or errors in non-interactive (CI) environments.
  • How do you pass a variable value without editing any .tf file? Use -var="name=value", a -var-file, or TF_VAR_<name> environment variable.
  • Does changing a variable always update resources in place? No — some argument changes force destroy-and-recreate (shown as -/+ in the plan).