Smart DNS: Auto-Detecting Route53 Records in Terraform
Every Route53 record in plain Terraform means a zone lookup, an explicit type, and for load balancers - a separate data source just to wire the alias.
data "aws_route53_zone" "this" {
name = "api.example.com."
}
data "aws_lb" "api" {
name = "api-nlb"
}
resource "aws_route53_record" "api" {
zone_id = data.aws_route53_zone.this.zone_id
name = "api.example.com"
type = "A"
alias {
name = data.aws_lb.api.dns_name
zone_id = data.aws_lb.api.zone_id
evaluate_target_health = true
}
}
With a module that infers everything, the same record becomes:
inputs = {
name = "api.example.com"
records = "api-nlb-1234567890.elb.eu-west-1.amazonaws.com"
}
zone auto-detection
name = "api.example.com"
│
└─ strip first label
│
▼
"example.com" ──► aws_route53_zone lookup
The module splits the FQDN, drops the first label, queries Route53 for the hosted zone. No zone_id from the caller.
For apex records or non-standard cases, zone_override skips this.
type inference
records value
│
├─ type_override set? ───────────────► use that type (NS/MX/TXT/etc.)
│
├─ is map?
│ ├─ has "name" key? ────────────► alias A (lookup LB by name)
│ └─ has "tags" key? ────────────► alias A (lookup LB by tags)
│
├─ is string or list?
│ ├─ all values are IPv4? ───────► A
│ ├─ ends with .amazonaws.com? ──► alias A (lookup LB by hostname)
│ └─ else ───────────────────────► CNAME
│
└─ (nothing created if no branch matches)
Each branch maps to a separate aws_route53_record resource using for_each over a conditional set - only one fires per invocation.
lb alias lookup modes
| Mode | Input | How |
|---|---|---|
| By hostname | records = "my-nlb-xxx.amazonaws.com" |
parse LB name from hostname prefix |
| By name | records = { nlb = { name = "my-nlb" } } |
aws_lb data source by name |
| By tags | records = { alb = { tags = {...} } } |
aws_lb data source by tag filter |
All three produce an alias block with evaluate_target_health = true.
escape hatches
| Scenario | Solution |
|---|---|
| NS delegation | type_override = "NS" |
| MX at zone apex |
type_override = "MX" + zone_override = "example.com"
|
| TXT verification | type_override = "TXT" |
| Private hosted zone | private_zone = true |
| Custom TTL | ttl = 300 |
the idea
DNS configuration is mostly mechanical - you know the hostname or IP address(es), you know what it points to. The type, the zone, whether it needs an alias block - those are derivable. This module pushes that derivation into Terraform so callers don't repeat it.
The tradeoff is explicitness. Plain Terraform is verbose, but every field is visible and validated. The module is terse, but inference can surprise you - especially the records = "..." string that silently becomes an alias lookup against AWS instead of a literal CNAME value.
It works best when:
- you manage many records across consistent naming conventions
- all your LBs follow predictable hostname patterns (
*.<region>.elb.amazonaws.com) - your zones map cleanly to second-level domains (one zone per domain, no split-horizon edge cases)
It gets awkward when:
- you need fine-grained TTL control per record
- you have private hosted zones with the same name as public ones (need
private_zone = trueconsistently) - your LB names are not stable - tag-based lookup is more resilient there
trade-off
records is type = any. Terraform cannot validate at plan time. A typo in a map key (naam instead of name) silently falls through to CNAME instead of alias. Review plan output before apply - the wrong record type shows up as a different resource being created.
full example
# CNAME
inputs = {
name = "api.example.com"
records = "old-api.example.com"
}
# A record
inputs = {
name = "api.example.com"
records = ["203.0.113.10", "203.0.113.11"]
}
# Alias to NLB by hostname
inputs = {
name = "api.example.com"
records = "api-nlb-1234567890.elb.eu-west-1.amazonaws.com"
}
# Alias to ALB by tags
inputs = {
name = "api.example.com"
records = {
alb = {
tags = {
Name = "api-alb"
Env = "prod"
}
}
}
}
# NS delegation with zone override
inputs = {
name = "example.com"
records = ["ns1.example.net", "ns2.example.net"]
type_override = "NS"
zone_override = "example.com"
}
Originally published at https://bard.sh/posts/route53-auto-detecting-terraform/
Top comments (0)