Симптомы: «containers cannot resolve» и странный DNS 127.0.0.53
Классическая ситуация на Ubuntu/Debian: на хосте интернет есть, apt или curl работают, а внутри контейнера DNS «умирает». В логах и выводах обычно встречается что-то из этого набора:
Temporary failure in name resolution
Could not resolve host
lookup registry-1.docker.io: no such host
Если заглянуть внутрь контейнера, часто видно, что /etc/resolv.conf указывает на nameserver 127.0.0.53. На хосте это нормально: так работает stub-резолвер systemd-resolved. Но в контейнере такой адрес почти всегда означает проблему.
Причина простая: 127.0.0.53 — loopback-адрес. Внутри контейнера loopback — это «сам контейнер», а не хост. DNS-сервера на 127.0.0.53 в контейнере нет, поэтому запросам некуда уходить.
Как Docker формирует DNS в контейнере
Docker не «угадывает» DNS, он формирует контейнерный /etc/resolv.conf из доступных источников конфигурации. Чаще всего используются:
- настройки, видимые через
/etc/resolv.confна хосте (часто это symlink); - параметр
--dnsпри запуске контейнера; - настройка
dnsна уровне Docker daemon (через/etc/docker/daemon.json); - параметр
dnsв Docker Compose для конкретного сервиса.
Когда на хосте активен systemd-resolved, файл /etc/resolv.conf нередко указывает на stub 127.0.0.53. Docker переносит это внутрь контейнера, и начинается «containers cannot resolve».
Почему на хосте это работает, а в контейнере нет
systemd-resolvedслушает127.0.0.53в сетевом пространстве имён хоста. Контейнер — другое сетевое пространство, и его127.0.0.1/127.0.0.53не имеет отношения к хостовому stub-резолверу.
Отдельно полезно помнить: поведение может отличаться между машинами. Например, где-то /etc/resolv.conf уже содержит «реальные» адреса DNS (роутер, провайдер, корпоративный резолвер) и контейнеры живут нормально. А на сервере с systemd-resolved вы видите только 127.0.0.53 — и всё ломается.
Если вы держите несколько проектов и сетей на одном сервере, проще диагностировать и чинить такие вещи, когда есть полный контроль над системой и сетевым стеком на VDS: можно аккуратно настроить DNS для Docker и не зависеть от «магии» окружения.

