Deploy Recursor, Authoritative, and PowerAdmin

Series navigation


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:

  1. Clients query the Recursor on port 53.
  2. The Recursor forwards internal zones to the Authoritative server on port 5353.
  3. 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:

  1. Create the PowerAdmin database and user.
  2. Import the PowerAdmin schema.
  3. 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.d via include-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

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.