Day 11 of my Terraform journey was all about going deeper on conditionals.
I had already used conditionals briefly before, but today I focused on how they make a single Terraform configuration behave differently across environments without duplicating code.
This is what makes Terraform feel much smarter in real projects:
- dev and production can use the same module
- optional resources can be turned on or off cleanly
- invalid input can be caught before deployment
- one codebase can support different use cases
Why Conditionals Matter
Without conditionals, infrastructure code becomes repetitive very quickly.
You end up with:
- separate dev and production files that mostly repeat the same logic
- optional resources that require manual commenting in and out
- brittle outputs that fail when a resource is disabled
- confusing module behavior when bad input slips through
Conditionals solve that by making Terraform react to input values in a controlled way.
1. The Ternary Expression
The basic Terraform conditional is the ternary expression:
condition ? true_value : false_value
A common mistake is scattering that logic directly inside many resource arguments.
Before
resource "aws_instance" "web" {
instance_type = var.environment == "production" ? "t3.small" : "t3.micro"
}
This works, but if you repeat the same logic in many places, the configuration becomes hard to read.
Better Pattern: Use locals
locals {
is_production = var.environment == "production"
instance_type = local.is_production ? "t3.small" : "t3.micro"
min_size = local.is_production ? 3 : 1
max_size = local.is_production ? 10 : 3
}
resource "aws_instance" "web" {
instance_type = local.instance_type
}
This solves a real problem:
- the decision logic stays in one place
- resources stay easier to read
- testing environment differences becomes simpler
2. Optional Resources with count = condition ? 1 : 0
This is one of the most useful Terraform patterns.
It lets you create a resource only when a condition is true.
resource "aws_cloudwatch_metric_alarm" "high_cpu" {
count = var.enable_detailed_monitoring ? 1 : 0
alarm_name = "${var.cluster_name}-high-cpu"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 2
metric_name = "CPUUtilization"
namespace = "AWS/EC2"
period = 120
statistic = "Average"
threshold = 80
}
This solves the problem of optional infrastructure.
Instead of:
- duplicating code
- maintaining separate files
- commenting blocks in and out
you can simply say:
- create it when enabled
- skip it when disabled
I used the same idea for optional DNS creation too.
resource "aws_route53_record" "web" {
count = var.create_dns_record ? 1 : 0
zone_id = data.aws_route53_zone.primary[0].zone_id
name = var.domain_name
type = "A"
ttl = 300
records = [aws_instance.web.public_ip]
}
3. Referencing Conditionally Created Resources Safely
This is where many people get tripped up.
If a resource uses count, Terraform no longer sees it as a single object. It becomes a list.
Wrong
output "alarm_arn" {
value = aws_cloudwatch_metric_alarm.high_cpu.arn
}
This breaks when count = 0 because the resource does not exist.
Correct
output "alarm_arn" {
value = var.enable_detailed_monitoring ? aws_cloudwatch_metric_alarm.high_cpu[0].arn : null
}
This solves a very real problem:
- outputs remain safe
- Terraform does not crash when the optional resource is missing
- the module can behave differently without breaking downstream consumers
In my Day 11 module, I used the same pattern for both:
alarm_arndns_record_fqdn
4. Input Validation Blocks
Validation is one of the most practical Terraform features for shared modules.
variable "environment" {
description = "Deployment environment: dev, staging, or production"
type = string
validation {
condition = contains(["dev", "staging", "production"], var.environment)
error_message = "Environment must be dev, staging, or production."
}
}
This solves the problem of bad input reaching plan or apply.
Without validation:
- someone could pass
produtionby mistake - the module might behave unexpectedly
- debugging becomes harder
With validation:
- Terraform fails early
- the error is clear
- invalid values are caught before deployment starts
That is especially useful when modules are shared across teams.
5. The Environment-Aware Module Pattern
This was the biggest takeaway of the day.
Instead of maintaining separate modules for dev and production, I built one module that changes behavior based on a single environment variable.
locals {
is_production = var.environment == "production"
instance_type = local.is_production ? "t3.small" : "t3.micro"
min_cluster_size = local.is_production ? 3 : 1
max_cluster_size = local.is_production ? 10 : 3
detailed_monitoring_enabled = local.is_production
dns_record_enabled = local.is_production
}
Then the root configurations become very small.
Dev
module "webserver_cluster" {
source = "../../modules/services/webserver-cluster"
cluster_name = "day11-web-dev"
environment = "dev"
}
Production
module "webserver_cluster" {
source = "../../modules/services/webserver-cluster"
cluster_name = "day11-web-production"
environment = "production"
create_dns_record = false
enable_detailed_monitoring = true
}
This solves a major real-world problem:
- one reusable module
- environment-specific behavior
- less duplication
- fewer chances for config drift between environments
A Real Problem I Hit
One useful lesson from today came from Route53.
Because production defaulted to enabling DNS, Terraform tried to look up a hosted zone during planning. Since that hosted zone did not exist in my AWS account, terraform plan failed.
That showed why conditionals matter so much:
- optional behavior must be guarded carefully
- data lookups should only happen when the related feature is actually enabled
- environment-aware defaults sometimes need explicit overrides
That is exactly why I made DNS override-friendly in the production caller.
My Main Takeaway
Day 11 showed me that conditionals are not just small syntax tricks.
They are what make Terraform:
- reusable
- environment-aware
- safer
- easier to maintain
The biggest lessons for me were:
- keep conditionals in
locals - use
count = condition ? 1 : 0for optional resources - reference count-based resources safely with
[0]andnull - add validation blocks to fail early
- let one module adapt to multiple environments instead of duplicating code
That is what makes Terraform infrastructure dynamic and efficient.
Full Code
👉 https://github.com/mary20205090/30-day-Terraform-Challenge/tree/main/day_11
Follow My Journey
This is Day 11 of my 30-Day Terraform Challenge.
See you on Day 12 🚀
Top comments (0)