MAATRIX / Блог / BIND9 в Docker Compose: готовый файл

BIND9 в Docker Compose: готовый файл

MAATRIX

Если вы администрируете больше пары доменов или хотите держать собственный резолвер для внутренней сети, рано или поздно упираетесь в BIND9 — он старый, местами неудобный, но именно он де-факто стандарт индустрии, и почти вся документация RFC по DNS написана с оглядкой на его поведение. Ставить его «в систему» через apt — плохая идея на современном сервере: версия в репозитории отстаёт, конфиги расползаются по /etc, а откатить неудачный zone-файл без даунтайма неудобно. Ниже — рабочий docker-compose.yml, который поднимает BIND9 как авторитетный DNS-сервер за пару минут и позволяет держать все зоны и конфиги в одной папке под git.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Почему BIND9, а не dnsmasq или PowerDNS

BIND9 — это не «ещё один DNS-сервер», а эталонная реализация, на которую ориентируются другие проекты. У него есть три вещи, которых часто не хватает более лёгким альтернативам: полноценная поддержка DNSSEC из коробки, гибкие ACL на уровне view (можно отдавать разные ответы внутренним и внешним клиентам с одного сервера) и зрелая система логирования запросов через rndc и категории логов.

Если вам нужен просто быстрый рекурсивный резолвер для дома или небольшой сети — присмотритесь к AdGuard Home в Docker Compose, он проще в администрировании. Если вы уже работаете с PowerDNS и вас устраивает его REST API и SQL-бэкенд — менять шило на мыло смысла нет. BIND9 выбирают, когда нужна максимальная совместимость с корпоративными практиками, DNSSEC-подпись зон вручную или сложные ACL с несколькими views на одном IP.

Структура проекта

Прежде чем писать docker-compose.yml, разложим файлы так, чтобы конфиг и зоны версионировались отдельно от рантайма:

bind9/
├── docker-compose.yml
├── config/
│   ├── named.conf
│   ├── named.conf.options
│   ├── named.conf.local
│   └── zones/
│       └── db.example.com
├── cache/
└── logs/

Папки cache/ и logs/ монтируются как volume — так журналы и кэш переживут пересоздание контейнера, а config/ держим под git, чтобы откатить неудачный zone-файл было делом одной команды.

mkdir -p bind9/{config/zones,cache,logs}
cd bind9

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Готовый docker-compose.yml

Используем образ internetsystemsconsortium/bind9 — это официальная сборка от ISC, разработчика самого BIND. На момент написания статьи (конец августа 2026) актуальна ветка BIND 9.18 LTS — уточняйте тег на Docker Hub перед деплоем, версии там обновляются.

services:
  bind9:
    image: internetsystemsconsortium/bind9:9.18
    container_name: bind9
    restart: unless-stopped
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      # rndc-порт наружу не публикуем — управляем изнутри сети docker
    volumes:
      - ./config:/etc/bind
      - ./cache:/var/cache/bind
      - ./logs:/var/log/bind
    environment:
      - TZ=Europe/Moscow
    cap_add:
      - NET_BIND_SERVICE
    networks:
      - dns_net

networks:
  dns_net:
    driver: bridge

Обратите внимание на cap_add: NET_BIND_SERVICE — без него контейнер не сможет забиндиться на 53-й порт без privileged: true, а полный privileged-режим для DNS-сервера, торчащего наружу, — плохая идея с точки зрения безопасности.

Базовый конфиг named.conf.options

Файл config/named.conf.options отвечает за общее поведение сервера: рекурсию, ACL, логирование запросов. Начните с максимально закрытой конфигурации и открывайте только то, что реально нужно:

acl "trusted" {
    10.0.0.0/8;
    172.16.0.0/12;
    localhost;
};

options {
    directory "/var/cache/bind";
    pid-file "/var/run/named/named.pid";

    recursion yes;
    allow-recursion { trusted; };
    allow-query { trusted; any; };
    allow-transfer { none; };

    listen-on { any; };
    listen-on-v6 { none; };

    dnssec-validation auto;

    querylog yes;
};

logging {
    channel query_log {
        file "/var/log/bind/query.log" versions 5 size 20m;
        severity info;
        print-time yes;
    };
    category queries { query_log; };
};

Ключевые моменты, о которых часто забывают:

  • allow-recursion должен быть ограничен доверенными сетями — открытый рекурсивный резолвер на публичном IP превращается в участника DNS-амплификации для DDoS-атак третьих лиц, и это не гипотетическая угроза, а то, за что провайдер может забанить сервер.
  • allow-transfer { none; } в options блокирует зонный трансфер по умолчанию, а разрешаем его точечно уже в описании конкретной зоны для конкретного slave-сервера.
  • querylog yes полезен на старте для отладки, но на проде с большим трафиком генерирует много I/O — на VPS с медленным диском лучше снизить severity или использовать size с ротацией пожёстче.

Конфигурация зоны

Файл config/named.conf.local подключает зоны:

zone "example.com" {
    type master;
    file "/etc/bind/zones/db.example.com";
    allow-transfer { 203.0.113.10; }; // slave-сервер
    also-notify { 203.0.113.10; };
};

А сам zone-файл config/zones/db.example.com — классический формат BIND, с serial, который обязательно инкрементить при каждом изменении (иначе slave-серверы не подхватят обновление):

