Series navigation
- Part 1 (this post): Deploy Recursor + Authoritative + PowerAdmin
- Part 2: Operations and troubleshooting
- Part 3: AXFR workflows and the BIND backend
Goal and scope
This series documents an internal DNS stack running in Docker for my homelab:
- PowerDNS Recursor as the client-facing resolver (port 53)
- PowerDNS Authoritative serving internal zones (port 5353)
- PowerAdmin as the management UI (port 80)
This setup is internal-only. It is not meant for public authoritative DNS.
Architecture overview
The most important design decision is that the authoritative server is not on port 53. Instead it listens on port 5353 because it is “behind” the recursor. This allows using the recursor for both internal and external lookups while keeping the roles clean and avoiding port conflicts on the host.
High-level request flow:
- Clients query the Recursor on port 53.
- The Recursor forwards internal zones to the Authoritative server on port 5353.
- External domains are resolved by the Recursor via recursion (root name servers).
As the underlying OS Debian 12 is used, but anything which is capable of running Docker will do fine.
The database is a MariaDB Galera Cluster which is located elsewhere. Requests to the central database are load balanced to prevent outages due to server maintenance.
Docker Compose
Deploy the following Compose stack on each DNS node (for example dns01 and dns02). The important part is the port split: Recursor on 53, Authoritative on 5353.
Update the IP address you bind to based on your hosts IP.
name: site-dns
services:
pdns-auth:
image: powerdns/pdns-auth-48:latest
environment:
TZ: Europe/Zurich
volumes:
- /srv/powerdns/pdns-auth/config/:/etc/powerdns/:ro
- /srv/powerdns/pdns-auth/supermaster/:/var/lib/powerdns/:rw
ports:
- 10.26.2.53:5353:53/tcp
- 10.26.2.53:5353:53/udp
networks:
- svc
restart: unless-stopped
pdns-recursor:
image: powerdns/pdns-recursor-53:latest
environment:
TZ: Europe/Zurich
volumes:
- /srv/powerdns/pdns-recursor/config/:/etc/powerdns/:ro
ports:
- 10.26.2.53:53:53/tcp
- 10.26.2.53:53:53/udp
networks:
- svc
restart: unless-stopped
poweradmin:
image: poweradmin/poweradmin:4.0.4
environment:
DB_TYPE: mysql
DB_HOST: <db-host-or-vip>
DB_USER: <db-user>
DB_PASS: <db-pass>
DB_NAME: <poweradmin-db>
DNS_NS1: dns01.example.com
DNS_NS2: dns02.example.com
DNS_HOSTMASTER: hostmaster.example.com
PA_CREATE_ADMIN: yes
PA_CONFIG_PATH: /config/custom.php
PA_ADMIN_USERNAME: admin
PA_ADMIN_PASSWORD: change-me
PA_ADMIN_EMAIL: hostmaster@example.com
PA_PDNS_DB_NAME: <pdns-db>
TZ: Europe/Zurich
volumes:
- /srv/powerdns/poweradmin/config.php:/config/custom.php:ro
ports:
- 10.26.2.53:80:80
networks:
- web
restart: unless-stopped
networks:
svc:
driver: bridge
web:
driver: bridge
Why PowerAdmin is published on port 80
PowerAdmin is published on port 80 internally because access restrictions are handled by a central reverse proxy. This keeps the container setup simple while maintaining a single, consistent access-control point.
PowerAdmin: initialize the schema first
PowerAdmin requires the database schema to exist before the container is started. The schema is provided by PowerAdmin itself and can be imported by the admin. Please see the official documentation on how this is done.
In short:
- Create the PowerAdmin database and user.
- Import the PowerAdmin schema.
- Start the container.
Recursor configuration (forward internal zones, recurse for external)
The Recursor is the single entry point for clients. Internal zones are forwarded to both authoritative nodes (redundancy), and everything else is resolved via recursion.
Example (adapt IPs to your environment):
incoming:
listen:
- 0.0.0.0
recursor:
include_dir: /etc/powerdns/recursor.d
root_nx_trust: false
nothing_below_nxdomain: no
forward_zones:
- zone: "site.internal"
forwarders:
- 10.26.2.53:5353
- 10.26.2.54:5353
- zone: "other.internal"
forwarders:
- 10.46.2.53
- 10.46.2.54
dnssec:
validation: process-no-validate
log_bogus: true
logging:
loglevel: 7
quiet: false
Authoritative configuration (what matters here)
Your authoritative template makes two things clear:
- It sets a default SOA template (rename values for your environment).
- It loads backend snippets from
/etc/powerdns/pdns.dviainclude-dir.
Example:
default-soa-content=site-dns.srv.site.internal. hostmaster.example.com 0 10800 3600 604800 3600
default-soa-edit=INCEPTION-INCREMENT
include-dir=/etc/powerdns/pdns.d
Verification checklist
Run these checks from a client network:
- External recursion through the Recursor:
dig @10.26.2.53 example.com A
- Internal zone forwarding through the Recursor:
dig @10.26.2.53 host1.site.internal A
- Direct authoritative debug query:
dig @10.26.2.53 -p 5353 host1.site.internal A
Conclusion
This deployment keeps roles clean: clients query the Recursor on port 53, and internal zones are served by the Authoritative server on port 5353 behind it. In Part 2, I will focus on proving redundancy and covering the most relevant troubleshooting steps.
Next in series
Continue with Part 2: Operations and troubleshooting.
All configuration files and scripts for this post are available here: https://git.spacewars.ch/blog/public
