PowerDNS в Docker Compose: готовый файл
Когда доменов и зон становится больше двух-трёх, панель регистратора превращается в проблему: нет API, нельзя автоматизировать выпуск записей из CI/CD, DNSSEC включается через тикет в поддержку неделю. PowerDNS решает это одним движением — авторитетный DNS-сервер с полноценным REST API, хранением зон в реляционной базе и встроенной поддержкой DNSSEC. Ниже — рабочий docker-compose.yml с бэкендом MySQL, инициализация схемы и конфигурация, которая не превращает сервер в открытый резолвер для чужого DDoS.
Содержание
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Почему PowerDNS, а не BIND9 или файлы зон руками
BIND9 — эталон надёжности, но зоны в нём живут в текстовых файлах: любое программное изменение записи — это генерация файла, rndc reload и надежда, что синтаксис не сломался. Для одного домена нормально, для полусотни доменов на несколько проектов — уже боль.
PowerDNS Authoritative Server устроен иначе: зоны и записи лежат в базе данных (MySQL, PostgreSQL, SQLite и другие бэкенды), сервер сам следит за консистентностью через SQL-транзакции. Отсюда три практических плюса:
- REST API — зоны и записи создаются из скрипта, Terraform-провайдера или собственной админки, не трогая конфиги руками;
- DNSSEC "из коробки" — подпись зоны и ротация ключей одной командой
pdnsutil, без ручной генерации ключей иdnssec-signzone; - Primary/secondary "из коробки" — включить AXFR-репликацию между двумя серверами PowerDNS проще, чем настраивать
also-notify/allow-transferв BIND.
Обратная сторона — каждый запрос теперь идёт через SQL-запрос к базе, а не чтение файла из памяти. PowerDNS сглаживает это встроенным кэшем ответов, и для собственных доменных зон (не публичный резолвер с миллионами клиентов) разница не критична.
Что понадобится перед стартом
- VPS с 1 vCPU / 1 ГБ RAM хватает под несколько десятков зон и умеренную нагрузку; для сотен зон с частыми обновлениями возьмите 2 ГБ RAM — под MySQL и кэш PowerDNS;
- установленный Docker и Docker Compose;
- домен, для которого вы готовы прописать у регистратора кастомные NS-записи (делегирование зоны на ваш сервер) — без этого шага PowerDNS не будет получать запросы из интернета;
- открытый на сервере 53 порт (TCP и UDP) — авторитетный DNS обязан отвечать на оба протокола, часть резолверов переходит на TCP при больших ответах, например при DNSSEC;
- если ещё не работали с зонами напрямую, полезно заранее прочитать про настройку домена и DNS с нуля на Ubuntu 24.04 — там разобрана логика A/AAAA/CNAME/MX записей.
Проверьте, что порт 53 свободен на нужном интерфейсе (на свежем Ubuntu его иногда занимает systemd-resolved):
sudo ss -tulpn | grep :53
Для авторитетного сервера, в отличие от рекурсивного резолвера типа Pi-hole, порт не прячут за VPN — он должен быть доступен всему интернету, чтобы отвечать на запросы к вашим доменам. Защита строится иначе: ограничением AXFR, лимитами запросов и закрытым API — об этом ниже.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверDocker Compose: PowerDNS с бэкендом MySQL
Создайте рабочую директорию:
mkdir -p ~/powerdns && cd ~/powerdns
Файл docker-compose.yml:
services:
db:
image: mysql:8.4
container_name: pdns-mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: "смените_на_свой_root_пароль"
MYSQL_DATABASE: pdns
MYSQL_USER: pdns
MYSQL_PASSWORD: "смените_на_свой_пароль_pdns"
volumes:
- ./mysql-data:/var/lib/mysql
- ./schema.mysql.sql:/docker-entrypoint-initdb.d/schema.mysql.sql:ro
networks:
- pdns-net
pdns:
image: powerdns/pdns-auth-49:latest
container_name: pdns-auth
restart: unless-stopped
depends_on:
- db
ports:
- "53:53/tcp"
- "53:53/udp"
- "127.0.0.1:8081:8081"
environment:
PDNS_launch: gmysql
PDNS_gmysql_host: db
PDNS_gmysql_user: pdns
PDNS_gmysql_password: "смените_на_свой_пароль_pdns"
PDNS_gmysql_dbname: pdns
PDNS_allow_axfr_ips: "0.0.0.0/32"
PDNS_disable_axfr: "no"
PDNS_api: "yes"
PDNS_api_key: "смените_на_длинный_случайный_ключ"
PDNS_webserver: "yes"
PDNS_webserver_address: "0.0.0.0"
PDNS_webserver_allow_from: "127.0.0.1,ваш_рабочий_ip/32"
PDNS_default_soa_content: "@ hostmaster@example.com 0 10800 3600 604800 3600"
PDNS_version_string: "anonymous"
networks:
- pdns-net
networks:
pdns-net:
driver: bridge
Ключевые моменты:
- образ
powerdns/pdns-auth-49— официальная сборка PowerDNS Authoritative Server ветки 4.9; на Docker Hub у проекта отдельные теги под каждую крупную ветку, перед разворачиванием сверьтесь с актуальным списком тегов — версии обновляются чаще, чем эта статья; - переменные вида
PDNS_имя_настройкиавтоматически транслируются образом вpdns.conf— отдельный конфиг-файл монтировать не обязательно; PDNS_allow_axfr_ips: "0.0.0.0/32"— заглушка "никому не разрешён AXFR"; когда заведёте вторичный сервер, замените на его реальный IP (см. раздел про безопасность);- порт API (
8081) забинден только на127.0.0.1— снаружи недоступен, обращаться нужно либо с самого сервера, либо через SSH-туннель, либо через отдельный reverse-proxy с TLS.
Обязательно замените все пароли и api_key на собственные случайные значения — самый частый способ угона DNS-зоны — забытый дефолтный пароль из примера в интернете.
Инициализация схемы БД и первая зона
Перед первым запуском нужен файл schema.mysql.sql — SQL-схема таблиц PowerDNS (domains, records, cryptokeys, domainmetadata и т.д.). Актуальная схема под вашу версию всегда лежит в официальном репозитории проекта, в каталоге modules/gmysqlbackend/. Скачайте её под версию образа:
curl -o schema.mysql.sql \
https://raw.githubusercontent.com/PowerDNS/pdns/master/modules/gmysqlbackend/schema.mysql.sql
Проверьте, что скачался непустой файл с CREATE TABLE — при смене мажорной версии образа путь в репозитории может отличаться, уточняйте в документации PowerDNS. Затем запускайте стек:
docker compose up -d
MySQL применит schema.mysql.sql автоматически при первом старте (через docker-entrypoint-initdb.d), если каталог mysql-data был пустым. Проверьте логи:
docker compose logs -f pdns
Здоровый запуск заканчивается строкой вида Listening for UDP queries on 0.0.0.0:53. Дальше создаём первую зону через pdnsutil прямо внутри контейнера:
docker exec -it pdns-auth pdnsutil create-zone example.com ns1.example.com
docker exec -it pdns-auth pdnsutil add-record example.com @ A 300 203.0.113.10
docker exec -it pdns-auth pdnsutil add-record example.com www CNAME 300 example.com.
docker exec -it pdns-auth pdnsutil add-record example.com @ MX 3600 "10 mail.example.com."
Проверить, что зона реально отдаётся:
dig @127.0.0.1 example.com A +short
После этого пропишите у регистратора домена NS-записи на ваш сервер (ns1.example.com → IP сервера) и дождитесь распространения делегирования — от нескольких часов до суток в зависимости от TTL у текущего регистратора.
REST API и управление зонами без консоли
Главное преимущество PowerDNS перед файловым DNS — API. Проверить, что он живой:
curl -s -H "X-API-Key: ваш_api_key" http://127.0.0.1:8081/api/v1/servers/localhost/zones | python3 -m json.tool
Добавление записи через API (замена целиком через PATCH с rrsets):
curl -s -X PATCH \
-H "X-API-Key: ваш_api_key" \
-H "Content-Type: application/json" \
http://127.0.0.1:8081/api/v1/servers/localhost/zones/example.com. \
-d '{"rrsets": [{"name": "api-test.example.com.", "type": "A", "ttl": 300,
"changetype": "REPLACE", "records": [{"content": "203.0.113.20", "disabled": false}]}]}'
Это то, что нужно для автоматизации: скрипт деплоя может создавать поддомен под каждое preview-окружение, Terraform-провайдер pdns — управлять зонами декларативно. Если хочется готового веб-интерфейса поверх той же базы, в compose можно добавить контейнер powerdns-admin — он подключается к той же MySQL-базе pdns без отдельной синхронизации. Общий подход к нескольким сервисам в одном compose-файле разобран в статье про docker-compose для продакшена на VPS.
DNSSEC и безопасность: AXFR, лимиты, закрытый API
Включить DNSSEC для уже созданной зоны:
docker exec -it pdns-auth pdnsutil secure-zone example.com
docker exec -it pdns-auth pdnsutil rectify-zone example.com
docker exec -it pdns-auth pdnsutil show-zone example.com
Последняя команда покажет DS-запись — её нужно вручную добавить в панели регистратора (раздел DNSSEC у домена), чтобы цепочка доверия замкнулась от корневых серверов до вашей зоны. Без этого шага DNSSEC-ключи сгенерированы, но резолверы их не проверяют — просто игнорируют подписи.
Если зона важная (домен с почтой, платежами, авторизацией) — DNSSEC стоит включать сразу, а не откладывать: без него подмена ответа резолвера (DNS spoofing/cache poisoning) остаётся теоретически возможной атакой, хоть и требующей ресурсов от злоумышленника.
Авторитетный DNS-сервер, в отличие от рекурсивного резолвера, обязан быть открыт всему интернету по 53-му порту — иначе на ваши домены никто не сможет достучаться. Это не значит, что защищать нечего. Три конкретных риска:
Неограниченный AXFR. Если allow-axfr-ips открыт для всех, любой может выкачать полный список ваших записей (dig axfr @ваш-сервер example.com) — это разведка перед атакой и просто утечка внутренней структуры инфраструктуры (поддомены вида staging, admin-panel, db-internal — прямая подсказка атакующему). Держите allow-axfr-ips пустым либо со списком реальных IP вторичных серверов:
PDNS_allow_axfr_ips: "198.51.100.5/32,198.51.100.6/32"
Открытый API без ограничения по IP. webserver-allow-from должен включать только доверенные адреса — офисный IP, VPN-подсеть, IP CI/CD-раннера. Плюс api-key — случайная строка минимум 32 символа (openssl rand -hex 24), не дефолтное значение из примеров в интернете.
PowerDNS как резолвер по ошибке. Authoritative Server не должен резолвить произвольные домены для чужих клиентов — за рекурсию отвечает отдельный продукт, PowerDNS Recursor, и в этой связке он не участвует. Если случайно оставить сервер открытым как рекурсивный резолвер, он моментально станет мишенью для DNS amplification DDoS — та же проблема, что разобрана в статье про Pi-hole в Docker Compose применительно к резолверам.
Дополнительно на уровне хоста стоит ограничить частоту запросов через iptables/nftables (rate-limit по UDP на 53 порт), чтобы смягчить флуд с одного источника.
Продакшн-нюансы: бэкапы и вторичный сервер
Данные PowerDNS — это, по сути, данные MySQL, ничего специфичного тут нет:
docker exec pdns-mysql mysqldump -u pdns -p pdns > pdns-backup-$(date +%F).sql
Если MySQL на сервере уже используется другими проектами, стоит разобраться с бэкапами системно — в статье про бэкап MySQL на VPS описана автоматизация через mysqldump по расписанию с ротацией.
Для отказоустойчивости стандартная практика — держать второй сервер PowerDNS как secondary (бывший slave), который получает зоны через AXFR от primary и отвечает на запросы независимо от него — если primary упадёт, secondary продолжит работать без вмешательства. На primary для этого открывается PDNS_allow_axfr_ips с IP secondary, на secondary — включается режим репликации (PDNS_secondary_mode в свежих версиях, PDNS_slave_mode в старых — терминология поменялась в районе версий 4.7–4.9) и добавляется зона через pdnsutil add-secondary-zone.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Обязательно ли MySQL, можно ли SQLite?
Можно — модуль gsqlite3 подходит для одного сервера без репликации и небольшого числа зон. Для primary/secondary или доступа из нескольких контейнеров нужна сетевая база — MySQL или PostgreSQL.
PowerDNS может резолвить произвольные домены как обычный DNS-клиента?
Нет, Authoritative Server отвечает только за зоны, которые вы явно создали через pdnsutil или API. За рекурсивный резолвинг отвечает отдельный продукт, PowerDNS Recursor — в этой связке он не участвует.
Что будет, если забыть открыть AXFR для вторичного сервера?
Secondary не сможет синхронизировать зоны и будет отвечать REFUSED или отдавать устаревшие данные — проверяется командой pdnsutil list-zone example.com на обеих машинах со сверкой содержимого.
Нужен ли Traefik или nginx перед PowerDNS?
Для самого DNS (порт 53) — нет, DNS не работает через HTTP-прокси. А вот если открываете REST API наружу, а не только на 127.0.0.1, TLS и авторизацию перед ним разумно поставить через reverse-proxy — подход описан в статье про Traefik как reverse proxy для Docker.
Как проверить, что зона доступна из интернета, а не только локально?
Внешним резолвером: dig @8.8.8.8 example.com A — если он видит записи, делегирование NS у регистратора применилось и распространилось.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →