Контейнеры обмениваются данными в изолированной сети, наружу открыт один контролируемый путь
DevOps

Сети 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.

Источники

Рекламное местоВаша компания здесьРазместить рекламу

Самопроверка

Проверьте, что материал усвоен

Ответьте на все вопросы. Результат сохранится только в этом браузере и будет учтён в статистике прочитанных материалов.

01По какому адресу приложение web должно обращаться к контейнеру db в общей пользовательской сети?
02Что означает -p 8080:80 без указания IP host?
03Что даёт отдельная сеть только для app и db?

Разбираем коротко

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

Нужно ли публиковать порт базы данных для доступа из соседнего контейнера?

Нет. Контейнеры в одной пользовательской сети обращаются к порту контейнера напрямую. Публикация нужна для доступа с host, из другой сети или извне.

Почему адрес контейнера нельзя записывать в конфигурацию приложения?

IP назначается динамически и способен измениться после пересоздания. В пользовательской сети используйте DNS-имя контейнера или имя сервиса Compose.

Ограничивает ли запись -p 127.0.0.1:8080:80 доступ только текущим сервером?

Да, порт привязывается к loopback Docker host. Для внешнего доступа обычно ставят reverse proxy на host и отдельно проверяют правила firewall.