docker:docker-compose:powerdns
PowerDNS
Single instance - Master only
- Create network for powerdns
docker network create --ipv4 -d bridge \ --gateway 192.168.156.1 \ --subnet 192.168.156.0/24 \ powerdns_default
- pdns/pdns.conf
local-address=0.0.0.0,:: local-port=53 launch=gpgsql gpgsql-host=db gpgsql-port=5432 gpgsql-dbname=pdns gpgsql-user=pdns gpgsql-password=pdns_strong_password # Optional but recommended if you will use DNSSEC with this backend: gpgsql-dnssec=yes # The docs note DNSSEC is enabled for this backend by setting gpgsql-dnssec. [6](https://doc.powerdns.com/authoritative/backends/generic-postgresql.html) include-dir=/etc/powerdns/pdns.d # Good container defaults daemon=no disable-syslog=yes loglevel=4 trusted-notification-proxy=192.168.156.11 allow-axfr-ips=192.168.156.11
- pdns.d/api.conf
# Enable API + webserver webserver=yes api=yes # Bind API on all interfaces (because it's a container), but RESTRICT who can reach it: webserver-address=0.0.0.0 webserver-port=8081 webserver-allow-from=127.0.0.1/32,::1/128,192.168.0.0/16 # Authentication api-key=change_me_long_random_key webserver-password=change_me_another_password
- docker-compose.yaml
services: db: image: postgres:16 container_name: pdns-db environment: POSTGRES_DB: pdns POSTGRES_USER: pdns POSTGRES_PASSWORD: pdns_strong_password networks: powerdns_default: ipv4_address: 192.168.156.9 volumes: - pdns_pgdata:/var/lib/postgresql/data - ./postgres-init:/docker-entrypoint-initdb.d:ro healthcheck: test: ["CMD-SHELL", "pg_isready -U pdns -d pdns"] interval: 5s timeout: 3s retries: 20 pdns: image: powerdns/pdns-auth-49 container_name: pdns-auth depends_on: db: condition: service_healthy ports: - "127.0.0.1:5300:5300/tcp" - "127.0.0.1:5300:5300/udp" - "192.168.22.8:8089:8081/tcp" networks: powerdns_default: ipv4_address: 192.168.156.10 volumes: - ./pdns/pdns.conf:/etc/powerdns/pdns.conf:ro - ./pdns.d:/etc/powerdns/pdns.d:rw restart: unless-stopped recursor: image: powerdns/pdns-recursor-55 container_name: pdns-recursor depends_on: - pdns ports: - "192.168.22.8:53:53/udp" - "192.168.22.8:53:53/tcp" networks: powerdns_default: ipv4_address: 192.168.156.11 command: - "--local-address=0.0.0.0" - "--local-port=53" - "--allow-from=192.168.22.0/24,127.0.0.1/32,192.168.156.0/24" - "--forward-zones=example.com=192.168.156.10:53" - "--forward-zones-recurse=.=8.8.8.8;1.1.1.1" - "--dont-query=127.0.0.0/8,::1/128" - "--log-common-errors=yes" restart: unless-stopped volumes: pdns_pgdata: networks: powerdns_default: external: true
- database schema.sql
CREATE TABLE domains ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, master VARCHAR(128) DEFAULT NULL, last_check INT DEFAULT NULL, type TEXT NOT NULL, notified_serial BIGINT DEFAULT NULL, account VARCHAR(40) DEFAULT NULL, options TEXT DEFAULT NULL, catalog TEXT DEFAULT NULL, CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT))) ); CREATE UNIQUE INDEX name_index ON domains(name); CREATE INDEX catalog_idx ON domains(catalog); CREATE TABLE records ( id BIGSERIAL PRIMARY KEY, domain_id INT DEFAULT NULL, name VARCHAR(255) DEFAULT NULL, type VARCHAR(10) DEFAULT NULL, content VARCHAR(65535) DEFAULT NULL, ttl INT DEFAULT NULL, prio INT DEFAULT NULL, disabled BOOL DEFAULT 'f', ordername VARCHAR(255), auth BOOL DEFAULT 't', CONSTRAINT domain_exists FOREIGN KEY(domain_id) REFERENCES domains(id) ON DELETE CASCADE, CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT))) ); CREATE INDEX rec_name_index ON records(name); CREATE INDEX nametype_index ON records(name,type); CREATE INDEX domain_id ON records(domain_id); CREATE INDEX recordorder ON records (domain_id, ordername text_pattern_ops); CREATE TABLE supermasters ( ip INET NOT NULL, nameserver VARCHAR(255) NOT NULL, account VARCHAR(40) NOT NULL, PRIMARY KEY(ip, nameserver) ); CREATE TABLE comments ( id SERIAL PRIMARY KEY, domain_id INT NOT NULL, name VARCHAR(255) NOT NULL, type VARCHAR(10) NOT NULL, modified_at INT NOT NULL, account VARCHAR(40) DEFAULT NULL, comment VARCHAR(65535) NOT NULL, CONSTRAINT domain_exists FOREIGN KEY(domain_id) REFERENCES domains(id) ON DELETE CASCADE, CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT))) ); CREATE INDEX comments_domain_id_idx ON comments (domain_id); CREATE INDEX comments_name_type_idx ON comments (name, type); CREATE INDEX comments_order_idx ON comments (domain_id, modified_at); CREATE TABLE domainmetadata ( id SERIAL PRIMARY KEY, domain_id INT REFERENCES domains(id) ON DELETE CASCADE, kind VARCHAR(32), content TEXT ); CREATE INDEX domainidmetaindex ON domainmetadata(domain_id); CREATE TABLE cryptokeys ( id SERIAL PRIMARY KEY, domain_id INT REFERENCES domains(id) ON DELETE CASCADE, flags INT NOT NULL, active BOOL, published BOOL DEFAULT TRUE, content TEXT ); CREATE INDEX domainidindex ON cryptokeys(domain_id); CREATE TABLE tsigkeys ( id SERIAL PRIMARY KEY, name VARCHAR(255), algorithm VARCHAR(50), secret VARCHAR(255), CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT))) ); CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm);
- Load the schema
docker exec -it pdns-db psql -U pdns -d pdns ***PASTE the SCHEMA ABOVE*** #Show the tables: pdns=# \dt List of relations Schema | Name | Type | Owner --------+----------------+-------+------- public | comments | table | pdns public | cryptokeys | table | pdns public | domainkeys | table | pdns public | domainmetadata | table | pdns public | domains | table | pdns public | records | table | pdns public | supermasters | table | pdns public | tsigkeys | table | pdns (8 rows)
- add entries
docker compose exec pdns pdnsutil create-zone example.com ns1.example.com docker compose exec pdns pdnsutil add-record example.com example.com NS ns2.example.com docker compose exec pdns pdnsutil add-record example.com example.com NS ns3.example.com docker compose exec pdns pdnsutil replace-rrset example.com '' SOA 3600 "ns1.example.com. hostmaster.example.com. 1 10800 3600 604800 3600" docker compose exec pdns pdnsutil add-record example.com ns1 A 192.168.10.11 && \ docker compose exec pdns pdnsutil add-record example.com ns2 A 192.168.10.12 && \ docker compose exec pdns pdnsutil add-record example.com ns3 A 192.168.10.13 && \ docker compose exec pdns pdnsutil add-record example.com ns1 AAAA 2001:db8::11 && \ docker compose exec pdns pdnsutil add-record example.com ns2 AAAA 2001:db8::12 && \ docker compose exec pdns pdnsutil add-record example.com ns3 AAAA 2001:db8::13 && \ docker compose exec pdns pdnsutil add-record example.com www A 3600 "192.168.10.51" docker compose exec pdns pdnsutil increase-serial example.com docker compose exec pdns pdnsutil list-zone example.com
GUI/API
To access the GUI (http://192.168.22.8:8089/), you must use the admin username and the password defined in “pdns/pdns.conf”.
For API access, the API secret key is also defined in “pdns/pdns.conf”.
jonathan@jonathan-VirtualBox:~/Downloads$ curl -H "X-API-Key: change_me_long_random_key" http://192.168.22.8:8089/api/v1/servers | jq .
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 328 100 328 0 0 50719 0 --:--:-- --:--:-- --:--:-- 54666
[
{
"autoprimaries_url": "/api/v1/servers/localhost/autoprimaries{/autoprimary}",
"config_url": "/api/v1/servers/localhost/config{/config_setting}",
"daemon_type": "authoritative",
"id": "localhost",
"type": "Server",
"url": "/api/v1/servers/localhost",
"version": "4.9.13",
"zones_url": "/api/v1/servers/localhost/zones{/zone}"
}
]
jonathan@jonathan-VirtualBox:~/Downloads$ curl -s \
-H "X-API-Key: change_me_long_random_key" \
http://192.168.22.8:8089/api/v1/servers/localhost/zones \
| jq .
[
{
"account": "",
"catalog": "",
"dnssec": false,
"edited_serial": 2,
"id": "example.com.",
"kind": "Native",
"last_check": 0,
"masters": [],
"name": "example.com.",
"notified_serial": 0,
"serial": 2,
"url": "/api/v1/servers/localhost/zones/example.com."
}
]
jonathan@jonathan-VirtualBox:~/Downloads$ curl -s \
-H "X-API-Key: change_me_long_random_key" \
http://192.168.22.8:8089/api/v1/servers/localhost/zones/example.com. \
| jq .
{
"account": "",
"api_rectify": false,
"catalog": "",
"dnssec": false,
"edited_serial": 2,
"id": "example.com.",
"kind": "Native",
"last_check": 0,
"master_tsig_key_ids": [],
"masters": [],
"name": "example.com.",
"notified_serial": 0,
"nsec3narrow": false,
"nsec3param": "",
"rrsets": [
{
"comments": [],
"name": "www.example.com.",
"records": [
{
"content": "192.168.10.51",
"disabled": false
}
],
"ttl": 3600,
"type": "A"
},
{
"comments": [],
"name": "mail.example.com.",
"records": [
{
"content": "192.168.10.60",
"disabled": false
}
],
"ttl": 3600,
"type": "A"
},
{
"comments": [],
"name": "ns2.example.com.",
"records": [
{
"content": "192.168.22.8",
"disabled": false
}
],
"ttl": 3600,
"type": "A"
},
{
"comments": [],
"name": "ns1.example.com.",
"records": [
{
"content": "192.168.22.8",
"disabled": false
}
],
"ttl": 3600,
"type": "A"
},
{
"comments": [],
"name": "example.com.",
"records": [
{
"content": "\"v=spf1 -all\"",
"disabled": false
}
],
"ttl": 3600,
"type": "TXT"
},
{
"comments": [],
"name": "example.com.",
"records": [
{
"content": "10 mail.example.com.",
"disabled": false
}
],
"ttl": 3600,
"type": "MX"
},
{
"comments": [],
"name": "example.com.",
"records": [
{
"content": "ns1.example.com. hostmaster.example.com. 2 10800 3600 604800 3600",
"disabled": false
}
],
"ttl": 3600,
"type": "SOA"
},
{
"comments": [],
"name": "example.com.",
"records": [
{
"content": "ns1.example.com.",
"disabled": false
},
{
"content": "ns2.example.com.",
"disabled": false
}
],
"ttl": 3600,
"type": "NS"
},
{
"comments": [],
"name": "example.com.",
"records": [
{
"content": "192.168.10.50",
"disabled": false
}
],
"ttl": 3600,
"type": "A"
}
],
"serial": 2,
"slave_tsig_key_ids": [],
"soa_edit": "",
"soa_edit_api": "",
"url": "/api/v1/servers/localhost/zones/example.com."
}
jonathan@jonathan-VirtualBox:~/Downloads$ curl -s \
-H "X-API-Key: change_me_long_random_key" \
http://192.168.22.8:8089/api/v1/servers/localhost/zones/example.com. \
| jq '.rrsets'
[
{
"comments": [],
"name": "www.example.com.",
"records": [
{
"content": "192.168.10.51",
"disabled": false
}
],
"ttl": 3600,
"type": "A"
},
{
"comments": [],
"name": "mail.example.com.",
"records": [
{
"content": "192.168.10.60",
"disabled": false
}
],
"ttl": 3600,
"type": "A"
},
{
"comments": [],
"name": "ns2.example.com.",
"records": [
{
"content": "192.168.22.8",
"disabled": false
}
],
"ttl": 3600,
"type": "A"
},
{
"comments": [],
"name": "ns1.example.com.",
"records": [
{
"content": "192.168.22.8",
"disabled": false
}
],
"ttl": 3600,
"type": "A"
},
{
"comments": [],
"name": "example.com.",
"records": [
{
"content": "\"v=spf1 -all\"",
"disabled": false
}
],
"ttl": 3600,
"type": "TXT"
},
{
"comments": [],
"name": "example.com.",
"records": [
{
"content": "10 mail.example.com.",
"disabled": false
}
],
"ttl": 3600,
"type": "MX"
},
{
"comments": [],
"name": "example.com.",
"records": [
{
"content": "ns1.example.com. hostmaster.example.com. 2 10800 3600 604800 3600",
"disabled": false
}
],
"ttl": 3600,
"type": "SOA"
},
{
"comments": [],
"name": "example.com.",
"records": [
{
"content": "ns1.example.com.",
"disabled": false
},
{
"content": "ns2.example.com.",
"disabled": false
}
],
"ttl": 3600,
"type": "NS"
},
{
"comments": [],
"name": "example.com.",
"records": [
{
"content": "192.168.10.50",
"disabled": false
}
],
"ttl": 3600,
"type": "A"
}
]
docker/docker-compose/powerdns.txt · Last modified: by jonathan
