
Every outage story starts the same way: “We just changed a DNS record.” The New Stack made the case recently that DNS is infrastructure and should be managed like it — and they’re right. Yet most SMBs still run DNS from a web console: a human clicks through a UI, saves a record, and hopes nothing breaks. There is no version history, no review, no rollback, and no audit trail. When a typo takes the site down, the fix is another frantic click.
DNS sits in front of everything you build: your website, your API, your email, your Kubernetes ingress. It is the first thing to break and the last thing you can debug, because every recursive resolver between you and your users caches whatever you published. The good news: treating DNS as code is cheap, fast, and well within reach of a lean DevOps team. Here is how to do it in 2026.
Why DNS Still Causes Outages at SMBs
DNS failures are not exotic. They happen for painfully mundane reasons:
- No rollback path. A record changed in the console cannot be reverted — you must remember what it was and type it back in.
- TTL mistakes. Long TTLs (86400s is still common) mean a bad record propagates for up to 24 hours before it expires, even after you fix it.
- No review. One engineer with console access can change production DNS with zero peer review, usually on a Friday afternoon.
- No inventory. Nobody knows which zones and records actually exist, so orphaned records point at decommissioned IPs and dead subdomains.
DNS also slows down deployments. Canary and blue-green releases often need weighted or switchable records; doing that by hand in a console is slow, error-prone, and impossible to reproduce. The moment you move DNS into version control, every one of these failure modes gets a mechanism, not a hope.
DNS as Code: Terraform, octoDNS, and dnscontrol
The fastest win for most SMBs is managing DNS with Terraform. You declare the zone and records, terraform plan shows exactly what will change, and terraform apply makes it so. A bad change is a git revert plus an apply — not a support ticket to your provider.
# dns.tf — Route 53 as code
resource "aws_route53_zone" "main" {
name = "example.com"
}
resource "aws_route53_record" "www" {
zone_id = aws_route53_zone.main.zone_id
name = "www.example.com"
type = "A"
ttl = 300
records = ["203.0.113.10"]
}
Keep TTLs short (300s) for records you change often, and reserve long TTLs for stable records like MX. If you already wrestle with Terraform state, read our guide on mastering Terraform state management without enterprise tooling before you add a hundred DNS resources to your state.
If you run multiple providers (say, Cloudflare in front of a Route 53 origin), tools like dnscontrol or octoDNS let you manage all zones from one declarative file and push to every provider at once:
// dnsconfig.js — dnscontrol example
var REG_NONE = NewRegistrar("none");
var CLOUDFLARE = NewDnsProvider("cloudflare");
D("example.com", REG_NONE, DnsProvider(CLOUDFLARE),
A("@", "203.0.113.10", TTL(300)),
CNAME("www", "example.com."),
MX("@", 10, "mail.example.com.")
);
Whichever tool you choose, the rule is the same: the source of truth is Git, not the provider console. That also makes DNS a natural fit for your GitOps workflow — see how lean DevOps teams run GitOps in 2026.
Kubernetes-Native DNS: ExternalDNS and CoreDNS Done Right
Inside Kubernetes, two DNS layers matter. CoreDNS resolves service names inside the cluster. The default installation works, but teams routinely hand-edit its ConfigMap for custom domains or stub zones — edits that silently disappear on the next cluster upgrade. Keep CoreDNS configuration in Git and apply it via your deployment pipeline instead.
The second layer is the one that bites SMBs: publishing public records for Services and Ingresses. ExternalDNS watches your cluster and syncs records to Route 53, Cloudflare, or any provider with an API. You annotate a Service and the record appears — no console clicks, no drift:
apiVersion: v1
kind: Service
metadata:
name: api
annotations:
external-dns.alpha.kubernetes.io/hostname: api.example.com
external-dns.alpha.kubernetes.io/ttl: "300"
spec:
type: LoadBalancer
ports:
- port: 443
targetPort: 443
helm repo add external-dns https://kubernetes-sigs.github.io/external-dns/
helm upgrade --install external-dns external-dns/external-dns \
--namespace external-dns --create-namespace \
--set provider=aws \
--set txtOwnerId=smb-prod \
--set domainFilters[0]=example.com
The txtOwnerId is not optional: ExternalDNS writes a TXT ownership record for every record it manages, so it never fights with records created by hand. That single flag prevents half the “who changed my DNS” incidents you will ever have.
DNSSEC, Split-Horizon, and Failover for Lean Teams
Reliability and security layers that used to be enterprise-only are now one checkbox or a few lines of config:
- DNSSEC. Enable it at your registrar or provider (Route 53, Cloudflare, and most registrars support one-click or Terraform-managed DNSSEC). It stops cache poisoning — attackers forging DNS responses to redirect your users. If you handle payments or hold customer data, this is table stakes in 2026.
- Split-horizon DNS. Internal users should resolve
api.example.comto the private IP; external users get the public one. Manage both views from the same IaC repository so they cannot drift. - Health-check failover. A Route 53 failover record with a health check routes traffic away from a dead origin in seconds, without anyone waking up:
resource "aws_route53_health_check" "primary" {
fqdn = "primary.example.com"
port = 443
type = "HTTPS"
failure_threshold = 3
}
resource "aws_route53_record" "app_failover" {
zone_id = aws_route53_zone.main.zone_id
name = "app.example.com"
type = "A"
set_identifier = "primary"
ttl = 60
failover_routing_policy {
type = "PRIMARY"
}
health_check_id = aws_route53_health_check.primary.id
records = ["203.0.113.10"]
}
Note the TTL of 60 seconds: failover only helps if clients re-resolve quickly. DNS failover is a core piece of any realistic disaster recovery story — see how SMBs can build DR that actually works.
A 30-Day DNS Hygiene Plan
You do not need a big project. You need a sequence:
- Days 1–7 — Inventory. Export every zone and record from every provider. Delete records that point at decommissioned IPs. Document who owns each zone.
- Days 8–14 — Move to code. Import the zones into Terraform (or dnscontrol) and put them in Git with branch protection. Enable provider-level change history.
- Days 15–21 — Automate. Deploy ExternalDNS in Kubernetes, set short TTLs on volatile records, and wire DNS changes into your CI/CD pipeline with
terraform planin pull requests. - Days 22–30 — Harden and verify. Enable DNSSEC, add a health-check failover record for your main app, and set up external monitoring that alerts when your apex domain stops resolving.
DNS is the cheapest infrastructure you own and the most visible when it fails. Managing it as code turns “who changed the record?” into git log — and turns Friday-afternoon DNS anxiety into a reviewed, reversible pull request.
Want a hand moving your DNS (and the rest of your infrastructure) into code? Book a free consultation and we will map out a 30-day plan for your stack.