
Сети Docker: DNS сервисов, порты и изоляция контейнеров
Создаём пользовательскую bridge-сеть, обращаемся к контейнерам по имени, публикуем только нужный порт на loopback и проверяем сетевую изоляцию.
Содержание
Контейнеру обычно не нужен публичный порт, чтобы общаться с соседним сервисом. Достаточно подключить оба к одной сети Docker и обращаться по имени. Встроенная служба DNS преобразует имя web-demo во внутренний IP-адрес. Параметр -p решает другую задачу: открывает путь с хоста, то есть машины с Docker, к порту контейнера.
Какая связь нужна контейнерам
Создавайте пользовательские bridge-сети, используйте имена контейнеров вместо их IP и публикуйте только входные сервисы. Если порт предназначен для локального reverse proxy, привяжите его к 127.0.0.1. Базу, cache и внутренние API оставляйте без -p, а фактическую доступность проверяйте с host, из разрешённой сети и из контейнера, который не должен иметь доступа.
Создайте отдельную сеть приложения
docker network create app-net
docker network inspect app-net
Драйвер bridge создаёт на одном хосте виртуальную сеть, похожую по роли на отдельный коммутатор. Пользовательская сеть даёт автоматическое разрешение имён контейнеров и не подключает к себе посторонние контейнеры без явной команды.
Запустите тестовый HTTP-сервис без публикации порта:
docker run -d --name web-demo \
--network app-net \
nginx:alpine
Теперь второй контейнер в app-net может найти его по имени:
docker run --rm --network app-net \
curlimages/curl:8.16.0 \
--fail --silent --show-error http://web-demo/
IP web-demo сохранять не нужно. После пересоздания он может измениться, а DNS-имя останется частью конфигурации запуска.
Внутренний и опубликованный порт — не одно и то же
Nginx в примере слушает порт 80 внутри контейнера. Соседний контейнер обращается к web-demo:80. Запись -p 127.0.0.1:8080:80 означает:
127.0.0.1— адрес Docker host, на котором создаётся публикация;8080— порт на host;80— порт целевого контейнера.
Чтобы добавить публикацию, контейнер нужно пересоздать:
docker rm -f web-demo
docker run -d --name web-demo \
--network app-net \
-p 127.0.0.1:8080:80 \
nginx:alpine
Теперь проверьте публикацию с самого хоста. curl запрашивает страницу, docker port показывает краткую привязку, а inspect — сохранённые настройки портов:
curl --fail --silent --show-error http://127.0.0.1:8080/
docker port web-demo
docker inspect web-demo --format '{{json .NetworkSettings.Ports}}'
Если написать только -p 8080:80, Docker публикует порт на адресах host согласно конфигурации daemon. На VPS это может сделать сервис доступным извне. Не рассчитывайте, что отсутствие правила в UFW автоматически закроет Docker-публикацию: Docker управляет собственными firewall-правилами. Проверяйте итоговый доступ с другой машины и проектируйте фильтрацию с учётом цепочек Docker.
Не публикуйте внутренние сервисы без необходимости
Для схемы reverse proxy → app → database обычно достаточно:
- reverse proxy подключён к внешней и внутренней сети;
- app подключён к внутренней сети приложения и сети базы;
- database подключена только к сети базы;
- наружу опубликован лишь HTTP/HTTPS reverse proxy либо loopback-порт приложения для proxy на host.
Создадим отдельную сеть и тестовый контейнер, который изображает внутренний сервис без -p:
docker network create data-net
docker run -d --name db-demo \
--network data-net \
alpine:3.22 sh -c 'while :; do sleep 3600; done'
В реальном стеке на этом месте может быть PostgreSQL, MariaDB или другой закрытый компонент. Его прикладной порт не требуется публиковать, если клиент тоже находится в data-net. Аутентификацию и секреты всё равно нужно настраивать по документации выбранного сервиса.
Один контейнер можно подключить к нескольким сетям:
docker network connect data-net web-demo
docker inspect web-demo --format '{{json .NetworkSettings.Networks}}'
Теперь web-demo видит обе сети. Это увеличивает его возможности, поэтому подключайте только те сегменты, которые нужны роли сервиса.
Проверьте изоляцию с третьей стороны
Создайте временный контейнер без подключения к app-net и попробуйте разрешить имя. Команда getent hosts спрашивает системный механизм поиска адресов и ничего не меняет:
docker run --rm alpine:3.22 \
getent hosts web-demo
Команда должна завершиться без адреса web-demo. Затем повторите из разрешённой сети:
docker run --rm --network app-net alpine:3.22 \
getent hosts web-demo
Здесь ожидается внутренний адрес. Такая проверка подтверждает членство и DNS, но не доказывает безопасность приложения. Любой участник одной сети потенциально может обратиться к слушающему порту другого участника; сервис всё равно требует аутентификации и минимальных прав.
Диагностика сетевой ошибки
Если связь не работает, сначала выясните, запущен ли сервис и к каким сетям он подключён. Следующие команды выполняются на хосте и только собирают состояние контейнеров, сети и журнала:
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
docker network inspect app-net
docker inspect web-demo --format '{{json .NetworkSettings.Networks}}'
docker logs --tail 100 web-demo
Затем проверьте DNS и TCP/HTTP из того контейнера, где работает клиент. Проверка только с host не воспроизводит его сетевой контекст.
Частые причины отказа:
- клиент и сервер находятся в разных сетях;
- приложение слушает только
127.0.0.1внутри контейнера вместо0.0.0.0; - в URL указан host port, хотя контейнеры должны использовать внутренний port;
- в конфигурации сохранён старый IP вместо имени;
- порт опубликован наружу без необходимости;
- сервис ещё запускается, хотя контейнер уже имеет статус running.
Последний случай относится к готовности приложения и решается healthcheck и повторными попытками клиента, а не случайной задержкой sleep.
Удалите контейнеры и сети из примера
docker rm -f web-demo db-demo
docker network rm app-net data-net
Перед удалением рабочей сети проверьте её через docker network inspect: Docker не удалит сеть с подключёнными контейнерами, но автоматизировать принудительное отключение незнакомых сервисов опасно.
Запуск и исследование экземпляров разобраны в руководстве по run, exec и inspect. Постоянные каталоги подключайте по правилам volumes и bind mounts, а декларативно собрать эти связи помогает Docker Compose.
Источники
Самопроверка
Проверьте, что материал усвоен
Ответьте на все вопросы. Результат сохранится только в этом браузере и будет учтён в статистике прочитанных материалов.
Разбираем коротко
Частые вопросы
Нужно ли публиковать порт базы данных для доступа из соседнего контейнера?
Нет. Контейнеры в одной пользовательской сети обращаются к порту контейнера напрямую. Публикация нужна для доступа с host, из другой сети или извне.
Почему адрес контейнера нельзя записывать в конфигурацию приложения?
IP назначается динамически и способен измениться после пересоздания. В пользовательской сети используйте DNS-имя контейнера или имя сервиса Compose.
Ограничивает ли запись -p 127.0.0.1:8080:80 доступ только текущим сервером?
Да, порт привязывается к loopback Docker host. Для внешнего доступа обычно ставят reverse proxy на host и отдельно проверяют правила firewall.


