A root to recurse from
A recursive resolver starts every lookup at the root of the DNS, so the lab needs a root to start from. The real root and TLD servers aren't reachable from a Docker lab, and you'd never want a lab to touch them anyway, so one more PowerDNS server, rootns, plays both parts.
It hosts two zones, built with the same pdnsutil commands as before:
- The root zone,
., which delegatesnetto the TLD server. - The
netzone, which delegatesexample.netto ns1 and ns2. Because those servers' names are insideexample.netitself, thenetzone also carries their addresses (glue), or no resolver could ever find them.
The server names use .test, a top-level domain reserved for testing, so nothing in the lab pretends to be a real root server.
Asked about www.example.net without recursion, the lab root does exactly what the real net servers would: it doesn't answer, but refers the client to ns1 and ns2, with their glue addresses in the additional section. Following those referrals from the root down is precisely what the recursor will do.
root@rootns:~# pdnsutil create-zone . ns.root.test >/dev/null && pdnsutil replace-rrset . @ SOA 86400 "ns.root.test. hostmaster.root.test. 2026102101 1800 900 604800 86400" >/dev/null && pdnsutil add-record . ns.root.test A 203.0.113.1 >/dev/null && pdnsutil add-record . net NS ns.tld.test >/dev/null && pdnsutil add-record . ns.tld.test A 203.0.113.1 >/dev/null; pdnsutil create-zone net ns.tld.test >/dev/null && pdnsutil replace-rrset net @ SOA 86400 "ns.tld.test. hostmaster.tld.test. 2026102101 1800 900 604800 86400" >/dev/null && pdnsutil add-record net example NS ns1.example.net >/dev/null && pdnsutil add-record net example NS ns2.example.net >/dev/null && pdnsutil add-record net ns1.example A 203.0.113.53 >/dev/null && pdnsutil add-record net ns2.example A 203.0.113.54 >/dev/null; systemctl restart pdns
Creating empty zone '.'
Creating empty zone 'net'
root@rootns:~# pdnsutil list-zone .; pdnsutil list-zone net
$ORIGIN .
. 3600 IN NS ns.root.test.
. 86400 IN SOA ns.root.test hostmaster.root.test 2026102101 1800 900 604800 86400
net 3600 IN NS ns.tld.test.
ns.root.test 3600 IN A 203.0.113.1
ns.tld.test 3600 IN A 203.0.113.1
$ORIGIN .
example.net 3600 IN NS ns1.example.net.
example.net 3600 IN NS ns2.example.net.
net 3600 IN NS ns.tld.test.
net 86400 IN SOA ns.tld.test hostmaster.tld.test 2026102101 1800 900 604800 86400
ns1.example.net 3600 IN A 203.0.113.53
ns2.example.net 3600 IN A 203.0.113.54
root@client:~# dig @203.0.113.1 www.example.net +norecurse
; <<>> DiG 9.20.29-1~deb13u1-Debian <<>> @203.0.113.1 www.example.net +norecurse
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 6561
;; flags: qr; QUERY: 1, ANSWER: 0, AUTHORITY: 2, ADDITIONAL: 3
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;www.example.net. IN A
;; AUTHORITY SECTION:
example.net. 3600 IN NS ns1.example.net.
example.net. 3600 IN NS ns2.example.net.
;; ADDITIONAL SECTION:
ns2.example.net. 3600 IN A 203.0.113.54
ns1.example.net. 3600 IN A 203.0.113.53
;; Query time: 1 msec
;; SERVER: 203.0.113.1#53(203.0.113.1) (UDP)
;; WHEN: Wed Oct 21 05:44:13 UTC 2026
;; MSG SIZE rcvd: 112- Lines 7–9: The root: its own nameserver, and a delegation of net.
- Lines 10–11: Addresses for the root and net servers (both 203.0.113.1 in the lab).
- Lines 13–14: The net zone delegates example.net to ns1 and ns2…
- Lines 17–18: …with glue: their addresses, which live inside the zone being delegated.
- Lines 26–27: No answer (aa isn't set), but authority…
- Lines 35–40: …a referral to ns1 and ns2, with their glue addresses.