Deploy PowerDNS Recursor using Docker containers with production-ready configurations. This guide covers deploying the Recursor for recursive DNS resolution.
NET_BIND_SERVICE capability, orPowerDNS uses versioned repository names on Docker Hub:
| Component | Repository | Example Tag |
|---|---|---|
| Recursor 5.3.x (stable) | powerdns/pdns-recursor-53 |
5.3.5 |
| Recursor 5.2.x | powerdns/pdns-recursor-52 |
5.2.5 |
| Recursor 5.1.x | powerdns/pdns-recursor-51 |
5.1.3 |
| Recursor 5.0.x | powerdns/pdns-recursor-50 |
5.0.8 |
| Recursor 4.9.x | powerdns/pdns-recursor-49 |
4.9.4 |
No latest tag exists - always specify explicit version tags.
version: '3.8'
services:
powerdns-recursor:
image: powerdns/pdns-recursor-53:5.3.5
container_name: powerdns-recursor
restart: unless-stopped
ports:
- "53:53/udp"
- "53:53/tcp"
cap_add:
- NET_BIND_SERVICE
environment:
# Allow queries from these networks (RFC1918 + localhost)
- RECURSOR_allow-from=127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
# Forward all queries to upstream resolvers
- RECURSOR_forward-zones=.=223.5.5.5,1.0.0.1
# Enable DNSSEC validation
- RECURSOR_dnssec=validate
# Web server for API/stats
- RECURSOR_webserver=yes
- RECURSOR_webserver-address=0.0.0.0
- RECURSOR_webserver-port=8082
- RECURSOR_webserver-password=your_secure_password_here
volumes:
- ./config:/etc/powerdns
- ./logs:/var/log/powerdns
networks:
- recursor-net
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8082/api/v1/servers/localhost"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
volumes:
recursor-data:
networks:
recursor-net:
driver: bridge
For different responses based on query source:
version: '3.8'
services:
powerdns-recursor:
image: powerdns/pdns-recursor-53:5.3.5
container_name: powerdns-recursor
restart: unless-stopped
ports:
- "53:53/udp"
- "53:53/tcp"
cap_add:
- NET_BIND_SERVICE
environment:
# Allow from all (be careful with open resolvers!)
- RECURSOR_allow-from=0.0.0.0/0,::/0
# Forward specific zones differently
- RECURSOR_forward-zones=example.com.=10.0.0.10,internal.local.=10.0.0.20
# Default forwarders for everything else
- RECURSOR_forward-zones-recurse=.=223.5.5.5,1.0.0.1
# DNSSEC validation
- RECURSOR_dnssec=validate
# Logging
- RECURSOR_loglevel=6
# Web server
- RECURSOR_webserver=yes
- RECURSOR_webserver-address=127.0.0.1
- RECURSOR_webserver-port=8082
- RECURSOR_webserver-password=your_secure_password_here
volumes:
- ./config:/etc/powerdns
- ./logs:/var/log/powerdns
networks:
- recursor-net
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8082/api/v1/servers/localhost"]
interval: 30s
timeout: 10s
retries: 3
version: '3.8'
services:
powerdns-recursor:
image: powerdns/pdns-recursor-53:5.3.5
container_name: powerdns-recursor
restart: unless-stopped
ports:
- "53:53/udp"
- "53:53/tcp"
cap_add:
- NET_BIND_SERVICE
environment:
- RECURSOR_allow-from=10.0.0.0/8,192.168.0.0/16
- RECURSOR_dnssec=validate
# RPZ configuration via file mount
volumes:
- ./config:/etc/powerdns
- ./rpz:/etc/powerdns/rpz
- ./logs:/var/log/powerdns
networks:
- recursor-net
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8082/api/v1/servers/localhost"]
interval: 30s
timeout: 10s
retries: 3
Create RPZ configuration file config/recursor.conf:
# RPZ configuration
rpz-file=/etc/powerdns/rpz/blocklist.txt
rpz-action-override=Drop
rpz-ip-override=0.0.0.0
Create blocklist rpz/blocklist.txt:
; Malware domains
malware.example.com CNAME .
; Ad domains
ads.example.com CNAME .
; Tracking domains
tracker.example.com CNAME .
PowerDNS Recursor Docker images support configuration via RECURSOR_ prefixed environment variables:
Network Settings:
RECURSOR_allow-from: Networks allowed to query (comma-separated CIDR)RECURSOR_allow-from-subnet-size: Maximum subnet size for allow-fromRECURSOR_local-address: IP addresses to listen onForwarding:
RECURSOR_forward-zones: Zones to forward specifically (e.g., example.com.=1.2.3.4)RECURSOR_forward-zones-recurse: Zones to forward with recursionRECURSOR_forward-zones-file: File containing forward zonesDNSSEC:
RECURSOR_dnssec: validate, process, log-fail, or offRECURSOR_trustanchors: Custom trust anchor filePerformance:
RECURSOR_threads: Number of worker threadsRECURSOR_cache-size: Maximum cache entriesRECURSOR_packetcache-max-size: Packet cache sizeLogging:
RECURSOR_loglevel: 0-9 (higher = more verbose)RECURSOR_quiet: yes to reduce loggingRECURSOR_log-common-troubles: yes to log common issuesWeb Server/API:
RECURSOR_webserver: yes/noRECURSOR_webserver-address: Web server bind addressRECURSOR_webserver-port: Web server port (default: 8082)RECURSOR_webserver-password: Web server password# Create project directory
mkdir -p /opt/powerdns-recursor
cd /opt/powerdns-recursor
# Create docker-compose.yml (copy from examples above)
# Start the service
docker compose up -d
# Check status
docker compose ps
# View logs
docker compose logs -f powerdns-recursor
# Test basic resolution
dig @localhost example.com
# Test DNSSEC validation
dig @localhost example.com +dnssec
# Test API endpoint
curl -H "X-API-Key: your_password" http://localhost:8082/api/v1/servers/localhost
# Check statistics
curl -H "X-API-Key: your_password" http://localhost:8082/api/v1/servers/localhost/statistics
Critical: Never allow recursion from untrusted networks without rate limiting:
environment:
# Only allow trusted networks
- RECURSOR_allow-from=10.0.0.0/8,192.168.0.0/16,127.0.0.0/8
# Rate limiting (queries per second per IP)
- RECURSOR_max-mthreads=2000
services:
powerdns-recursor:
# ... other settings ...
read_only: true
tmpfs:
- /tmp
- /var/run
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
user: "104:108" # pdns-recursor:pdns-recursor UIDs
security_opt:
- no-new-privileges:true
Enable metrics export for Prometheus:
environment:
- RECURSOR_webserver=yes
- RECURSOR_webserver-address=0.0.0.0
- RECURSOR_webserver-port=8082
Access metrics at http://localhost:8082/metrics
All examples include Docker health checks. Verify with:
docker compose ps
docker inspect --format='{{json .State.Health}}' powerdns-recursor | jq
# Backup configuration
docker cp powerdns-recursor:/etc/powerdns ./backup-powerdns-config
# Backup is automatic for Recursor (no persistent data)
# Pull latest image
docker compose pull
# Restart with new image
docker compose up -d
# Verify version
docker compose logs powerdns-recursor | grep "PowerDNS"
Port 53 already in use:
sudo ss -tulnp | grep :53
sudo systemctl stop systemd-resolved
Permission denied binding to port 53:
cap_add:
- NET_BIND_SERVICE
No DNSSEC validation:
dig @localhost dnssec-failed.org
# Should show SERVFAIL if validation working
API not accessible:
curl -H "X-API-Key: your_password" http://localhost:8082/api/v1/servers/localhost
For high availability:
version: '3.8'
services:
recursor-1:
image: powerdns/pdns-recursor-53:5.3.5
container_name: powerdns-recursor-1
ports:
- "53:53/udp"
- "53:53/tcp"
environment:
- RECURSOR_allow-from=10.0.0.0/8
- RECURSOR_dnssec=validate
networks:
- recursor-net
recursor-2:
image: powerdns/pdns-recursor-53:5.3.5
container_name: powerdns-recursor-2
ports:
- "5354:53/udp"
- "5354:53/tcp"
environment:
- RECURSOR_allow-from=10.0.0.0/8
- RECURSOR_dnssec=validate
networks:
- recursor-net
networks:
recursor-net:
driver: bridge
Deploying PowerDNS Recursor in containers for production? Our consulting covers:
Get expert help: office@linux-server-admin.com | Contact Page