vault backup: 2026-06-24 17:52:15
This commit is contained in:
@@ -0,0 +1,679 @@
|
||||
---
|
||||
tags:
|
||||
- installation
|
||||
- dns
|
||||
- powerdns
|
||||
created: 2026-06-17
|
||||
---
|
||||
|
||||
# PowerDNS Docker Compose
|
||||
|
||||
## Purpose
|
||||
|
||||
This note describes how to install a PowerDNS Authoritative environment with:
|
||||
|
||||
- PostgreSQL 16
|
||||
- PowerDNS Authoritative 5.0.4
|
||||
- Poweradmin
|
||||
- pgweb
|
||||
- scheduled PostgreSQL backups
|
||||
|
||||
This installation model assumes:
|
||||
|
||||
1. DNS is exposed on host port `53`
|
||||
2. the PowerDNS API is only exposed on `127.0.0.1:8081`
|
||||
3. web management services are published through a reverse proxy such as Traefik
|
||||
|
||||
## Component versions
|
||||
|
||||
| Component | Version |
|
||||
| --- | --- |
|
||||
| PostgreSQL | `16` |
|
||||
| PowerDNS Authoritative | `5.0.4` |
|
||||
| Poweradmin | `stable` |
|
||||
| pgweb | `0.16.2` |
|
||||
| Backup container | `alpine:3.20` |
|
||||
|
||||
## Before you start
|
||||
|
||||
Make sure the target host has:
|
||||
|
||||
1. Linux
|
||||
2. Docker
|
||||
3. `docker compose`
|
||||
4. port `53/tcp` and `53/udp` available
|
||||
5. a reverse proxy network if Poweradmin and pgweb will be published through Traefik
|
||||
6. public DNS names for the web interfaces if they will be exposed externally
|
||||
|
||||
## Deployment values to prepare
|
||||
|
||||
Prepare all runtime values before starting the stack.
|
||||
|
||||
### Required core values
|
||||
|
||||
| Variable | Meaning |
|
||||
| --- | --- |
|
||||
| `PGUSER` | PostgreSQL administrative user |
|
||||
| `PGPASSWORD` | PostgreSQL administrative password |
|
||||
| `DB_NAME` | Main PowerDNS database |
|
||||
| `DB_USER` | Application database user |
|
||||
| `DB_PASS` | Application database password |
|
||||
| `PDNS_API_KEY` | PowerDNS API key |
|
||||
| `CRON_SCHEDULE` | Backup schedule |
|
||||
| `PA_SESSION_KEY` | Poweradmin session secret |
|
||||
| `PA_ADMIN_USERNAME` | Bootstrap Poweradmin admin username |
|
||||
| `PA_ADMIN_PASSWORD` | Bootstrap Poweradmin admin password |
|
||||
| `PA_ADMIN_EMAIL` | Bootstrap Poweradmin admin email |
|
||||
| `PA_ADMIN_FULLNAME` | Bootstrap Poweradmin admin full name |
|
||||
| `PGWEB_USER` | pgweb login username |
|
||||
| `PGWEB_PASS` | pgweb login password |
|
||||
|
||||
### Optional values with defaults
|
||||
|
||||
| Variable | Default | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `TZ` | `Asia/Shanghai` | Service timezone |
|
||||
| `DB_HOST` | `db` | Database hostname |
|
||||
| `DB_PORT` | `5432` | Database port |
|
||||
| `ADMIN_DB` | `pdnsadmin` | Admin database used for bootstrap and restore |
|
||||
| `RETENTION_DAYS` | `7` | Backup age retention |
|
||||
| `MAX_BACKUPS` | `7` | Number of backup sets to keep |
|
||||
| `DUMP_ROLES` | `true` | Include PostgreSQL role dump in backups |
|
||||
| `PDNS_VERSION` | `49` | Poweradmin PowerDNS compatibility mode |
|
||||
| `DNS_NS1` | `ns1.wsvc.info` | Default NS1 value in Poweradmin |
|
||||
| `DNS_NS2` | `ns2.wsvc.info` | Default NS2 value in Poweradmin |
|
||||
| `DNS_HOSTMASTER` | `hostmaster.wsvc.info` | Default SOA hostmaster |
|
||||
| `PA_APP_TITLE` | `Poweradmin` | Poweradmin UI title |
|
||||
| `PA_CREATE_ADMIN` | `1` | Enable bootstrap admin creation |
|
||||
|
||||
### Example values
|
||||
|
||||
Replace every placeholder with your own values:
|
||||
|
||||
```env
|
||||
TZ=Asia/Shanghai
|
||||
PGUSER=postgres
|
||||
PGPASSWORD=<strong-postgres-password>
|
||||
DB_HOST=db
|
||||
DB_PORT=5432
|
||||
DB_NAME=pdns
|
||||
DB_USER=pdns
|
||||
DB_PASS=<strong-app-password>
|
||||
ADMIN_DB=pdnsadmin
|
||||
CRON_SCHEDULE=0 3 * * *
|
||||
RETENTION_DAYS=7
|
||||
MAX_BACKUPS=7
|
||||
DUMP_ROLES=true
|
||||
PDNS_API_KEY=<strong-api-key>
|
||||
PA_SESSION_KEY=<long-random-session-key>
|
||||
PA_ADMIN_USERNAME=admin
|
||||
PA_ADMIN_PASSWORD=<strong-admin-password>
|
||||
PA_ADMIN_EMAIL=admin@example.com
|
||||
PA_ADMIN_FULLNAME=DNS Administrator
|
||||
PGWEB_USER=pgweb
|
||||
PGWEB_PASS=<strong-pgweb-password>
|
||||
PDNS_VERSION=49
|
||||
DNS_NS1=ns1.example.com
|
||||
DNS_NS2=ns2.example.com
|
||||
DNS_HOSTMASTER=hostmaster.example.com
|
||||
PA_APP_TITLE=Poweradmin
|
||||
PA_CREATE_ADMIN=1
|
||||
```
|
||||
|
||||
## Full configuration blocks
|
||||
|
||||
Use the following full configuration content as the installation baseline.
|
||||
|
||||
### Environment file
|
||||
|
||||
```env
|
||||
TZ=Asia/Shanghai
|
||||
PGUSER=postgres
|
||||
PGPASSWORD=<strong-postgres-password>
|
||||
DB_HOST=db
|
||||
DB_PORT=5432
|
||||
DB_NAME=pdns
|
||||
DB_USER=pdns
|
||||
DB_PASS=<strong-app-password>
|
||||
ADMIN_DB=pdnsadmin
|
||||
CRON_SCHEDULE=0 3 * * *
|
||||
RETENTION_DAYS=7
|
||||
MAX_BACKUPS=7
|
||||
DUMP_ROLES=true
|
||||
PDNS_API_KEY=<strong-api-key>
|
||||
PA_SESSION_KEY=<long-random-session-key>
|
||||
PA_ADMIN_USERNAME=admin
|
||||
PA_ADMIN_PASSWORD=<strong-admin-password>
|
||||
PA_ADMIN_EMAIL=admin@example.com
|
||||
PA_ADMIN_FULLNAME=DNS Administrator
|
||||
PGWEB_USER=pgweb
|
||||
PGWEB_PASS=<strong-pgweb-password>
|
||||
PDNS_VERSION=49
|
||||
DNS_NS1=ns1.example.com
|
||||
DNS_NS2=ns2.example.com
|
||||
DNS_HOSTMASTER=hostmaster.example.com
|
||||
PA_APP_TITLE=Poweradmin
|
||||
PA_CREATE_ADMIN=1
|
||||
```
|
||||
|
||||
### Docker Compose configuration
|
||||
|
||||
```yaml
|
||||
networks:
|
||||
frontend:
|
||||
name: traefik
|
||||
external: true
|
||||
|
||||
backend:
|
||||
internal: true
|
||||
|
||||
edge:
|
||||
|
||||
services:
|
||||
db:
|
||||
image: postgres:16
|
||||
container_name: pdns-db
|
||||
environment:
|
||||
POSTGRES_DB: postgres
|
||||
POSTGRES_USER: ${PGUSER:?missing PGUSER}
|
||||
POSTGRES_PASSWORD: ${PGPASSWORD:?missing PGPASSWORD}
|
||||
TZ: ${TZ:-Asia/Shanghai}
|
||||
PGTZ: ${TZ:-Asia/Shanghai}
|
||||
volumes:
|
||||
- dbdata:/var/lib/postgresql
|
||||
- ./db-init-generated:/docker-entrypoint-initdb.d:ro
|
||||
- ./backup:/backup:ro
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U \"$${POSTGRES_USER}\" -d \"$${POSTGRES_DB}\""]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
restart: unless-stopped
|
||||
networks: [backend, edge]
|
||||
|
||||
auth:
|
||||
image: powerdns/pdns-auth-50:5.0.4
|
||||
container_name: pdns-auth
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "53:53/udp"
|
||||
- "53:53/tcp"
|
||||
- "127.0.0.1:8081:8081"
|
||||
environment:
|
||||
PDNS_API_KEY: ${PDNS_API_KEY:?missing PDNS_API_KEY}
|
||||
DB_NAME: ${DB_NAME:?missing DB_NAME}
|
||||
DB_USER: ${DB_USER:?missing DB_USER}
|
||||
DB_PASS: ${DB_PASS:?missing DB_PASS}
|
||||
TEMPLATE_FILES: secrets
|
||||
volumes:
|
||||
- ./auth/pdns.conf:/etc/powerdns/pdns.conf:ro
|
||||
- ./auth/templates.d:/etc/powerdns/templates.d:ro
|
||||
- ./auth/keys:/var/lib/powerdns
|
||||
- ./auth/import:/import
|
||||
- ./auth/export:/export
|
||||
- ./auth/logs:/var/log/pdns
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD-SHELL",
|
||||
"python3 -c \"import json, os, urllib.request; req = urllib.request.Request('http://127.0.0.1:8081/api/v1/servers/localhost', headers={'X-API-Key': os.environ['PDNS_API_KEY']}); data = json.load(urllib.request.urlopen(req, timeout=3)); assert data['daemon_type'] == 'authoritative'\""
|
||||
]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
restart: unless-stopped
|
||||
networks: [backend, edge]
|
||||
|
||||
poweradmin:
|
||||
image: poweradmin/poweradmin:stable
|
||||
container_name: poweradmin
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
auth:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
DB_TYPE: pgsql
|
||||
DB_HOST: ${DB_HOST:-db}
|
||||
DB_PORT: ${DB_PORT:-5432}
|
||||
DB_NAME: ${DB_NAME:?missing DB_NAME}
|
||||
DB_USER: ${DB_USER:?missing DB_USER}
|
||||
DB_PASS: ${DB_PASS:?missing DB_PASS}
|
||||
PA_PDNS_API_URL: http://auth:8081
|
||||
PA_PDNS_API_KEY: ${PDNS_API_KEY:?missing PDNS_API_KEY}
|
||||
PA_DNS_BACKEND: sql
|
||||
PDNS_VERSION: ${PDNS_VERSION:-49}
|
||||
DNS_NS1: ${DNS_NS1:-ns1.wsvc.info}
|
||||
DNS_NS2: ${DNS_NS2:-ns2.wsvc.info}
|
||||
DNS_HOSTMASTER: ${DNS_HOSTMASTER:-hostmaster.wsvc.info}
|
||||
PA_APP_TITLE: ${PA_APP_TITLE:-Poweradmin}
|
||||
PA_TIMEZONE: ${TZ:-Asia/Shanghai}
|
||||
PA_SESSION_KEY: ${PA_SESSION_KEY:?missing PA_SESSION_KEY}
|
||||
PA_CREATE_ADMIN: ${PA_CREATE_ADMIN:-1}
|
||||
PA_ADMIN_USERNAME: ${PA_ADMIN_USERNAME:?missing PA_ADMIN_USERNAME}
|
||||
PA_ADMIN_PASSWORD: ${PA_ADMIN_PASSWORD:?missing PA_ADMIN_PASSWORD}
|
||||
PA_ADMIN_EMAIL: ${PA_ADMIN_EMAIL:?missing PA_ADMIN_EMAIL}
|
||||
PA_ADMIN_FULLNAME: ${PA_ADMIN_FULLNAME:?missing PA_ADMIN_FULLNAME}
|
||||
TRUSTED_PROXIES: private_ranges
|
||||
DEBUG: "false"
|
||||
restart: unless-stopped
|
||||
networks: [backend, frontend]
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=traefik"
|
||||
- "traefik.http.routers.poweradmin.rule=Host(`pdns.wsvc.info`)"
|
||||
- "traefik.http.routers.poweradmin.entrypoints=websecure"
|
||||
- "traefik.http.routers.poweradmin.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.poweradmin.loadbalancer.server.port=80"
|
||||
|
||||
backup:
|
||||
image: alpine:3.20
|
||||
container_name: pdns-backup
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
TZ: ${TZ:-Asia/Shanghai}
|
||||
DB_HOST: ${DB_HOST:-db}
|
||||
DB_PORT: ${DB_PORT:-5432}
|
||||
DB_USER: ${PGUSER:?missing PGUSER}
|
||||
DB_PASS: ${PGPASSWORD:?missing PGPASSWORD}
|
||||
DB_NAME: ${DB_NAME:?missing DB_NAME}
|
||||
RETENTION_DAYS: ${RETENTION_DAYS:-7}
|
||||
MAX_BACKUPS: ${MAX_BACKUPS:-7}
|
||||
DUMP_ROLES: ${DUMP_ROLES:-true}
|
||||
CRON_SCHEDULE: ${CRON_SCHEDULE:?missing CRON_SCHEDULE}
|
||||
volumes:
|
||||
- ./backup:/backup
|
||||
- ./scripts:/scripts:ro
|
||||
entrypoint: >
|
||||
sh -c '
|
||||
apk add --no-cache postgresql16-client tzdata util-linux bash coreutils findutils;
|
||||
ln -snf /usr/share/zoneinfo/$$TZ /etc/localtime && echo $$TZ > /etc/timezone;
|
||||
echo "$$DB_HOST:$$DB_PORT:*:$$DB_USER:$$DB_PASS" > /root/.pgpass;
|
||||
chmod 600 /root/.pgpass;
|
||||
echo "$$CRON_SCHEDULE flock -n /backup/.backup.lock /scripts/backup.sh >> /backup/backup.log 2>&1" > /etc/crontabs/root;
|
||||
echo "[$$(date -Iseconds)] cron started with schedule: $$CRON_SCHEDULE" >> /backup/backup.log;
|
||||
crond -f -l 8
|
||||
'
|
||||
restart: unless-stopped
|
||||
networks: [backend]
|
||||
|
||||
pgweb:
|
||||
image: sosedoff/pgweb:0.16.2
|
||||
container_name: pdns_pgweb
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
PGWEB_DATABASE_URL: "postgres://${PGUSER:?missing PGUSER}:${PGPASSWORD:?missing PGPASSWORD}@${DB_HOST:-db}:${DB_PORT:-5432}/${DB_NAME:?missing DB_NAME}?sslmode=disable"
|
||||
PGWEB_AUTH_USER: ${PGWEB_USER:?missing PGWEB_USER}
|
||||
PGWEB_AUTH_PASS: ${PGWEB_PASS:?missing PGWEB_PASS}
|
||||
TZ: ${TZ:-Asia/Shanghai}
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
networks: [backend, frontend]
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=traefik"
|
||||
- "traefik.http.routers.pgweb.rule=Host(`pgweb.wsvc.info`)"
|
||||
- "traefik.http.routers.pgweb.entrypoints=websecure"
|
||||
- "traefik.http.routers.pgweb.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.pgweb.loadbalancer.server.port=8081"
|
||||
|
||||
volumes:
|
||||
dbdata: {}
|
||||
```
|
||||
|
||||
### PowerDNS daemon configuration
|
||||
|
||||
```ini
|
||||
local-address=0.0.0.0
|
||||
local-port=53
|
||||
|
||||
launch=gpgsql
|
||||
gpgsql-host=db
|
||||
include-dir=/etc/powerdns/pdns.d
|
||||
gpgsql-dnssec=yes
|
||||
allow-axfr-ips=202.91.35.141
|
||||
also-notify=202.91.35.141
|
||||
primary=yes
|
||||
secondary=no
|
||||
|
||||
api=yes
|
||||
webserver=yes
|
||||
webserver-address=0.0.0.0
|
||||
webserver-port=8081
|
||||
webserver-allow-from=127.0.0.1,172.16.0.0/12,10.0.0.0/8,192.168.0.0/16
|
||||
|
||||
version-string=anonymous
|
||||
disable-syslog=yes
|
||||
loglevel=4
|
||||
|
||||
default-soa-edit=INCEPTION-INCREMENT
|
||||
default-soa-edit-signed=INCEPTION-INCREMENT
|
||||
|
||||
disable-axfr=no
|
||||
```
|
||||
|
||||
### Runtime-rendered secret template
|
||||
|
||||
```ini
|
||||
gpgsql-dbname={{ DB_NAME }}
|
||||
gpgsql-user={{ DB_USER }}
|
||||
gpgsql-password={{ DB_PASS }}
|
||||
api-key={{ PDNS_API_KEY }}
|
||||
```
|
||||
|
||||
## Network layout
|
||||
|
||||
The stack uses three networks:
|
||||
|
||||
| Network | Purpose |
|
||||
| --- | --- |
|
||||
| `backend` | internal service-to-service traffic |
|
||||
| `edge` | local host-facing DNS and API exposure |
|
||||
| `frontend` | reverse-proxy-facing web traffic |
|
||||
|
||||
Service attachment:
|
||||
|
||||
| Service | Networks |
|
||||
| --- | --- |
|
||||
| PostgreSQL | `backend`, `edge` |
|
||||
| PowerDNS auth | `backend`, `edge` |
|
||||
| Poweradmin | `backend`, `frontend` |
|
||||
| Backup | `backend` |
|
||||
| pgweb | `backend`, `frontend` |
|
||||
|
||||
## Installation procedure
|
||||
|
||||
### 1. Prepare the host
|
||||
|
||||
Install Docker and Docker Compose support on the Linux host.
|
||||
|
||||
If a reverse proxy network is required, create it before deployment.
|
||||
|
||||
### 2. Prepare runtime configuration
|
||||
|
||||
Set all required environment values.
|
||||
|
||||
At minimum, verify:
|
||||
|
||||
1. PostgreSQL admin credentials are defined
|
||||
2. application database credentials are defined
|
||||
3. PowerDNS API key is defined
|
||||
4. Poweradmin admin account values are defined
|
||||
5. pgweb login values are defined
|
||||
6. backup schedule and retention values are defined
|
||||
|
||||
### 3. Start the stack
|
||||
|
||||
Start the stack in detached mode.
|
||||
|
||||
Expected startup order:
|
||||
|
||||
1. PostgreSQL starts first
|
||||
2. database bootstrap creates the application role and databases
|
||||
3. PowerDNS starts after PostgreSQL is healthy
|
||||
4. Poweradmin starts after both PostgreSQL and PowerDNS are healthy
|
||||
5. backup service starts cron after PostgreSQL is healthy
|
||||
6. pgweb starts after PostgreSQL is healthy
|
||||
|
||||
### 4. Wait for health checks
|
||||
|
||||
The deployment should be considered ready only after:
|
||||
|
||||
1. PostgreSQL passes `pg_isready`
|
||||
2. PowerDNS API health check returns an authoritative daemon result
|
||||
3. Poweradmin and pgweb become reachable through the reverse proxy
|
||||
|
||||
### 5. Complete first access
|
||||
|
||||
After startup:
|
||||
|
||||
1. sign in to Poweradmin with the bootstrap admin account
|
||||
2. verify pgweb login works
|
||||
3. verify the PowerDNS API is reachable from the local host only
|
||||
4. verify DNS answers on port `53`
|
||||
|
||||
## Service configuration details
|
||||
|
||||
### PostgreSQL
|
||||
|
||||
| Setting | Value |
|
||||
| --- | --- |
|
||||
| Image | `postgres:16` |
|
||||
| Container name | `pdns-db` |
|
||||
| Restart policy | `unless-stopped` |
|
||||
| Startup database | `postgres` |
|
||||
| Health check | `pg_isready -U $POSTGRES_USER -d $POSTGRES_DB` |
|
||||
| Timezone | `TZ`, `PGTZ` |
|
||||
|
||||
Bootstrap behavior:
|
||||
|
||||
1. create the role named by `DB_USER` if missing
|
||||
2. update the password of `DB_USER` from `DB_PASS`
|
||||
3. create the database named by `DB_NAME` if missing
|
||||
4. create the database named by `ADMIN_DB` if missing
|
||||
5. assign ownership of both databases to `DB_USER`
|
||||
|
||||
### PowerDNS Authoritative
|
||||
|
||||
| Setting | Value |
|
||||
| --- | --- |
|
||||
| Image | `powerdns/pdns-auth-50:5.0.4` |
|
||||
| Container name | `pdns-auth` |
|
||||
| Restart policy | `unless-stopped` |
|
||||
| Published ports | `53/tcp`, `53/udp`, `127.0.0.1:8081` |
|
||||
| Health check | local API request using `PDNS_API_KEY` |
|
||||
|
||||
Environment used by PowerDNS:
|
||||
|
||||
| Variable | Meaning |
|
||||
| --- | --- |
|
||||
| `PDNS_API_KEY` | API authentication key |
|
||||
| `DB_NAME` | PowerDNS PostgreSQL database |
|
||||
| `DB_USER` | PowerDNS PostgreSQL user |
|
||||
| `DB_PASS` | PowerDNS PostgreSQL password |
|
||||
| `TEMPLATE_FILES=secrets` | enables runtime rendering of dynamic config |
|
||||
|
||||
Rendered runtime directives:
|
||||
|
||||
| Directive | Source |
|
||||
| --- | --- |
|
||||
| `gpgsql-dbname` | `DB_NAME` |
|
||||
| `gpgsql-user` | `DB_USER` |
|
||||
| `gpgsql-password` | `DB_PASS` |
|
||||
| `api-key` | `PDNS_API_KEY` |
|
||||
|
||||
Daemon behavior:
|
||||
|
||||
| Directive | Value | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `local-address` | `0.0.0.0` | listen on all container interfaces |
|
||||
| `local-port` | `53` | DNS listener port |
|
||||
| `launch` | `gpgsql` | PostgreSQL backend |
|
||||
| `gpgsql-host` | `db` | database service hostname |
|
||||
| `gpgsql-dnssec` | `yes` | DNSSEC enabled |
|
||||
| `api` | `yes` | API enabled |
|
||||
| `webserver` | `yes` | embedded web server enabled |
|
||||
| `webserver-address` | `0.0.0.0` | listen on all container interfaces |
|
||||
| `webserver-port` | `8081` | API port |
|
||||
| `webserver-allow-from` | `127.0.0.1,172.16.0.0/12,10.0.0.0/8,192.168.0.0/16` | restrict API access to local and private ranges |
|
||||
| `primary` | `yes` | primary DNS role enabled |
|
||||
| `secondary` | `no` | secondary role disabled |
|
||||
| `allow-axfr-ips` | `202.91.35.141` | allowed AXFR peer |
|
||||
| `also-notify` | `202.91.35.141` | notify destination |
|
||||
| `version-string` | `anonymous` | hide version string |
|
||||
| `disable-syslog` | `yes` | disable syslog |
|
||||
| `loglevel` | `4` | logging verbosity |
|
||||
| `default-soa-edit` | `INCEPTION-INCREMENT` | SOA serial update policy |
|
||||
| `default-soa-edit-signed` | `INCEPTION-INCREMENT` | SOA serial policy for signed zones |
|
||||
| `disable-axfr` | `no` | AXFR globally allowed if otherwise permitted |
|
||||
|
||||
### Poweradmin
|
||||
|
||||
| Setting | Value |
|
||||
| --- | --- |
|
||||
| Image | `poweradmin/poweradmin:stable` |
|
||||
| Container name | `poweradmin` |
|
||||
| Restart policy | `unless-stopped` |
|
||||
| Database type | `pgsql` |
|
||||
| API endpoint | `http://auth:8081` |
|
||||
| DNS backend mode | `sql` |
|
||||
| Trusted proxies | `private_ranges` |
|
||||
| Debug | `false` |
|
||||
|
||||
Important Poweradmin values:
|
||||
|
||||
| Variable | Value |
|
||||
| --- | --- |
|
||||
| `DB_HOST` | `db` by default |
|
||||
| `DB_PORT` | `5432` by default |
|
||||
| `DB_NAME` | required |
|
||||
| `DB_USER` | required |
|
||||
| `DB_PASS` | required |
|
||||
| `PA_PDNS_API_KEY` | same value as `PDNS_API_KEY` |
|
||||
| `PDNS_VERSION` | `49` |
|
||||
| `DNS_NS1` | configurable default nameserver |
|
||||
| `DNS_NS2` | configurable default nameserver |
|
||||
| `DNS_HOSTMASTER` | configurable default hostmaster |
|
||||
| `PA_APP_TITLE` | `Poweradmin` by default |
|
||||
| `PA_TIMEZONE` | value from `TZ` |
|
||||
| `PA_SESSION_KEY` | required |
|
||||
| `PA_CREATE_ADMIN` | `1` by default |
|
||||
| `PA_ADMIN_USERNAME` | required |
|
||||
| `PA_ADMIN_PASSWORD` | required |
|
||||
| `PA_ADMIN_EMAIL` | required |
|
||||
| `PA_ADMIN_FULLNAME` | required |
|
||||
|
||||
Reverse proxy routing:
|
||||
|
||||
| Item | Value |
|
||||
| --- | --- |
|
||||
| Router host | `pdns.wsvc.info` |
|
||||
| Entry point | `websecure` |
|
||||
| TLS resolver | `letsencrypt` |
|
||||
| Internal service port | `80` |
|
||||
|
||||
### Backup service
|
||||
|
||||
| Setting | Value |
|
||||
| --- | --- |
|
||||
| Image | `alpine:3.20` |
|
||||
| Container name | `pdns-backup` |
|
||||
| Restart policy | `unless-stopped` |
|
||||
| Trigger mode | cron inside the container |
|
||||
| Locking | `flock -n` |
|
||||
|
||||
Startup behavior:
|
||||
|
||||
1. install PostgreSQL client tools and required shell utilities
|
||||
2. set container timezone
|
||||
3. build `/root/.pgpass` for unattended database access
|
||||
4. write the cron job using `CRON_SCHEDULE`
|
||||
5. start cron in foreground mode
|
||||
|
||||
Backup variables:
|
||||
|
||||
| Variable | Value |
|
||||
| --- | --- |
|
||||
| `DB_HOST` | `db` by default |
|
||||
| `DB_PORT` | `5432` by default |
|
||||
| `DB_USER` | same as `PGUSER` |
|
||||
| `DB_PASS` | same as `PGPASSWORD` |
|
||||
| `DB_NAME` | required |
|
||||
| `RETENTION_DAYS` | `7` by default |
|
||||
| `MAX_BACKUPS` | `7` by default |
|
||||
| `DUMP_ROLES` | `true` by default |
|
||||
| `CRON_SCHEDULE` | required |
|
||||
|
||||
Backup behavior:
|
||||
|
||||
1. dump the configured application database
|
||||
2. optionally dump PostgreSQL roles
|
||||
3. remove stale temporary files
|
||||
4. remove zero-byte role dumps
|
||||
5. prune backups older than `RETENTION_DAYS`
|
||||
6. keep only the newest `MAX_BACKUPS` backup sets
|
||||
|
||||
Backup artifact patterns:
|
||||
|
||||
| Artifact | Pattern |
|
||||
| --- | --- |
|
||||
| Database dump | `pdns_YYYY-MM-DD_HH-MM-SS.sql.gz` |
|
||||
| Roles dump | `roles_YYYY-MM-DD_HH-MM-SS.sql` |
|
||||
|
||||
### pgweb
|
||||
|
||||
| Setting | Value |
|
||||
| --- | --- |
|
||||
| Image | `sosedoff/pgweb:0.16.2` |
|
||||
| Container name | `pdns_pgweb` |
|
||||
| Restart policy | `unless-stopped` |
|
||||
| Database URL mode | PostgreSQL DSN with `sslmode=disable` |
|
||||
| Login user | `PGWEB_USER` |
|
||||
| Login password | `PGWEB_PASS` |
|
||||
|
||||
Reverse proxy routing:
|
||||
|
||||
| Item | Value |
|
||||
| --- | --- |
|
||||
| Router host | `pgweb.wsvc.info` |
|
||||
| Entry point | `websecure` |
|
||||
| TLS resolver | `letsencrypt` |
|
||||
| Internal service port | `8081` |
|
||||
|
||||
## Port exposure summary
|
||||
|
||||
| Host binding | Container port | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `53/udp` | `53/udp` | DNS over UDP |
|
||||
| `53/tcp` | `53/tcp` | DNS over TCP |
|
||||
| `127.0.0.1:8081` | `8081` | local PowerDNS API |
|
||||
|
||||
## Post-install checks
|
||||
|
||||
After installation, verify all of the following:
|
||||
|
||||
1. PostgreSQL is healthy and reachable by dependent services
|
||||
2. PowerDNS answers on TCP and UDP port `53`
|
||||
3. the PowerDNS API responds on `127.0.0.1:8081`
|
||||
4. the API is not exposed on public interfaces
|
||||
5. Poweradmin login works with the configured admin account
|
||||
6. pgweb login works with the configured credentials
|
||||
7. scheduled backups are being created and rotated
|
||||
|
||||
## Restore note
|
||||
|
||||
Restore should be treated as destructive.
|
||||
|
||||
Expected restore flow:
|
||||
|
||||
1. terminate active connections to the target database
|
||||
2. optionally restore PostgreSQL roles
|
||||
3. drop the target database
|
||||
4. recreate the target database with `DB_USER` as owner
|
||||
5. import the selected SQL dump
|
||||
6. reassign schema ownership if needed
|
||||
|
||||
Restore control values:
|
||||
|
||||
| Variable | Meaning |
|
||||
| --- | --- |
|
||||
| `DB_NAME` | target database to recreate |
|
||||
| `DB_USER` | owner of the restored database |
|
||||
| `ADMIN_DB` | administrative database used during restore |
|
||||
| `ADMIN_USER` | administrative PostgreSQL user, default `postgres` |
|
||||
| `RESTORE_ROLES` | `auto`, `always`, or `never` |
|
||||
| `CONFIRM_RESTORE=YES` | skip confirmation prompt |
|
||||
| `ROLES_FILE` | explicit role dump file |
|
||||
|
||||
## Security notes
|
||||
|
||||
1. Keep live secrets out of static service config where possible.
|
||||
2. Bind the PowerDNS API only to loopback or trusted private ranges.
|
||||
3. Restrict AXFR and notify peers to trusted IP addresses only.
|
||||
4. Publish Poweradmin and pgweb through HTTPS only.
|
||||
5. Protect backup files so only intended operators can read them.
|
||||
Reference in New Issue
Block a user