Стоит поднять больше двух-трёх контейнеров, как встаёт вопрос: а как они вообще должны общаться друг с другом и с внешним миром? Docker предлагает несколько драйверов сети, и выбор не самого подходящего часто оборачивается либо проблемами с безопасностью, либо контейнерами, которые не видят друг друга. Разберём три главных варианта — bridge, host и overlay — и когда какой использовать.

Тестовый стенд — сервер srv-docker01 для одиночного хоста и три ноды srv-swarm01, srv-swarm02, srv-swarm03 для примеров с overlay, все в сети 192.168.10.0/24, домен itpages.lab.local.

Bridge: сеть по умолчанию для одного хоста

Bridge — самый распространённый режим, используется по умолчанию для контейнеров на одной машине. Docker создаёт виртуальный мост (обычно docker0), контейнеры получают собственные IP из отдельной подсети и общаются через NAT с внешним миром.

Важно различать сеть bridge по умолчанию и пользовательскую bridge-сеть — это не одно и то же. В сети по умолчанию контейнеры не резолвят друг друга по имени, только по IP:

docker network ls
docker network inspect bridge

Пользовательская bridge-сеть даёт встроенный DNS и изоляцию от других проектов на том же хосте:

docker network create --driver bridge \
  --subnet 172.20.0.0/24 \
  app-net

docker run -d --name db01 --network app-net mysql:8.0
docker run -d --name app01 --network app-net node:20-alpine sleep infinity

docker exec app01 getent hosts db01

Когда использовать: почти всегда, если у тебя один хост (srv-docker01) и несколько связанных контейнеров одного проекта. Это дефолтный и самый безопасный выбор для большинства задач — каждый проект в своей сети, никаких пересечений портов и случайных обращений между несвязанными сервисами.

Host: без изоляции сети вообще

Режим host убирает сетевую изоляцию контейнера полностью — контейнер использует сетевой стек хоста напрямую, без NAT и проброса портов. Это даёт максимальную производительность (нет накладных расходов на трансляцию адресов), но и максимум риска.

docker run -d --name web01 --network host nginx:1.27-alpine

Обрати внимание: флаг -p здесь не нужен и не будет работать — контейнер и так слушает порты хоста напрямую. Если nginx внутри слушает 80-й порт, он займёт 80-й порт именно на srv-docker01, и конфликт с другим сервисом на этом же порту приведёт к ошибке запуска.

Когда использовать: узкий круг случаев — высоконагруженные прокси и балансировщики, где важна каждая доля миллисекунды задержки, или инструменты мониторинга, которым нужен доступ к сетевым интерфейсам хоста. Для типичного веб-приложения или API host-режим избыточен и убирает полезную изоляцию без ощутимой выгоды.

Overlay: сеть поверх нескольких хостов

Overlay-сеть решает задачу, которую bridge решить не может, — связать контейнеры, запущенные на разных физических или виртуальных машинах, в единую логическую сеть. Это основа Docker Swarm: overlay-сеть работает поверх обычной сети между нодами (в нашем случае — поверх 192.168.10.0/24) и инкапсулирует трафик контейнеров в VXLAN-туннели.

Overlay-сети создаются на инициализированном Swarm-кластере. Сначала инициализируем Swarm на srv-swarm01:

docker swarm init --advertise-addr 192.168.10.41

После присоединения srv-swarm02 и srv-swarm03 (подробности — в статье про Docker Swarm) создаём overlay-сеть:

docker network create \
  --driver overlay \
  --attachable \
  --subnet 10.10.0.0/24 \
  swarm-net

Флаг --attachable позволяет подключать к этой сети не только Swarm-сервисы, но и обычные standalone-контейнеры — удобно для отладки. Разворачиваем сервис в этой сети:

docker service create \
  --name web \
  --network swarm-net \
  --replicas 3 \
  nginx:1.27-alpine

Три реплики сервиса разъедутся по трём нодам кластера, но благодаря overlay-сети и встроенному DNS они будут видеть друг друга по имени сервиса web вне зависимости от того, на какой физической ноде находятся.

Когда использовать: как только у тебя больше одной ноды и контейнерам с разных нод нужно общаться напрямую — то есть практически всегда, если ты используешь Docker Swarm или аналогичный оркестратор.

Сравнение и практический выбор

Если формулировать коротко: bridge — для одного хоста и большинства повседневных задач, host — для редких случаев, где критична производительность и можно пожертвовать изоляцией, overlay — как только речь заходит о кластере из нескольких нод. На srv-docker01 в качестве одиночного хоста разработки и тестирования держись пользовательских bridge-сетей по проектам. Для боевого кластера srv-swarm01–03 overlay становится обязательным элементом архитектуры.

Проверить, к каким сетям подключён контейнер, и какие адреса он получил, можно так:

docker inspect app01 --format '{{json .NetworkSettings.Networks}}' | python3 -m json.tool

Режим none: полная изоляция от сети

Есть и четвёртый, редко используемый вариант — сеть none. Контейнер в этом режиме получает только loopback-интерфейс и никак не взаимодействует ни с другими контейнерами, ни с внешним миром:

docker run -d --name isolated01 --network none alpine sleep infinity
docker exec isolated01 ip addr

Применяется в узких сценариях — например, для пакетной обработки данных, которая читает и пишет файлы через volume, но по своей природе не должна иметь сетевого доступа вообще (соображения безопасности при работе с недоверенными данными).

Macvlan: собственный MAC-адрес и IP из сети хоста