$TTL 3600
@   IN  SOA ns1.example.com. admin.example.com. (
        2026083101 ; Serial (год-месяц-день-номер)
        7200       ; Refresh
        3600       ; Retry
        1209600    ; Expire
        3600 )     ; Minimum TTL

    IN  NS  ns1.example.com.
    IN  NS  ns2.example.com.

    IN  MX  10 mail.example.com.

@       IN  A   203.0.113.5
ns1     IN  A   203.0.113.5
ns2     IN  A   203.0.113.6
www     IN  A   203.0.113.5
mail    IN  A   203.0.113.7

Формат serial ГГГГММДД+номер — не требование протокола, а удобная конвенция: сразу видно, когда зона правилась в последний раз, и не нужно держать в голове счётчик.

Запуск, проверка синтаксиса и первые тесты

Перед стартом контейнера стоит проверить конфиг статически — BIND9 предоставляет для этого утилиты named-checkconf и named-checkzone, они есть внутри образа:

docker compose run --rm bind9 named-checkconf /etc/bind/named.conf
docker compose run --rm bind9 named-checkzone example.com /etc/bind/zones/db.example.com

Если обе команды не выдали ошибок, поднимаем сервис:

docker compose up -d
docker compose logs -f bind9

Проверяем резолвинг снаружи контейнера — dig покажет и ответ, и время, за которое сервер его отдал:

dig @127.0.0.1 example.com A +short
dig @127.0.0.1 www.example.com A

Если сервер стоит на VPS с внешним IP и вы хотите проверить доступность именно с публичного адреса — то же самое с dig @ваш_публичный_ip. На этом этапе легко словить проблему, о которой ниже.

Firewall, порты и типичные грабли

DNS работает на 53-м порту сразу по TCP и UDP — TCP нужен для зонных трансферов и ответов, не влезающих в один UDP-пакет (например, при DNSSEC). Если вы уже настраивали ufw на сервере, не забудьте открыть оба протокола:

ufw allow 53/tcp
ufw allow 53/udp

Частые проблемы при первом запуске:

СимптомПричинаРешение
dig отдаёт connection refusedПорт не проброшен в firewall или занят другим процессом (systemd-resolved)`ss -tulpn \grep :53`, освободить порт или отключить локальный резолвер
Zone не грузится, в логах not loaded due to errorsОпечатка в zone-файле, чаще всего пропущенная точка в конце FQDNnamed-checkzone укажет строку с ошибкой
Внешние клиенты не резолвят доменallow-query слишком узкий, либо провайдер блокирует исходящий 53/udpПроверить ACL, для авторитетного сервера allow-query { any; }; — это нормально, ограничивать нужно allow-recursion
Контейнер падает сразу после стартаsystemd-resolved на хосте уже держит 53-й портОтключить systemd-resolved заглушку stub-listener или биндить BIND на другой интерфейс

Отдельно стоит сказать про защиту от brute-force и сканирования: DNS-сервер редко атакуют перебором паролей (аутентификации там как таковой нет), но он часто становится целью для флуда запросами. Если вы уже используете fail2ban для других сервисов на этом сервере, для BIND есть готовые джейлы, которые банят IP по паттернам в query-логе при аномальной частоте запросов — это неплохая дополнительная линия обороны поверх ACL.

Резервное копирование конфигурации и зон

Так как конфиг и зоны лежат в обычных файлах на хосте (volume ./config), бэкап сводится к архивации папки и, в идеале, к git-репозиторию с историей изменений zone-файлов — это даёт бесплатный аудит «кто и когда поменял A-запись»:

cd bind9
git init
git add config/
git commit -m "initial BIND9 config"

Для регулярного бэкапа достаточно cron-задачи с архивацией и отправкой на удалённое хранилище:

0 3 * * * tar czf /backup/bind9-$(date +\%F).tar.gz -C /opt/bind9 config && find /backup -name 'bind9-*.tar.gz' -mtime +14 -delete

Перед откатом всегда прогоняйте named-checkconf/named-checkzone на восстановленных файлах — иначе есть риск поднять сервис с конфигом, который выглядит правильно, но не проходит валидацию из-за изменившейся версии BIND.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Нужен ли BIND9 в Docker или лучше ставить на голый сервер?

В контейнере проще версионировать конфиг, откатывать обновления образа и переносить сервис между серверами — сам сервис от этого не медленнее, оверхед Docker для DNS-трафика пренебрежимо мал.

Можно ли использовать этот же compose-файл как рекурсивный резолвер для локальной сети?

Да, для этого достаточно расширить allow-recursion на подсеть ваших клиентов и убрать секцию zone с master-зонами, если вам не нужно быть ещё и авторитетным сервером для собственных доменов.

Как включить DNSSEC-подпись зоны?

Нужно сгенерировать ключи через dnssec-keygen, подписать зону dnssec-signzone и подключить в конфиге уже подписанный .signed-файл — это отдельная тема, объём которой не укладывается в один compose-файл, ориентируйтесь на официальную документацию ISC для конкретной версии.

Чем BIND9 отличается от dns leak, который проверяют для VPN?

Это разные вещи: DNS leak — про то, куда уходят запросы клиента при активном VPN, а BIND9 здесь может выступать как раз тем доверенным резолвером, на который вы принудительно направляете трафик, чтобы утечки не было.

Сколько ресурсов нужно серверу под BIND9?

Для нескольких десятков доменов без большой рекурсивной нагрузки хватает 1 vCPU и 1 ГБ RAM с запасом — узкое место обычно не CPU, а сетевые лимиты и защита от флуда при паблик-резолвере.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →