Build the lab yourself
The lab runs on any machine with Docker, including an Apple Silicon Mac, in well under a gigabyte of memory. The four DNS servers run real systemd, so every systemctl, journalctl, pdnsutil and pdns_control command in this lesson works exactly as on a Debian server.
- Put the files from this page in one directory, with
ns1.conf,ns2.conf,rootns.conf,recursor.confandlab-root.hintsfrom earlier in the lesson. docker compose -f lab-compose.yaml up -d --build, then./lab-setup.shto address every node.- On each authoritative server, create the database, copy its configuration into
/etc/powerdns/pdns.d/(docker compose cp), and restartpdns. Then follow the lesson: zones on ns1, secondary zones on ns2, the root andnetzones on rootns, then DNSSEC. - The recursor's trust anchor must be your lab root's DS, from
pdnsutil export-zone-ds .on rootns, because the keys are generated fresh in every lab. - Query from
client, which uses the recursor, and fromoutsider, which isn't allowed to.
notify-test.sh is the script that timed NOTIFY. It runs from the lab directory on your own machine.
# PowerDNS on Debian: the lab. docker compose -f lab-compose.yaml up -d --build && ./lab-setup.sh
name: pdns-lab
x-systemd: &systemd # Debian with systemd as PID 1
privileged: true
cgroup: host
volumes: ["/sys/fs/cgroup:/sys/fs/cgroup:rw"]
tmpfs: ["/run", "/run/lock"]
x-host: &host
build: { context: ., dockerfile: Dockerfile.host }
image: ttr-dns-host:13
privileged: true
services:
rootns: { <<: *systemd, build: { context: ., dockerfile: Dockerfile.auth }, image: ttr-pdns-auth:13, hostname: rootns, networks: { internet: { interface_name: eth0 } } }
ns1: { <<: *systemd, build: { context: ., dockerfile: Dockerfile.auth }, image: ttr-pdns-auth:13, hostname: ns1, networks: { internet: { interface_name: eth0 } } }
ns2: { <<: *systemd, build: { context: ., dockerfile: Dockerfile.auth }, image: ttr-pdns-auth:13, hostname: ns2, networks: { internet: { interface_name: eth0 } } }
recursor: { <<: *systemd, build: { context: ., dockerfile: Dockerfile.recursor }, image: ttr-pdns-rec:13, hostname: recursor, networks: { office: { interface_name: eth0 } } }
gw: { <<: *host, hostname: gw, networks: { office: { interface_name: eth0, priority: 100 }, internet: { interface_name: eth1 } } }
client: { <<: *host, hostname: client, networks: { office: { interface_name: eth0 } } }
outsider: { <<: *host, hostname: outsider, networks: { internet: { interface_name: eth0 } } }
# Layer-2-only networks: every address and route comes from lab-setup.sh.
networks:
internet: { enable_ipv4: false, enable_ipv6: true, ipam: { config: [{ subnet: "fd00:71::/64" }] } }
office: { enable_ipv4: false, enable_ipv6: true, ipam: { config: [{ subnet: "fd00:72::/64" }] } }#!/bin/sh
# Address every node and give it its routes. Run after `docker compose up`.
x() { docker compose -f lab-compose.yaml exec -T "$1" sh -c "$2"; }
for n in rootns ns1 ns2 recursor gw client outsider; do x $n 'sysctl -qw net.ipv6.conf.all.disable_ipv6=1'; done
# The "internet": a lab root server and the company's two authoritative servers.
x rootns 'ip addr add 203.0.113.1/24 dev eth0; ip route add 10.10.0.0/24 via 203.0.113.254'
x ns1 'ip addr add 203.0.113.53/24 dev eth0; ip route add 10.10.0.0/24 via 203.0.113.254'
x ns2 'ip addr add 203.0.113.54/24 dev eth0; ip route add 10.10.0.0/24 via 203.0.113.254'
x outsider 'ip addr add 203.0.113.99/24 dev eth0; ip route add 10.10.0.0/24 via 203.0.113.254'
x gw 'ip addr add 10.10.0.1/24 dev eth0; ip addr add 203.0.113.254/24 dev eth1; sysctl -qw net.ipv4.ip_forward=1'
# The office: the recursor and a client that uses it.
x recursor 'ip addr add 10.10.0.53/24 dev eth0; ip route add default via 10.10.0.1'
x client 'ip addr add 10.10.0.20/24 dev eth0; ip route add default via 10.10.0.1; echo "nameserver 10.10.0.53" > /etc/resolv.conf'# A PowerDNS Authoritative Server: Debian 13 with systemd, so pdns runs as a normal service.
FROM debian:trixie-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
systemd systemd-sysv pdns-server pdns-backend-sqlite3 sqlite3 \
bind9-dnsutils curl iproute2 iputils-ping procps less \
&& rm -rf /var/lib/apt/lists/* \
&& systemctl mask systemd-modules-load.service
CMD ["/sbin/init"]# The PowerDNS Recursor that office clients use: Debian 13 with systemd.
FROM debian:trixie-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
systemd systemd-sysv pdns-recursor bind9-dnsutils iproute2 iputils-ping procps less \
&& rm -rf /var/lib/apt/lists/* \
&& systemctl mask systemd-modules-load.service
CMD ["/sbin/init"]# Clients and the router: Debian 13 with dig.
FROM debian:trixie-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
bind9-dnsutils iproute2 iputils-ping procps \
&& rm -rf /var/lib/apt/lists/*
CMD ["sleep", "infinity"]# /etc/powerdns/pdns.d/rootns.conf: the lab's own root and .net servers.
launch=gsqlite3
gsqlite3-database=/var/lib/powerdns/pdns.sqlite3
gsqlite3-dnssec=yes
local-address=203.0.113.1Where next
- Linux Network ConnectivityPrerequisite · Beginner · 80 min
Find out why a Linux machine can't reach something, layer by layer: interfaces and addresses, routes and ip route get, ARP and the neighbour table, ping and traceroute, DNS, and listening sockets — with real output from a three-host lab.
- Office gateway: DHCP, DNS and VRRP on VyOSAlso covers DNS · Beginner · 40 min
Two VyOS routers serving one office LAN: VRRP for a gateway that survives a router failure, DHCP with split pools and a printer reservation, and DNS forwarding. Then a real failover, the DNS outage it exposed, and the fix.
- OpenVPN on Debian: users, routing and revocationAlso covers debian · Intermediate · 60 min
Install and run an OpenVPN server on Debian with your own certificate authority: one certificate per user, fixed addresses, routing to an office and a branch LAN, split and full tunnels, tls-crypt, revoking users, and the mistakes that lock everyone out.
- WireGuard on Debian: users, routing and keysAlso covers debian · Intermediate · 50 min
Run a WireGuard server on Debian for several users, an office LAN and a branch: keys, wg-quick and systemd, AllowedIPs as routing and access control, split and full tunnels, roaming and keepalives, adding and removing users live, and the silent failures.