What's Actually Happening
You're passing values to Terraform variables that don't match the declared type. Terraform's type system is strict about type matching, and mismatches cause immediate failures during validation or plan phases.
The Error You'll See
Basic type mismatches:
``` Error: Invalid value for input variable
on variables.tf line 15: The given value is not suitable for var.instance_count declared at variables.tf:15,1: number required. ```
Collection type errors:
``` Error: Invalid value for input variable
on variables.tf line 23: The given value is not suitable for var.subnet_ids declared at variables.tf:23,1: list of string required. ```
Object type errors:
``` Error: Invalid value for input variable
on variables.tf line 45: The given value is not suitable for var.tags declared at variables.tf:45,1: all object attributes must have corresponding variable attributes.
Attribute "Environment" is required. ```
Complex nested types:
``` Error: Invalid value for input variable
on variables.tf line 67: The given value is not suitable for var.server_config declared at variables.tf:67,1: attribute "instance_type": string required. ```
Why This Happens
Common causes of type mismatch errors:
- 1.Wrong primitive type - Passing string when number expected
- 2.Null vs empty values - Passing null instead of empty string/list
- 3.Collection type mismatch - List vs set vs tuple
- 4.Map key type errors - Non-string keys in maps
- 5.Object attribute missing - Required object field not provided
- 6.TFVARS formatting - Incorrect .tfvars syntax
- 7.Environment variable strings - All env vars are strings
- 8.JSON parsing issues - Improper JSON to HCL conversion
Step 1: Understand Variable Type Declarations
Terraform supports several type constraints:
```hcl # Basic types variable "instance_count" { type = number default = 1 }
variable "environment" { type = string default = "dev" }
variable "enabled" { type = bool default = true }
# Collection types variable "subnet_ids" { type = list(string) default = [] }
variable "availability_zones" { type = list(string) default = ["us-east-1a", "us-east-1b"] }
variable "tags" { type = map(string) default = {} }
variable "unique_ids" { type = set(string) default = [] }
# Tuple (fixed-size list with specific types) variable "coordinates" { type = tuple([number, number, string]) default = [10, 20, "point-a"] }
# Object (map with required attributes) variable "server_config" { type = object({ instance_type = string ami_id = string volume_size = number }) default = { instance_type = "t3.micro" ami_id = "ami-12345678" volume_size = 20 } } ```
Step 2: Fix Basic Type Mismatches
When passing values via command line or tfvars:
```bash # Wrong: passing string as number terraform apply -var="instance_count=3" # This is actually correct terraform apply -var="instance_count=three" # Wrong - string not number
# Correct: pass number terraform apply -var="instance_count=3"
# Wrong: passing string as bool terraform apply -var="enabled=true" # String "true", not bool true
# Correct: for bools, use proper format # In terraform.tfvars: enabled = true # Boolean, not string
# Or via environment (all env vars are strings!) export TF_VAR_enabled=true # This becomes string "true" # Terraform will convert, but be explicit ```
In terraform.tfvars:
# Correct type assignments
instance_count = 3 # number
environment = "production" # string
enabled = true # bool
subnet_ids = ["subnet-1", "subnet-2"] # list(string)
tags = { # map(string)
Name = "web-server"
Environment = "prod"
}Step 3: Handle Null vs Empty Values
Null and empty values behave differently:
```hcl variable "optional_tags" { type = map(string) default = null # Can be null or empty map }
variable "required_tags" { type = map(string) default = {} # Empty map, not null } ```
When using these variables:
```hcl # Handle null safely resource "aws_instance" "web" { ami = var.ami_id instance_type = var.instance_type
tags = var.optional_tags != null ? var.optional_tags : {} }
# Or use coalesce tags = coalesce(var.optional_tags, {})
# For optional object attributes variable "config" { type = object({ name = string value = optional(string, "default") }) } ```
Step 4: Fix List and Set Confusion
Lists and sets are different:
```hcl # List - ordered, allows duplicates variable "subnet_ids" { type = list(string) default = ["subnet-1", "subnet-2", "subnet-1"] # Duplicates allowed }
# Set - unordered, unique values only variable "unique_security_groups" { type = set(string) default = ["sg-1", "sg-2"] # Duplicates removed automatically }
# Converting between them variable "my_list" { type = list(string) default = ["a", "b", "c"] }
output "as_set" { value = toset(var.my_list) # Convert list to set }
output "as_list" { value = tolist(toset(var.my_list)) # Convert back (deduplicated) } ```
When passing lists via command line:
```bash # Wrong: string instead of list terraform apply -var="subnet_ids=subnet-1"
# Correct: JSON format for complex types terraform apply -var='subnet_ids=["subnet-1","subnet-2"]'
# Or use -var-file terraform apply -var-file="prod.tfvars" ```
Step 5: Fix Map and Object Issues
Maps require string keys:
```hcl # Correct: string keys variable "tags" { type = map(string) default = { "Name" = "web-server" "Environment" = "production" } }
# Also correct: unquoted keys (valid identifiers) variable "tags" { type = map(string) default = { Name = "web-server" Environment = "production" } }
# Wrong: numeric keys (must be strings) variable "ports" { type = map(string) # default = { 80 = "http", 443 = "https" } # Error!
# Correct: convert to strings default = { "80" = "http", "443" = "https" } } ```
Object attribute mismatches:
```hcl variable "server" { type = object({ name = string instance_type = string port = number }) }
# Correct assignment server = { name = "web-01" instance_type = "t3.micro" port = 80 }
# Wrong: missing required attribute server = { name = "web-01" instance_type = "t3.micro" # port is missing - error! }
# Wrong: wrong type for attribute server = { name = "web-01" instance_type = "t3.micro" port = "80" # String, not number - error! } ```
Step 6: Handle Optional Object Attributes
Use optional() for flexible objects:
variable "instance_config" {
type = object({
name = string
instance_type = string
ami = optional(string)
tags = optional(map(string), {})
monitoring = optional(bool, false)
})
default = {
name = "default"
instance_type = "t3.micro"
# ami omitted - will be null
# tags omitted - will be {}
# monitoring omitted - will be false
}
}Step 7: Debug Variable Values
Inspect actual variable values:
```bash # Output all variables for debugging terraform console > var.instance_count 3 > type(var.subnet_ids) list(string) > var.tags {"Environment" = "production", "Name" = "web-server"}
# Use terraform output for computed values terraform output -json | jq .
# Debug in configuration output "debug_variables" { value = { instance_count_type = type(var.instance_count) subnet_ids_type = type(var.subnet_ids) tags_type = type(var.tags) } } ```
Step 8: Fix Environment Variable Issues
Environment variables are always strings:
```bash # Environment variables passed to Terraform export TF_VAR_instance_count=3 # This is STRING "3", not number
# Terraform will attempt to convert, but explicit is better # In terraform.tfvars or -var: instance_count = 3 # Proper number
# For complex types via environment, use JSON export TF_VAR_config='{"name":"web","port":80}'
# Then in variables.tf variable "config" { type = object({ name = string port = number }) } ```
Step 9: Validate Variable Files
Check .tfvars syntax:
```bash # Validate syntax terraform fmt -check terraform.tfvars
# Check if variables are valid terraform validate
# View parsed variables terraform console > var
# Use JSON format for validation cat terraform.tfvars.json { "instance_count": 3, "subnet_ids": ["subnet-1", "subnet-2"], "tags": { "Name": "web-server" } } ```
Step 10: Common Type Conversion Patterns
Explicit type conversions:
```hcl # Convert string to number number_value = tonumber("42")
# Convert number to string string_value = tostring(42)
# Convert to boolean bool_value = tobool("true")
# Convert list to set set_value = tolist(var.my_set)
# Convert map to object (careful!) object_value = { name = var.map_value["name"] type = var.map_value["type"] }
# Safe conversion with defaults safe_number = tonumber(var.string_number) != null ? tonumber(var.string_number) : 0
# Using can() for safe conversion safe_value = can(tonumber(var.input)) ? tonumber(var.input) : 0 ```
Verify the Fix
After correcting type mismatches:
```bash # Validate configuration terraform validate
# Success message: # Success! The configuration is valid.
# Plan with specific variables terraform plan -var="instance_count=3" -var="subnet_ids=['subnet-1','subnet-2']"
# Or with variable file terraform plan -var-file="prod.tfvars" ```
Check variable types in console:
terraform console
> type(var.instance_count)
number
> type(var.subnet_ids)
list(string)
> var.subnet_ids
tolist(["subnet-1", "subnet-2"])