Terraform variables are where flexibility begins—and where things can quietly go wrong if you're not intentional. One of the simplest features, variable defaults, often gets overlooked or misused, especially in reusable modules.
Let’s walk through how Terraform variable defaults actually behave, when they’re helpful, and where they can introduce subtle bugs.
Starting with a simple example
Here’s a basic variable definition with a default:
1variable "instance_type" {
2 type = string
3 default = "t3.micro"
4}If you don’t explicitly provide a value for instance_type, Terraform quietly falls back to t3.micro.
This is convenient—but also dangerous if the default doesn’t match your real-world expectations.
How Terraform decides which value wins
Defaults are the lowest priority in Terraform’s variable resolution. Values can come from multiple places, and Terraform picks the highest priority source.
From highest to lowest priority:
- Command-line flags (
-var) .tfvarsfiles- Environment variables (
TF_VAR_*) - Default values in the variable block
This means defaults are only used when nothing else is provided.
When defaults are actually helpful
Defaults shine in scenarios where a “reasonable baseline” exists.
1. Local development convenience
You can avoid constantly passing values during testing:
1variable "region" {
2 type = string
3 default = "us-east-1"
4}Developers can run Terraform quickly without extra setup.
2. Optional module inputs
Defaults make module inputs optional:
1variable "enable_logging" {
2 type = bool
3 default = true
4}Consumers don’t need to think about it unless they want to override behavior.
3. Sensible infrastructure defaults
Some values rarely change:
- Common tags
- Standard ports
- Baseline instance sizes
Defaults reduce repetition without sacrificing clarity.
Where things get tricky
A common mistake developers make is treating defaults as “safe fallbacks” in all cases. They’re not.
Hidden configuration issues
If a variable has a default, Terraform won’t complain when you forget to set it.
Example:
1variable "db_password" {
2 type = string
3 default = "password123"
4}This might silently deploy insecure infrastructure if no override is provided.
Unintended production behavior
Imagine this:
1variable "replica_count" {
2 type = number
3 default = 1
4}If someone forgets to override it in production, you end up under-provisioned.
Defaults don’t enforce correctness—they only prevent errors.
A better pattern: required vs optional variables
One of the most effective design decisions is simply deciding when not to use defaults.
Required variable (no default)
1variable "db_password" {
2 type = string
3}Terraform will force the user to provide a value.
Optional variable (with default)
1variable "backup_retention_days" {
2 type = number
3 default = 7
4}This keeps the module flexible while enforcing critical inputs.
Using null instead of a hard default
Here’s where things get interesting.
Instead of setting a fixed default, you can use null and handle logic explicitly:
1variable "instance_type" {
2 type = string
3 default = null
4}Then inside your resource:
1resource "aws_instance" "example" {
2 instance_type = var.instance_type != null ? var.instance_type : "t3.micro"
3}This gives you more control and makes fallback logic visible where it’s used.
Defaults in complex types
Defaults aren’t limited to simple values—you can define them for maps, lists, and objects.
Example: map default
1variable "tags" {
2 type = map(string)
3 default = {
4 Environment = "dev"
5 Owner = "team-a"
6 }
7}Example: object default
1variable "app_config" {
2 type = object({
3 port = number
4 log_level = string
5 })
6
7 default = {
8 port = 8080
9 log_level = "info"
10 }
11}Be careful here: partial overrides are not automatically merged unless you explicitly handle them.
Defaults vs tfvars: choosing the right place
There’s often confusion between defaults and .tfvars files.
- Defaults: baked into the module
- tfvars: environment-specific overrides
A practical approach:
- Put stable, reusable values in defaults
- Put environment-specific values in tfvars
Example:
1# variables.tf
2variable "region" {
3 default = "us-east-1"
4}
5
6# prod.tfvars
7region = "us-west-2"Validation with defaults
You can combine defaults with validation rules to avoid bad configurations:
1variable "instance_type" {
2 type = string
3 default = "t3.micro"
4
5 validation {
6 condition = can(regex("^t3", var.instance_type))
7 error_message = "Instance type must be from t3 family."
8 }
9}This ensures even default values follow your constraints.
Quick guidelines that hold up in real projects
- Use defaults for convenience, not correctness
- Avoid defaults for sensitive or critical values
- Prefer explicit inputs for production-impacting settings
- Use
nullwhen fallback logic needs context - Keep defaults simple—complex logic belongs in resources
Wrapping it up
Terraform variable defaults look simple, but they shape how your modules behave under pressure—especially when reused across teams and environments.
Used thoughtfully, they reduce friction and improve developer experience. Used carelessly, they hide misconfigurations until they become outages.
The trick is not to avoid defaults—but to be deliberate about when they exist and what they imply.