Deploy PowerDNS using Docker containers with production-ready configurations. This guide covers deploying the Authoritative Server with various backends.
NET_BIND_SERVICE capability, orDocker containers cannot bind to privileged ports (<1024) by default. Choose one of these options:
Option A: Use port mapping (Recommended)
ports:
- "53:53/udp"
- "53:53/tcp"
Run Docker with --cap-add=NET_BIND_SERVICE or as root.
Option B: Use non-privileged port
ports:
- "5353:5353/udp"
- "5353:5353/tcp"
Then configure PowerDNS with PDNS_local-port=5353.
Option C: Add capability in Docker Compose
cap_add:
- NET_BIND_SERVICE
PowerDNS uses versioned repository names on Docker Hub:
| Component | Repository | Current Stable | LTS Track |
|---|---|---|---|
| Authoritative 5.0.x | powerdns/pdns-auth-50 |
5.0.3 | - |
| Authoritative 4.9.x | powerdns/pdns-auth-49 |
4.9.13 | ✓ |
| Authoritative 4.8.x | powerdns/pdns-auth-48 |
4.8.3 | - |
| Recursor 5.3.x | powerdns/pdns-recursor-53 |
5.3.5 | - |
| Recursor 5.2.x | powerdns/pdns-recursor-52 |
5.2.5 | - |
| DNSdist 2.0.x | powerdns/dnsdist-20 |
2.0.2 | - |
| DNSdist 1.8.x | powerdns/dnsdist-18 |
1.8.3 | ✓ |
No latest tag exists - always specify explicit version tags.
Note: As of February 2026, PowerDNS 5.0.3 and 4.9.13 are the latest releases (2026-02-20).
For new deployments, use the latest 5.0.x series:
version: '3.8'
services:
powerdns:
image: powerdns/pdns-auth-50:5.0.3
container_name: powerdns-auth-50
restart: unless-stopped
ports:
- "53:53/udp"
- "53:53/tcp"
- "8081:8081" # Web server/API
cap_add:
- NET_BIND_SERVICE
environment:
- PDNS_AUTH_API_KEY=your_secure_api_key_here
- PDNS_webserver=yes
- PDNS_webserver-address=0.0.0.0
- PDNS_webserver-port=8081
- PDNS_webserver-password=your_secure_web_password
- PDNS_launch=gmysql
- PDNS_gmysql-host=mysql
- PDNS_gmysql-user=pdns
- PDNS_gmysql-password=pdns_password
- PDNS_gmysql-dbname=pdns
- PDNS_default-soa-name=a.misconfigured.dns.server.example.com
- PDNS_default-soa-mail=hostmaster.example.com
volumes:
- ./logs:/var/log/pdns
depends_on:
mysql:
condition: service_healthy
networks:
- pdns-net
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8081/api/v1/servers/localhost"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
mysql:
image: mysql:8.0
container_name: powerdns-mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: root_password
MYSQL_DATABASE: pdns
MYSQL_USER: pdns
MYSQL_PASSWORD: pdns_password
volumes:
- mysql-data:/var/lib/mysql
- ./init:/docker-entrypoint-initdb.d
networks:
- pdns-net
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "pdns", "-ppdns_password"]
interval: 10s
timeout: 5s
retries: 5
volumes:
mysql-data:
networks:
pdns-net:
driver: bridge
Create a project directory and configuration files:
mkdir -p /opt/powerdns/{config,data,init}
cd /opt/powerdns
Create a docker-compose.yml file:
version: '3.8'
services:
powerdns:
image: powerdns/pdns-auth-49:4.9.13
container_name: powerdns-auth
restart: unless-stopped
ports:
- "53:53/udp"
- "53:53/tcp"
- "8081:8081" # Web server/API
cap_add:
- NET_BIND_SERVICE
environment:
- PDNS_AUTH_API_KEY=your_secure_api_key_here
- PDNS_webserver=yes
- PDNS_webserver-address=0.0.0.0
- PDNS_webserver-port=8081
- PDNS_webserver-password=your_secure_web_password
- PDNS_launch=gmysql
- PDNS_gmysql-host=mysql
- PDNS_gmysql-user=pdns
- PDNS_gmysql-password=pdns_password
- PDNS_gmysql-dbname=pdns
- PDNS_default-soa-name=a.misconfigured.dns.server.example.com
- PDNS_default-soa-mail=hostmaster.example.com
volumes:
- ./logs:/var/log/pdns
depends_on:
mysql:
condition: service_healthy
networks:
- pdns-net
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8081/api/v1/servers/localhost"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
mysql:
image: mysql:8.0
container_name: powerdns-mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: root_password
MYSQL_DATABASE: pdns
MYSQL_USER: pdns
MYSQL_PASSWORD: pdns_password
volumes:
- mysql-data:/var/lib/mysql
- ./init:/docker-entrypoint-initdb.d
networks:
- pdns-net
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "pdns", "-ppdns_password"]
interval: 10s
timeout: 5s
retries: 5
volumes:
mysql-data:
networks:
pdns-net:
driver: bridge
Create the initial database schema file:
# Download schema from GitHub (recommended)
curl -fsSL https://raw.githubusercontent.com/PowerDNS/pdns/master/modules/gmysqlbackend/schema.mysql.sql > init/schema.sql
# Or extract from Docker image
docker run --rm powerdns/pdns-auth-49:4.9.13 cat /usr/share/doc/pdns/schema.mysql.sql > init/schema.sql
For PostgreSQL backend, use this docker-compose.yml:
version: '3.8'
services:
powerdns:
image: powerdns/pdns-auth-49:4.9.13
container_name: powerdns-auth
restart: unless-stopped
ports:
- "53:53/udp"
- "53:53/tcp"
- "8081:8081"
cap_add:
- NET_BIND_SERVICE
environment:
- PDNS_AUTH_API_KEY=your_secure_api_key_here
- PDNS_webserver=yes
- PDNS_webserver-address=0.0.0.0
- PDNS_webserver-port=8081
- PDNS_webserver-password=your_secure_web_password
- PDNS_launch=gpgsql
- PDNS_gpgsql-host=postgres
- PDNS_gpgsql-user=pdns
- PDNS_gpgsql-password=pdns_password
- PDNS_gpgsql-dbname=pdns
- PDNS_gpgsql-port=5432
- PDNS_default-soa-name=a.misconfigured.dns.server.example.com
- PDNS_default-soa-mail=hostmaster.example.com
volumes:
- ./logs:/var/log/pdns
depends_on:
postgres:
condition: service_healthy
networks:
- pdns-net
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8081/api/v1/servers/localhost"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
postgres:
image: postgres:15-alpine
container_name: powerdns-postgres
restart: unless-stopped
environment:
POSTGRES_DB: pdns
POSTGRES_USER: pdns
POSTGRES_PASSWORD: pdns_password
volumes:
- postgres-data:/var/lib/postgresql/data
- ./init:/docker-entrypoint-initdb.d
networks:
- pdns-net
healthcheck:
test: ["CMD-SHELL", "pg_isready -U pdns -d pdns"]
interval: 10s
timeout: 5s
retries: 5
volumes:
postgres-data:
networks:
pdns-net:
driver: bridge
Create the PostgreSQL schema:
# Download schema from GitHub (recommended)
curl -fsSL https://raw.githubusercontent.com/PowerDNS/pdns/master/modules/gpgsqlbackend/schema.pgsql.sql > init/schema.sql
# Or extract from Docker image
docker run --rm powerdns/pdns-auth-49:4.9.13 cat /usr/share/doc/pdns/schema.pgsql.sql > init/schema.sql
For a lightweight setup using SQLite:
version: '3.8'
services:
powerdns:
image: powerdns/pdns-auth-49:4.9.13
container_name: powerdns-auth
restart: unless-stopped
ports:
- "53:53/udp"
- "53:53/tcp"
- "8081:8081"
cap_add:
- NET_BIND_SERVICE
environment:
- PDNS_AUTH_API_KEY=your_secure_api_key_here
- PDNS_webserver=yes
- PDNS_webserver-address=0.0.0.0
- PDNS_webserver-port=8081
- PDNS_webserver-password=your_secure_web_password
- PDNS_launch=gsqlite3
- PDNS_gsqlite3-database=/var/lib/powerdns/pdns.sqlite3
- PDNS_default-soa-name=a.misconfigured.dns.server.example.com
- PDNS_default-soa-mail=hostmaster.example.com
volumes:
- sqlite-data:/var/lib/powerdns
- ./logs:/var/log/pdns
networks:
- pdns-net
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8081/api/v1/servers/localhost"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
volumes:
sqlite-data:
networks:
pdns-net:
driver: bridge
Start the PowerDNS container with your chosen backend:
# Navigate to your project directory
cd /opt/powerdns
# Start the services in detached mode
docker compose up -d
# Check the status
docker compose ps
# View logs
docker compose logs -f powerdns
PowerDNS Docker images support configuration via environment variables. The official images use the PDNS_ prefix with underscores replacing special characters. Common variables include:
Core Settings:
PDNS_local-address: IP addresses to listen on (default: 0.0.0.0)PDNS_local-port: Port to listen on (default: 53)PDNS_daemon: Run as daemon (default: no in Docker)API and Web Server:
PDNS_AUTH_API_KEY: Enables API and sets API key (also enables webserver)PDNS_webserver: Enable web server (yes/no)PDNS_webserver-address: Web server IP addressPDNS_webserver-port: Web server port (default: 8081)PDNS_webserver-password: Web server passwordBackend Configuration:
PDNS_launch: Backend to launch (gmysql, gpgsql, gsqlite3)PDNS_gmysql-host: MySQL hostPDNS_gmysql-user: MySQL userPDNS_gmysql-password: MySQL passwordPDNS_gmysql-dbname: MySQL database namePDNS_gpgsql-host: PostgreSQL hostPDNS_gpgsql-user: PostgreSQL userPDNS_gpgsql-password: PostgreSQL passwordPDNS_gpgsql-dbname: PostgreSQL database namePDNS_gpgsql-port: PostgreSQL port (default: 5432)PDNS_gsqlite3-database: SQLite database file pathDNS Settings:
PDNS_default-soa-name: Default SOA namePDNS_default-soa-mail: Default SOA emailPDNS_enable-dnssec: Enable DNSSEC (yes/no)Note: The official PowerDNS Docker images convert
PDNS_*environment variables to configuration options by replacing_with-and converting to lowercase. For example,PDNS_local-addressbecomeslocal-addressin the config.
For more complex configurations, mount a custom config file:
# Create config directory
mkdir -p config
# Create custom pdns.conf
cat > config/pdns.conf << EOF
# Custom PowerDNS configuration
local-address=0.0.0.0
local-port=53
launch=gsqlite3
gsqlite3-database=/var/lib/powerdns/pdns.sqlite3
api=yes
api-key=your_secure_api_key_here
webserver=yes
webserver-address=0.0.0.0
webserver-port=8081
webserver-password=your_secure_web_password
enable-dnssec=yes
EOF
Then modify your docker-compose.yml to mount the config:
powerdns:
# ... other settings ...
volumes:
- ./config/pdns.conf:/etc/powerdns/pdns.conf:ro
- sqlite-data:/var/lib/powerdns
- ./logs:/var/log/pdns
Use specific image tags instead of latest:
# Use versioned tags for reproducibility
image: powerdns/pdns-auth-49:4.9.13
# Available tags (as of February 2026):
# - 4.9.13 (latest stable)
# - 4.9.12, 4.9.11, 4.9.9, 4.9.8, etc.
# - latest (points to most recent)
Secure API and web server passwords:
# Generate strong passwords
openssl rand -hex 32 # for API key (64 characters)
openssl rand -base64 32 # for web password
Limit network exposure:
# Restrict web server to internal access only
PDNS_webserver-address=127.0.0.1
# Or bind to specific internal interface
PDNS_webserver-address=10.0.0.1
Use Docker secrets or environment files for sensitive data:
# docker-compose.yml
services:
powerdns:
env_file:
- .env.powerdns
# ... rest of config
# .env.powerdns (add to .gitignore)
PDNS_AUTH_API_KEY=super_secret_key_here
PDNS_webserver-password=super_secret_password_here
Add NET_BIND_SERVICE capability for port 53:
cap_add:
- NET_BIND_SERVICE
Enable Prometheus metrics for monitoring:
powerdns:
# ... other settings ...
environment:
- PDNS_api=yes
- PDNS_api-key=your_api_key
- PDNS_webserver=yes
- PDNS_webserver-address=0.0.0.0
- PDNS_webserver-port=8081
- PDNS_webserver-password=your_web_password
- PDNS_statistics-ringbuffer-size=10000
# Expose metrics endpoint
ports:
- "53:53/udp"
- "53:53/tcp"
- "8081:8081" # Metrics and API
Set up regular backups for your database:
# For MySQL backend
docker exec powerdns-mysql mysqldump -u pdns -p pdns > backup_$(date +%Y%m%d_%H%M%S).sql
# For PostgreSQL backend
docker exec powerdns-postgres pg_dump -U pdns pdns > backup_$(date +%Y%m%d_%H%M%S).sql
# For SQLite backend
docker cp powerdns-auth:/var/lib/powerdns/pdns.sqlite3 backup_$(date +%Y%m%d_%H%M%S).sqlite3
To update to a newer version:
# Pull the latest image
docker compose pull
# Stop containers
docker compose down
# Start with new images
docker compose up -d
# Verify the update
docker compose logs powerdns | grep "PowerDNS"
Tip: Always backup your database before updating. Test updates in a staging environment first.
All Docker Compose examples in this guide include health checks. You can verify container health with:
# Check health status
docker compose ps
# View health check logs
docker inspect --format='{{json .State.Health}}' powerdns-auth | jq
Port 53 already in use or Permission denied binding to port 53:
# Check what's using port 53
sudo ss -tulnp | grep :53
# Stop systemd-resolved if needed
sudo systemctl stop systemd-resolved
# If getting "Permission denied" error, add NET_BIND_SERVICE capability:
# In docker-compose.yml, add:
# cap_add:
# - NET_BIND_SERVICE
Database connection errors:
# Check database logs
docker compose logs mysql # or postgres
# Verify database is healthy
docker compose ps
# Test connection from PowerDNS container
docker exec powerdns-auth wget -q -O- http://mysql:3306 || echo "Connection failed"
Container won’t start:
# Check container logs
docker compose logs powerdns
# Check for configuration errors
docker compose run --rm powerdns pdns_server --config
API not responding:
# Test API endpoint
curl -H "X-API-Key: your_api_key" http://localhost:8081/api/v1/servers/localhost
# Check if webserver is enabled
docker compose logs powerdns | grep -i webserver
Schema not loaded:
# Verify schema was loaded
docker compose exec mysql mysql -u pdns -p -e "SHOW TABLES;" pdns
# If empty, manually load schema
docker exec -i powerdns-mysql mysql -u pdns -ppdns_password pdns < init/schema.sql
To run multiple PowerDNS components (Authoritative, Recursor, DNSdist):
version: '3.8'
services:
dnsdist:
image: powerdns/dnsdist-1.8:1.8.3
container_name: powerdns-dnsdist
restart: unless-stopped
ports:
- "53:53/udp"
- "53:53/tcp"
- "8083:8083" # Web interface
cap_add:
- NET_BIND_SERVICE
volumes:
- ./dnsdist.conf:/etc/dnsdist/dnsdist.conf:ro
networks:
- pdns-net
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8083/"]
interval: 30s
timeout: 10s
retries: 3
recursor:
image: powerdns/pdns-recursor-49:4.9.4
container_name: powerdns-recursor
restart: unless-stopped
environment:
- RECURSOR_webserver=yes
- RECURSOR_webserver-address=0.0.0.0
- RECURSOR_webserver-port=8082
- RECURSOR_webserver-password=recursor_password
networks:
- pdns-net
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8082/api/v1/servers/localhost"]
interval: 30s
timeout: 10s
retries: 3
authoritative:
image: powerdns/pdns-auth-49:4.9.13
container_name: powerdns-auth
restart: unless-stopped
environment:
- PDNS_AUTH_API_KEY=your_api_key
- PDNS_launch=gsqlite3
- PDNS_gsqlite3-database=/var/lib/powerdns/pdns.sqlite3
- PDNS_local-port=5300 # Different port internally
volumes:
- sqlite-data:/var/lib/powerdns
networks:
- pdns-net
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8081/api/v1/servers/localhost"]
interval: 30s
timeout: 10s
retries: 3
volumes:
sqlite-data:
networks:
pdns-net:
driver: bridge
Note: Image tags as of February 2026:
powerdns/dnsdist-1.8: Latest stable dnsdist 1.8.x seriespowerdns/pdns-recursor-49: Latest stable recursor 4.9.x seriespowerdns/pdns-auth-49: Latest stable authoritative 4.9.x series
Running PowerDNS in containers for production? We help with:
Need help? office@linux-server-admin.com or Contact Us