Ещё один драйвер, который стоит знать — macvlan. Он выдаёт контейнеру собственный MAC-адрес и позволяет получить IP-адрес напрямую из физической сети, как будто это отдельное устройство, а не процесс на хосте. Это удобно, когда контейнер должен быть виден в сети 192.168.10.0/24 как полноценный сосед, например для легаси-приложений, ожидающих прямого сетевого доступа без NAT:

docker network create -d macvlan \
  --subnet=192.168.10.0/24 \
  --gateway=192.168.10.1 \
  -o parent=eth0 \
  macvlan-net

docker run -d --name legacy01 \
  --network macvlan-net \
  --ip 192.168.10.60 \
  legacy-app:1.0

Минус в том, что по умолчанию хост и контейнер в режиме macvlan не видят друг друга напрямую (ограничение самого драйвера ядра Linux) — для большинства повседневных задач это делает macvlan менее удобным, чем bridge или overlay, и применять его стоит только когда действительно нужна именно такая топология.

Проброс портов подробнее

Раз уж речь зашла о сетях, стоит явно разобрать синтаксис проброса портов, с которым сталкивается каждый, кто работает с bridge-сетью. Базовый вариант публикует порт на всех интерфейсах хоста:

docker run -d -p 8080:80 nginx:1.27-alpine

Если нужно ограничить доступ только локальной сетью 192.168.10.0/24, а не всем интернетом (актуально для сервера с публичным IP), порт можно забиндить на конкретный внутренний адрес:

docker run -d -p 192.168.10.21:8080:80 nginx:1.27-alpine

Проброс диапазона портов и портов UDP делается так:

docker run -d -p 9000-9010:9000-9010 -p 5000:5000/udp myapp:1.0

Диагностика сетевых проблем между контейнерами

Когда два контейнера «не видят» друг друга, стандартный набор шагов диагностики выглядит так. Сначала убедись, что оба контейнера действительно в одной сети:

docker inspect app01 --format '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}'
docker inspect db01 --format '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}'

Если сети совпадают, но связи всё равно нет, проверь резолвинг DNS изнутри контейнера и доступность порта:

docker exec app01 nslookup db01
docker exec app01 sh -c "nc -zv db01 3306"

Если nslookup вообще не находит утилиты внутри минималистичного alpine-образа, временно поставь диагностический набор:

docker exec app01 sh -c "apk add --no-cache bind-tools netcat-openbsd"

Для более глубокой диагностики трафика между хостами можно подключиться к сетевому namespace контейнера напрямую и снять дамп трафика tcpdump — это уже уровень сетевого инженера, но иногда без него не обойтись, если проблема не в самом Docker, а в firewall между srv-docker01 и srv-app01.

Взаимодействие Docker с iptables и firewalld

Docker по умолчанию сам управляет правилами iptables для проброса портов и NAT — и это регулярно вызывает путаницу у администраторов, которые параллельно настраивают firewalld или ufw. Правила Docker добавляются в отдельную цепочку DOCKER и обычно применяются раньше, чем правила ufw, из-за чего попытка заблокировать порт контейнера через ufw может просто не сработать:

sudo iptables -L DOCKER -n -v

Если действительно нужно ограничить доступ к пробрасываемому порту контейнера по IP, правильнее делать это либо через параметр -p 192.168.10.21:8080:80 при запуске (привязка к конкретному интерфейсу), либо через явные правила в самой цепочке DOCKER-USER, которую Docker специально оставляет пустой для пользовательских правил и не перезаписывает при перезапуске демона:

sudo iptables -I DOCKER-USER -s 192.168.20.0/24 -j DROP

Настройка собственного DNS для контейнеров

Иногда контейнерам нужно резолвить внутренние имена лабораторного домена itpages.lab.local, которых нет ни во встроенном DNS Docker, ни в публичном DNS. Задать собственный DNS-сервер можно как глобально для демона, так и для конкретного контейнера. Глобально — через daemon.json:

{
  "dns": ["192.168.10.10", "8.8.8.8"]
}

Или точечно при запуске конкретного контейнера, если только ему нужно резолвить внутренние имена, а остальным — нет:

docker run -d --name app01 \
  --dns 192.168.10.10 \
  --dns-search itpages.lab.local \
  myapp:1.0

Это особенно актуально, когда контейнер должен обращаться не к другому контейнеру, а к внешнему по отношению к Docker сервису — например, к LDAP-серверу или почтовому релею, которые существуют только во внутренней зоне DNS домена.

Частые ошибки и как их избежать

  • Использование host-режима «на всякий случай». Это убирает сетевую изоляцию и повышает риск конфликтов портов между сервисами на одном хосте. Держи host-режим только для тех задач, где он реально обоснован.
  • Смешивание разных проектов в одной bridge-сети по умолчанию. Если не создавать отдельные пользовательские сети под каждый проект, контейнеры разных приложений могут случайно достучаться друг до друга. Разделяй сети по проектам.
  • Overlay-сеть без открытых нужных портов между нодами. Docker Swarm требует открытых портов 2377/tcp (управление), 7946/tcp и udp (discovery), 4789/udp (VXLAN для overlay). Если firewall между srv-swarm01–03 их блокирует, overlay-сеть работать не будет, а диагностировать это не всегда очевидно.
  • Использование macvlan без понимания ограничения хост-контейнер. Если после настройки macvlan хост неожиданно не может достучаться до собственного контейнера напрямую — это не баг, а особенность драйвера. При необходимости такого доступа добавляй дополнительный macvlan-интерфейс на самом хосте.

Что дальше

Docker редко используют в одиночку — вот что обычно настраивают рядом: