Короткий ответ
На user-defined сети Docker резолвит имена сервисов через embedded DNS 127.0.0.11. Форвард внешних имён идёт на DNS хоста host.example (часто systemd-resolved stub 127.0.0.53). Если db не резолвится — сначала общая сеть (контейнеры не видят друг друга). Если registry.example не резолвится — хостовый DNS/прокси, dns: в daemon.json/compose, иногда нужен extra_hosts. Не правите /etc/hosts внутри контейнера руками: слой ephemeral. Не privileged ради записи в resolv.conf.
Симптомы и как отличить
getent hosts dbпусто при общей сети;getent hosts registry.exampleSERVFAIL, ping 1.1.1.1 жив;cat /etc/resolv.confвнутри показывает8.8.8.8, хотя вы ждали 127.0.0.11;- на хосте
dig registry.exampleок, в контейнере нет.
| resolv.conf в контейнере | Смысл |
|---|---|
| 127.0.0.11 | штат user-defined |
| 8.8.8.8 из daemon.json | обход хоста |
| 127.0.0.53 | обычно ошибка копирования хостового stub внутрь default bridge |
| пусто | сломанный шаблон |
Возможные причины
- Контейнер на default
bridge— нет DNS имён compose. - Хостовый resolved сломан / нет DNS в netplan.
dns: [127.0.0.53]в compose — из контейнера stub хоста недоступен как 127.0.0.53 хоста.extra_hostsс опечаткой или перетёртnetwork_mode: host(тогда hosts хоста).- Firewall режет UDP/53 к корпоративному резолверу, ICMP при этом жив.
- Search-домен превращает
dbвdb.corp.exampleNXDOMAIN раньше, чем встроенное имя сервиса (редко, зависит от опций). - Редко:
--dnsв unit dockerd конфликтует с compose.
Диагностика
Проект app, сервис web.
1. Что в resolv.conf и hosts
docker compose -f /opt/app/compose.yaml exec web cat /etc/resolv.conf
docker compose exec web cat /etc/hosts
docker inspect app-web-1 --format '{{json .HostConfig.Dns}} extra={{json .HostConfig.ExtraHosts}} net={{.HostConfig.NetworkMode}}'2. Резолв сервис vs внешний
docker compose exec web getent hosts db
docker compose exec web getent hosts registry.example
docker compose exec web getent ahostsv4 registry.exampleНа хосте параллельно:
resolvectl query registry.example
cat /etc/resolv.conf3. Жив ли 127.0.0.11
Из контейнера (busybox/drill/dig если есть):
docker compose exec web sh -c 'command -v dig >/dev/null && dig +short db @127.0.0.11 || nslookup db 127.0.0.11 || getent hosts db'Таймаут на 127.0.0.11 — проблема dockerd/сети, не приложения.
4. extra_hosts в рендере
cd /opt/app && docker compose config | grep -A20 extra_hostsРешение
Сценарий A. Имена сервисов не резолвятся
Верните контейнеры на одну user-defined сеть. Уберите dns: который бьёт в никуда. Пересоздайте:
docker compose up -d
docker compose exec web getent hosts dbСценарий B. Внешние имена, хост resolved ок
Форвард с 127.0.0.11 должен идти на апстрим хоста. Если в daemon.json жёстко "dns": ["8.8.8.8"] — корпоративный registry.example станет NXDOMAIN. Уберите публичный DNS или добавьте корпоративный первым.
Точечно в compose:
services:
web:
dns:
- 10.0.0.53
extra_hosts:
- "registry.example:10.0.20.40"extra_hosts — для единичных исключений (split-horizon), не вместо DNS-зоны.
Сценарий C. dns: [127.0.0.53] в YAML
Замените на IP реального резолвера (10.0.0.53) или положитесь на 127.0.0.11 без поля dns. Stub resolved не слушает в netns контейнера.
Сценарий D. extra_hosts не применился
Поле есть только после recreate:
docker compose up -d --force-recreate web
docker compose exec web grep registry /etc/hostsНе docker exec vi /etc/hosts.
Сценарий E. registry.example и pull с хоста
Демон использует DNS хоста, не resolv контейнера web. Если docker pull registry.example:5000/app/web:1.2.3 падает, а exec curl по IP жив — чините /etc/hosts хоста или resolved, см. образ не скачивается.
Как проверить, что проблема устранена
docker compose exec web getent hosts db
docker compose exec web getent hosts registry.example
docker compose exec web grep registry.example /etc/hosts || trueОба имени дают ожидаемые IP. 127.0.0.11 в resolv.conf на user-defined сети. Pull: docker pull registry.example:5000/app/web:1.2.3 (если тег существует).
Если не помогло
- NXDOMAIN на
db.local— search-домен. Используйте короткоеdbили FQDN сервиса не придумывайте. - Работает после
echo nameserver 10.0.0.53в exec и пропадает после recreate — не задокументировали YAML. - DNS over HTTPS на хосте vs UDP/53 в политике — контейнерный форвард может ходить иначе, чем браузер администратора.
host.docker.internalна Linux Engine 27: задайте черезextra_hosts: ["host.docker.internal:host-gateway"]при необходимости, это не Windows-магия из коробки везде одинаково — проверьтеdocker compose exec getent hosts host.docker.internal.- SERVFAIL только для DNSSEC-зон — чините зону, не отключайте DNSSEC на корпоративном резолвере без причины.
Профилактика
- Не копировать
dns: 8.8.8.8в шаблоны. extra_hostsтолько как тикет с датой удаления.- Мониторинг резолва
dbиregistry.exampleиз sidecar. - Документировать split-horizon для
registry.example. - После смены netplan — тест compose exec, не только
ping ya.ruс хоста.
FAQ
Можно ли --dns 8.8.8.8 на одном web?
Да технически. Сломаете внутренние имена, если замените 127.0.0.11 полностью на публичный DNS: сервис db публичный DNS не знает. Либо оставляйте embedded, либо extra_hosts для db (хуже).
Почему hosts правится и сразу сбрасывается?
Контейнер пересоздали. Источник — ExtraHosts. Только YAML.
systemd-resolved ломает Docker?
Чаще ломает неверный dns: [127.0.0.53] в контейнере. Сам 127.0.0.11 → 127.0.0.53 на хосте — нормальная схема.
Нужен ли privileged чтобы echo >> resolv.conf?
Нет. Это антипаттерн. Правьте daemon.json/compose.
extra_hosts vs /etc/hosts на хосте
Хостовый hosts влияет на dockerd (pull) и на форвард, если контейнер использует DNS хоста. extra_hosts — только тот контейнер.
Ubuntu 24.04 со systemd-resolved часто держит /etc/resolv.conf как stub 127.0.0.53. Embedded DNS 127.0.0.11 ходит к этому stub с хоста, не из netns. Если вы «починили DNS в контейнере», прописав dns: [127.0.0.53] в YAML, запросы умрут: в namespace нет слушателя 53 на loopback хоста. Правильный путь — починить resolvectl status на host.example или указать достижимый IP резолвера (10.0.0.53) так, чтобы UDP/53 и TCP/53 были разрешены egress-политикой.
ndots:1 vs ndots:5 меняет, уйдёт ли короткое db в search-домен corp.example раньше, чем в Docker DNS. Симптом: getent hosts db даёт NXDOMAIN на db.corp.example, хотя сервис db жив. Смотрите options в /etc/resolv.conf контейнера и не копируйте хостовый search в daemon.json без нужды. Для registry.example всегда используйте FQDN, не короткое имя.