Serve DNS names with CoreDNS
Needed for two things: egress aliases (a client must dial api.corp.example, not
api.tailnet-system.svc, when the far end routes by TLS SNI / HTTP Host) and ingress names
(shop-shop-dev.prod-1.tailnet.example.internal for a TailnetIngress). The operator itself serves
no DNS. It renders a CoreDNS hosts
file into a ConfigMap; either
a TailnetGateway serves it as the zone (Run the gateway
; skip Serve the file
) or
you mount it into the resolver that is authoritative for those zones.
Enable rendering
kind: Tailnet
spec:
dns:
hostsConfigMap: tailnet-dns-hosts # operator namespace, key "hosts"
ingressNameTemplate: "{name}-{namespace}.prod-1.tailnet.example.internal"kubectl -n tailnet-system get cm tailnet-dns-hosts -o jsonpath='{.data.hosts}'
# # managed by tailnet-operator (Tailnet corp) — do not edit
# 10.43.12.7 api.corp.example
# 100.64.0.42 shop-shop-dev.prod-1.tailnet.example.internalKeep the ingress template one label below your gateway’s wildcard certificate: *.prod-1.<domain>
covers shop-shop-dev.prod-1.<domain>, not shop.shop-dev.prod-1.<domain>.
Serve the file
Mount the ConfigMap into the resolver for your tailnet zone (in a Headscale setup: the resolver your
split-DNS entry delegates prod-1.tailnet.example.internal to), optional: true so it starts before
the first render:
volumes:
- name: dns-hosts
configMap: { name: tailnet-dns-hosts, optional: true }
containers:
- name: coredns
volumeMounts: [{ name: dns-hosts, mountPath: /etc/tailnet-dns, readOnly: true }]prod-1.tailnet.example.internal:53 {
hosts /etc/tailnet-dns/hosts { fallthrough } # operator records win …
template IN A { answer "{{ .Name }} 30 IN A 10.0.0.80" } # … the gateway wildcard catches the rest
cache 30
reload
}
corp.example:53 { # zones with egress aliases
hosts /etc/tailnet-dns/hosts { fallthrough }
cache 30
forward . 10.0.0.53 { max_concurrent 1000 } # everything else: the site's DNS
reload
}Wire the cluster CoreDNS
Pods ask the cluster CoreDNS (kube-system). Forward the alias zones to your resolver first, with
the site DNS as sequential fallback so real corporate names keep resolving when the resolver is down:
corp.example:53 {
errors
cache 30
forward . 10.96.0.54 10.0.0.53 { # 10.96.0.54 = ClusterIP of your resolver Service
policy sequential
max_concurrent 1000
}
}Two details worth knowing: forward treats SERVFAIL as an answer (no failover, only unreachable
upstreams fail over), and a resolver without a route into the site network cannot use the site DNS
as upstream — forward that zone to a peer resolver inside the site over the tailnet instead
(forward . 100.64.0.3, a subnet router’s tailnet IP running the same block).
Freshness is ConfigMap propagation (≤ ~1 min) plus the hosts reload (5 s). If the operator dies,
the last file stays valid — the data path never depends on it.
Source: docs/how-to/dns-coredns.md at v0.18.0 — found a mistake? Tell us.