Быстрая диагностика: что именно у вас сейчас
Сначала проверьте хост: куда указывает /etc/resolv.conf, запущен ли systemd-resolved и какие upstream DNS реально используются.
ls -l /etc/resolv.conf
cat /etc/resolv.conf
systemctl status systemd-resolved --no-pager
resolvectl status
ss -lntup | grep ':53'
Типичные варианты, которые вы увидите:
/etc/resolv.confсодержитnameserver 127.0.0.53и комментарии проsystemd-resolved;/etc/resolv.confявляется symlink на файл в/run/systemd/resolve/;- в
resolvectl statusвидны Upstream DNS Servers (это как раз адреса, которые обычно и нужно дать контейнерам).
Далее проверьте, что видит контейнер:
docker run --rm busybox cat /etc/resolv.conf
docker run --rm busybox nslookup example.com
Если внутри видите 127.0.0.53 — вы практически гарантированно попали в конфликт systemd-resolved и Docker.
Надёжное решение №1: задать DNS для Docker daemon (daemon.json)
Самый предсказуемый вариант для серверов — задать DNS на уровне демона Docker. Тогда Docker перестанет «перетаскивать» stub-адрес и будет прописывать в контейнеры то, что вы укажете.
Создайте или отредактируйте файл:
sudo mkdir -p /etc/docker
sudo nano /etc/docker/daemon.json
Пример конфигурации (подставьте ваши DNS: провайдерские/корпоративные или публичные):
{
"dns": ["1.1.1.1", "8.8.8.8"],
"dns-search": []
}
Перезапустите Docker:
sudo systemctl restart docker
Проверьте, что новые контейнеры получают правильный /etc/resolv.conf:
docker run --rm busybox cat /etc/resolv.conf
docker run --rm busybox nslookup example.com
Это влияет на DNS всех контейнеров на хосте. В продакшене обычно это плюс: меньше сюрпризов при перезагрузках и обновлениях сети.
Решение №2: переключить /etc/resolv.conf хоста на upstream DNS от systemd-resolved
У systemd-resolved есть два «варианта» resolv.conf:
- stub-файл со
127.0.0.53(удобно для приложений на хосте); - файл со списком upstream DNS (реальные адреса), обычно
/run/systemd/resolve/resolv.conf.
Если сделать так, чтобы /etc/resolv.conf указывал на upstream-версию, Docker начнёт копировать нормальные DNS в контейнеры.
Сначала посмотрите, что лежит в каталоге resolve:
ls -l /run/systemd/resolve
cat /run/systemd/resolve/resolv.conf
cat /run/systemd/resolve/stub-resolv.conf
Дальше аккуратно переключите symlink:
sudo ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf
И перезапустите Docker:
sudo systemctl restart docker
Нюанс: на системах с VPN и «динамической» сетью некоторым хостовым приложениям удобнее работать именно через stub. На типичных серверных конфигурациях это обычно не мешает, но после изменения обязательно перепроверьте резолвинг на хосте и в контейнерах.
Решение №3: указать DNS точечно (Compose или docker run)
Если вы не хотите менять глобальное поведение Docker, задайте DNS для конкретного контейнера/сервиса.
docker run
docker run --rm --dns 1.1.1.1 --dns 8.8.8.8 busybox nslookup example.com
docker-compose.yml
services:
app:
image: your-image
dns:
- 1.1.1.1
- 8.8.8.8
Плюс — маленький радиус изменений. Минус — легко забыть добавить настройку в новый сервис, а ещё DNS может отличаться между окружениями без явной причины.
Что делать, если вам нужен split-DNS от systemd-resolved
Частый кейс: на хосте есть VPN, и systemd-resolved распределяет DNS по доменным зонам (например, публичные домены резолвятся через один набор серверов, а *.corp — через другой). Контейнеры при этом должны резолвить и внешние, и внутренние имена.
В таком сценарии простое "dns": ["8.8.8.8"] может сломать внутренние зоны. Рабочие подходы обычно такие:
- использовать корпоративные DNS напрямую в
/etc/docker/daemon.json(если они доступны по сети контейнеров); - поднять локальный рекурсивный резолвер на хосте (например, Unbound) и указать контейнерам адрес, доступный из docker bridge (не loopback);
- проверить маршрутизацию: контейнеры должны уметь ходить к DNS, которые выдаёт VPN, по сети (а не через
127.0.0.53).
Правило простое: контейнеру нужен DNS-адрес, который достижим из его сети. Loopback хоста для этого почти никогда не подходит.
Проверка после исправления: чек-лист
1) На хосте
resolvectl status
cat /etc/resolv.conf
2) В новом контейнере
docker run --rm busybox cat /etc/resolv.conf
docker run --rm busybox nslookup example.com
docker run --rm busybox nslookup registry-1.docker.io
3) Проверка реального сценария (pull/build)
Если проблема проявлялась при сборке образа или установке пакетов, прогоните тест:
docker pull alpine:latest
docker build --no-cache -t dns-test .
Важно проверять именно на новых контейнерах: контейнерный /etc/resolv.conf генерируется Docker’ом при старте.
Частые ошибки и анти-паттерны
Подсовывать контейнеру 127.0.0.53 «как есть»
Почти всегда неверно. Даже если «завелось» в одной сети, при изменении маршрутов, VPN или правил фильтрации ломается одним из первых.
Править /etc/resolv.conf внутри контейнера руками
Изменения не переживут пересоздание контейнера. Делайте настройку через Docker daemon, Compose/CLI или через устойчивую схему DNS.
Забывать про особенности окружения (VDS/сервер, несколько проектов, разные сети)
Если вы держите несколько стеков на одном хосте, разумнее привести DNS к предсказуемому виду на уровне демона Docker и инфраструктуры. Для такого сценария удобнее, когда вы полностью контролируете сеть и системные службы на VDS.

Если контейнеры обслуживают сайт или админку, не забудьте про транспорт: корректный DNS — база, но для продакшена также важны SSL-сертификаты, чтобы исключить ошибки доверия и проблемы с безопасностью при обновлениях и интеграциях.
Итоги
Проблема systemd-resolved и Docker — это не «мистика», а несовпадение сетевых пространств: 127.0.0.53 работает на хосте, но не является DNS-сервером внутри контейнера.
Самые практичные исправления: явно задать DNS в /etc/docker/daemon.json или переключить /etc/resolv.conf хоста на upstream-файл /run/systemd/resolve/resolv.conf. После правок всегда проверяйте резолвинг на новых контейнерах и в вашем реальном сценарии (pull/build/apt/pip).


