DOMAIN:NETWORKING:DNS_MANAGEMENT¶
OWNER: stef UPDATED: 2026-03-24 SCOPE: all DNS operations for GE and client domains AGENTS: stef (primary), karel (CDN CNAME coordination), arjan (server IPs), leon (deploy chain) PROVIDER: TransIP (Netherlands-based registrar and DNS hosting)
DNS:OVERVIEW¶
PURPOSE: manage DNS records for all GE and client domains PROVIDER: TransIP (aequitas/transip Terraform provider) METHOD: Terraform only — NEVER manual TransIP console changes CREDENTIALS: Vault path admin-ui/api-keys/transip (piotr manages) PRIMARY_DOMAIN: growing-europe.com HOSTING_DOMAIN: hosting.growing-europe.com (client subdomains)
RULE: all DNS changes via Terraform (IaC, auditable, version-controlled) RULE: never register a new domain without human (Dirk-Jan) approval RULE: always verify DNS propagation after changes
DNS:RECORD_TYPES¶
A RECORD¶
PURPOSE: maps domain to IPv4 address USE_WHEN: pointing domain to a server IP
resource "transip_dns_entry" "app_a" {
domain = var.domain
name = var.subdomain # e.g., "app" for app.example.com, "@" for apex
type = "A"
content = var.server_ip # from arjan's provisioning output
expire = 300 # 5 min TTL — quick failover
}
VERIFY:
AAAA RECORD¶
PURPOSE: maps domain to IPv6 address USE_WHEN: server has IPv6 (UpCloud servers typically have both IPv4 and IPv6) TTL: 300s (same as A record)
CNAME RECORD¶
PURPOSE: maps domain to another domain (alias) USE_WHEN: CDN domains (pointing to BunnyCDN pull zone)
resource "transip_dns_entry" "cdn_cname" {
domain = var.domain
name = "cdn" # cdn.example.com
type = "CNAME"
content = "${var.pullzone}.b-cdn.net." # Karel provides pull zone name
expire = 300
}
FLOW: Karel creates pull zone -> tells Stef pull zone hostname -> Stef creates CNAME
RULE: CNAME cannot coexist with other record types on the same name RULE: apex domain (@) cannot use CNAME — use A record or ALIAS (if supported)
MX RECORD¶
PURPOSE: mail routing USE_WHEN: client domain needs email
resource "transip_dns_entry" "mx" {
domain = var.domain
name = "@"
type = "MX"
content = "10 mail.example.com."
expire = 86400 # 24h TTL — mail records are stable
}
TTL: 86400s (24 hours) — MX records rarely change, high TTL reduces DNS load
TXT RECORD¶
PURPOSE: domain verification, email authentication (SPF, DKIM, DMARC) USE_WHEN: email setup, domain ownership verification, Let's Encrypt DNS-01 challenge
# SPF record
resource "transip_dns_entry" "spf" {
domain = var.domain
name = "@"
type = "TXT"
content = "v=spf1 include:_spf.google.com ~all"
expire = 86400
}
# DKIM record
resource "transip_dns_entry" "dkim" {
domain = var.domain
name = "google._domainkey"
type = "TXT"
content = "v=DKIM1; k=rsa; p={public_key}"
expire = 86400
}
# DMARC record
resource "transip_dns_entry" "dmarc" {
domain = var.domain
name = "_dmarc"
type = "TXT"
content = "v=DMARC1; p=quarantine; rua=mailto:dmarc@${var.domain}"
expire = 86400
}
SRV RECORD¶
PURPOSE: service discovery (used by some protocols) USE_WHEN: rarely — only for services that use SRV discovery (e.g., SIP, XMPP) TTL: 3600s
CAA RECORD¶
PURPOSE: Certificate Authority Authorization — controls which CAs can issue certs for domain USE_WHEN: always for production domains
resource "transip_dns_entry" "caa" {
domain = var.domain
name = "@"
type = "CAA"
content = "0 issue \"letsencrypt.org\""
expire = 86400
}
RULE: CAA records should restrict issuance to Let's Encrypt only (GE standard CA) EXCEPTION: client-provided certs from other CAs — add their CA to CAA
DNS:TTL_STRATEGY¶
| Record Type | TTL | Rationale |
|---|---|---|
| A / AAAA | 300s (5 min) | Quick failover during incidents or migrations |
| CNAME | 300s (5 min) | CDN changes need fast propagation |
| MX | 86400s (24h) | Mail servers rarely change |
| TXT (SPF/DKIM/DMARC) | 86400s (24h) | Email auth records are stable |
| TXT (verification) | 300s (5 min) | Temporary records, remove after verification |
| CAA | 86400s (24h) | CA policy rarely changes |
| NS | 86400s (24h) | Nameserver delegation is stable |
PRE_MIGRATION_TTL: - Before planned migration, reduce A/CNAME TTL to 60s (24 hours in advance) - After migration complete and verified, restore to standard TTL - REASON: low TTL ensures clients resolve to new IP quickly during cutover
TOOL: terraform
TIMELINE:
T-24h: reduce TTL to 60s via Terraform apply
T-0: perform migration, update A record to new IP
T+1h: verify all traffic on new IP
T+24h: restore TTL to 300s
DNS:PROPAGATION¶
CHECKING_PROPAGATION¶
TOOL: dig
RUN: dig {domain} @ns1.transip.net # authoritative nameserver
RUN: dig {domain} @8.8.8.8 # Google Public DNS
RUN: dig {domain} @1.1.1.1 # Cloudflare DNS
RUN: dig {domain} @9.9.9.9 # Quad9 DNS
IF_STALE_CACHE: - TTL determines how long resolvers cache records - After updating, wait at least 1x TTL for most resolvers to refresh - Some ISP resolvers ignore TTL (violation of RFC) — nothing GE can do about that - For critical cutover, monitor propagation across multiple resolvers
PROPAGATION_TIMELINE¶
| TTL | ~50% propagated | ~95% propagated |
|---|---|---|
| 60s | ~1 minute | ~5 minutes |
| 300s | ~5 minutes | ~15 minutes |
| 3600s | ~1 hour | ~4 hours |
| 86400s | ~12 hours | ~48 hours |
DNS:MULTI_DOMAIN_MANAGEMENT¶
DOMAIN_ORGANIZATION¶
growing-europe.com # GE corporate domain
office.growing-europe.com # admin-ui
wiki.growing-europe.com # wiki brain
hosting.growing-europe.com # client hosting base domain
{client}.hosting.growing-europe.com # per-client subdomain
{client-domain}.com # client's own domain (managed by stef)
www.{client-domain}.com # client website
app.{client-domain}.com # client application
api.{client-domain}.com # client API
cdn.{client-domain}.com # CDN (CNAME to BunnyCDN)
PER_CLIENT_DNS_SETUP¶
ON_NEW_CLIENT_DOMAIN:
1. Client provides domain name
2. Stef verifies domain ownership (TXT record challenge)
3. Client points nameservers to TransIP (if GE manages DNS)
OR client creates delegation records (if client keeps DNS control)
4. Stef creates standard record set:
→ A record: server IP (from arjan/rutger)
→ CNAME: cdn.{domain} -> {pullzone}.b-cdn.net (from karel)
→ TXT: SPF, DKIM, DMARC (if email needed)
→ CAA: restrict to letsencrypt.org
5. Stef triggers cert-manager for TLS certificate (DNS-01 challenge)
6. Verify all records resolving correctly
DNS:LET_S_ENCRYPT_VALIDATION¶
DNS_01_CHALLENGE¶
PURPOSE: prove domain ownership for TLS certificate issuance METHOD: cert-manager creates _acme-challenge TXT record via TransIP API ADVANTAGE: works for wildcard certificates, does not require inbound HTTP
FLOW:
1. cert-manager requests certificate from Let's Encrypt
2. Let's Encrypt returns challenge token
3. cert-manager webhook creates TXT record:
_acme-challenge.{domain} TXT "{token}"
4. Let's Encrypt verifies TXT record exists
5. Certificate issued
6. cert-manager removes challenge TXT record
TRANSIP_WEBHOOK: cert-manager uses TransIP webhook for DNS-01 challenge automation CREDENTIAL: TransIP API key in Vault (piotr manages)
IF_CHALLENGE_FAILS: 1. CHECK: is TransIP API key valid? (Vault path admin-ui/api-keys/transip) 2. CHECK: does the domain use TransIP nameservers? 3. CHECK: is there a CAA record blocking Let's Encrypt? 4. CHECK: cert-manager logs for specific error
TOOL: kubectl
RUN: kubectl logs -l app=cert-manager -n cert-manager --tail=50
RUN: kubectl get challenges -A
RUN: kubectl describe challenge {name} -n {namespace}
DNS:DNSSEC¶
STATUS¶
CURRENT: TransIP supports DNSSEC — can be enabled per domain GE_POLICY: enable DNSSEC for all production client domains
WHAT_DNSSEC_DOES¶
- Signs DNS responses with cryptographic keys
- Prevents DNS spoofing and cache poisoning
- Chain of trust from root DNS servers to domain
- Does NOT encrypt queries (that is DNS-over-HTTPS/TLS)
ENABLING_DNSSEC¶
METHOD: TransIP manages DNSSEC keys (KSK + ZSK)
ENABLE: via TransIP API or Terraform
VERIFY:
TOOL: dig
RUN: dig {domain} +dnssec
EXPECT: RRSIG records in response, AD (Authenticated Data) flag set
PITFALL: enabling DNSSEC with incorrect key configuration can make domain unreachable FIX: always test with dig +dnssec before and after enabling RULE: enable DNSSEC during initial setup, not mid-production
DNS:ORPHAN_RECORD_CHECK¶
WEEKLY_AUDIT (Stef, Monday 7am)¶
PURPOSE: detect DNS records pointing to decommissioned servers (dangling DNS)
CHECK:
1. List all A records from TransIP
2. For each A record, verify server is responsive:
TOOL: curl
RUN: curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 https://{domain}
3. For each CNAME, verify target exists:
TOOL: dig
RUN: dig {cname_target} +short
4. FLAG: any record pointing to unreachable IP or non-existent CNAME
5. ALERT: orphan records are a security risk (subdomain takeover)
SECURITY_RISK: dangling CNAME to deprovisioned CDN/cloud service = subdomain takeover vulnerability VICTORIA: notified of any dangling records for security assessment
DNS:DEPLOY_CHAIN_POSITION¶
STEF'S ROLE IN DEPLOY CHAIN¶
... → Rutger (prod k8s applied) →
STEF:
1. Create/update DNS records for new deployment
2. Verify TLS certificate valid and terminating
3. Apply/verify production firewall rules
4. Confirm network ready to Leon
→ Karel (CDN purge + edge config) →
→ Leon (cutover confirmed)
DNS:AGENT_WORKFLOW¶
FOR_STEF¶
ON_DNS_TASK: 1. READ this page for DNS standards 2. DETERMINE record type needed 3. WRITE Terraform config (never manual console) 4. RUN terraform plan — verify TTL, record value 5. RUN terraform apply 6. VERIFY: dig {domain} @ns1.transip.net 7. VERIFY: dig {domain} @8.8.8.8 (propagation) 8. HAND OFF to leon (deploy chain) or confirmation to requesting agent
ON_WEEKLY_AUDIT (Monday 7am):¶
- List all DNS records
- Check for orphan records
- Report findings
- Remove orphans (with approval)
DNS:ANTI_PATTERNS¶
BEFORE_EVERY_DNS_ACTION: 1. Am I making manual TransIP console changes? (NEVER — Terraform only) 2. Am I registering a new domain without human approval? (NEVER) 3. Am I creating wildcard DNS? (check with victoria first) 4. Am I setting TTL too high for records that may need quick changes? (A/CNAME: 300s max) 5. Am I setting TTL too low for stable records? (MX/TXT: 86400s appropriate) 6. Am I leaving dangling CNAME records? (security risk — subdomain takeover) 7. Am I skipping propagation verification? (NEVER — always verify with dig) 8. Am I touching CDN configuration? (Karel's domain, Stef only manages origin DNS)
DNS:CROSS_REFERENCES¶
TLS_CERTIFICATES: domains/networking/tls-certificates.md — DNS-01 challenge for cert issuance CDN_EDGE: domains/networking/cdn-edge.md — CNAME records for BunnyCDN pull zones NETWORK_SECURITY: domains/networking/network-security.md — firewall and network configuration TERRAFORM_PATTERNS: domains/infrastructure/terraform-patterns.md — TransIP provider config DEPLOYMENT_STRATEGIES: domains/infrastructure/deployment-strategies.md — DNS changes during deploy