Сети Docker
bridge default vs user-defined, DNS по имени сервиса, host и none, port publishing. Почему два разных network не видят друг друга.
Драйверы сети
Docker умеет несколько драйверов сети. На практике три из них реально используются.
| Driver | Когда | Особенности |
|---|---|---|
bridge default | docker run без флагов | контейнеры видят друг друга только по IP, DNS нет |
bridge user-defined | compose создаёт сам, или docker network create | встроенный DNS, ходишь по имени сервиса. Дефолт. |
host | --network=host | контейнер делит сетевой namespace с хостом. На macOS/Windows — с оговоркой, см. ниже |
none | --network=none | сети нет вообще, только loopback |
overlay | Swarm | сеть между нодами кластера |
macvlan | редко | контейнер виден в локалке как обычный хост со своим MAC |
В большинстве случаев тебе нужен user-defined bridge, и compose создаёт его автоматически. Отдельно поговорим про host и про дефолтный bridge, потому что про них чаще всего путаются.
Дефолтный bridge — почти бесполезен
Когда ты делаешь docker run без флагов сети, контейнер попадает в
сеть bridge — дефолтную, которую Docker поднимает при первом запуске.
В ней есть нюанс: встроенного DNS там нет.
docker run -d --name db postgres:16
docker run -d --name app myapp # обе в дефолтном bridge
docker exec app ping db # ping: bad address 'db'
app не знает про db. Только по IP, который ты в продакшен прописывать
не будешь. Поэтому дефолтный bridge — почти всегда не то, что нужно.
user-defined bridge — что используют все
docker network create app-net
docker run -d --network app-net --name db postgres:16
docker run -d --network app-net --name app myapp
docker exec app ping db # 64 bytes from db.app-net: ...
db резолвится в IP контейнера. Это встроенный DNS Docker. Если
заглянуть в контейнер:
docker exec app cat /etc/resolv.conf
# nameserver 127.0.0.11
Адрес 127.0.0.11 — это служебный DNS-резолвер, который слушает внутри
каждого контейнера на user-defined bridge. Он:
- Сначала проверяет, есть ли контейнер с таким именем в той же сети.
- Если нет — форвардит запрос на DNS хоста.
Поэтому db резолвится локально, а google.com уходит наружу.
Compose автоматически создаёт user-defined bridge при docker compose up. Поэтому в compose-файлах сервисы ходят друг к другу по имени без
дополнительной настройки.
Ловушка: разные сети не видят друг друга
Самый частый источник «connection refused» в compose — сервисы
лежат в разных блоках networks. DNS Docker резолвит имена внутри
одной сети, между сетями он не ходит.
Плохо:
services:
web:
image: api
networks: [frontend]
db:
image: postgres:16
networks: [backend]
networks:
frontend:
backend:
web и db физически в разных сетях. web сделает dig db, получит
NXDOMAIN, упадёт.
Лечение — положить в общую сеть:
services:
web:
image: api
networks: [app-net]
db:
image: postgres:16
networks: [app-net]
networks:
app-net:
Если нужны изолированные сети (admin отдельно от приложения), сервис можно добавить в несколько сетей сразу:
services:
web:
networks: [public-net, db-net]
db:
networks: [db-net]
admin:
networks: [public-net]
Тогда web видит и db, и admin, а admin видит только web -
напрямую к базе достучаться не может.
host network: без изоляции
docker run --network=host nginx
Контейнер делит сетевой namespace с хостом. Ему не нужен -p, он сразу
слушает на портах хоста. Изоляции по сети нет. Это нужно редко: для
прод-сервисов, где важна минимальная задержка, или для агентов
мониторинга, которым нужен прямой доступ к интерфейсам.
Важное: на Linux «хост» — это буквально твоя машина. На macOS и Windows
Docker Desktop крутит виртуалку, и host — это сеть внутри неё.
Исторически это значило, что порты такого контейнера с ноута не видно
вообще. В Docker Desktop 4.34 (2024) появилась поддержка host
networking и для macOS/Windows: порты контейнера пробрасываются на
localhost хоста. Включается в настройках (Resources → Network → Enable
host networking) и требует входа в Docker-аккаунт.
Полного паритета с Linux всё равно нет: интерфейсы, которые видит контейнер, принадлежат виртуалке. Если приложению нужны реальные NIC машины (мониторинг, discovery по broadcast) — это работает только на Linux.
none: без сети вообще
docker run --network=none alpine
У контейнера остаётся только loopback. В интернет не выйдет, никто к нему не достучится. Подходит для одноразовых задач, которые не должны ничего слать наружу.
Port publishing: -p host:container
Чтобы достучаться до контейнера из хоста (или из интернета, если хост смотрит наружу), порты надо опубликовать:
docker run -d -p 8080:80 nginx
# теперь curl http://localhost:8080 -> внутрь контейнера на порт 80
Важные детали:
-p 8080:80слушает на0.0.0.0:8080— доступен снаружи, если у хоста есть внешний IP. Это часто не то, что хочешь на dev-машине.-p 127.0.0.1:8080:80слушает только на loopback. Снаружи не достать, это безопасный дефолт для локальной разработки.- На macOS и Windows публикация идёт через виртуалку Docker Desktop: порт
виден с твоего хоста, но
0.0.0.0относится к VM, а не к сетевым интерфейсам ноута, поэтому «наружу» оно так просто не торчит. - В compose — блок
ports:с теми же форматами.
services:
web:
image: nginx
ports:
- "127.0.0.1:8080:80" # только локально
- "443:443" # снаружи тоже
EXPOSE не публикует порт
Соблазн: «в Dockerfile написано EXPOSE 80, значит, порт уже открыт».
Нет. EXPOSE — это только документация, метаданные в манифесте.
Чтобы порт был доступен с хоста, всё равно нужен -p или ports: в
compose.
EXPOSE полезен только в двух случаях: документация для других
разработчиков и эвристика для docker run -P (большая P) — тогда
docker сам опубликует все EXPOSE-порты на случайных портах хоста. На
практике -P используют редко.
Диагностика когда не работает
Краткий чек-лист, когда connection refused или DNS не резолвится:
docker network inspect <name>— кто реально в этой сети.docker inspect <container> --format '{{json .NetworkSettings.Networks}}'- к каким сетям подключён контейнер.
docker exec <container> cat /etc/resolv.conf— должен быть nameserver127.0.0.11для user-defined bridge.- Если в контейнере нет
dig,curl,ping(типично для distroless и alpine) — подними сайдкар:docker run --rm -it --network container:<имя> nicolaka/netshoot. Он делит сетевой namespace с твоим контейнером, и у тебя сразу есть все инструменты.
Порядок диагностики: сначала inspect (правильная ли сеть), потом DNS
(резолвится ли имя), потом tcpdump (доходит ли пакет). В этом порядке быстрее всего.
Что важно запомнить
Дефолтный bridge без DNS. user-defined bridge с DNS — это твой дефолт, и compose поднимает именно его.
Два сервиса в разных networks не видят друг друга. Положи в общую
сеть или подключи к нескольким сразу.
localhost внутри контейнера — это сам контейнер. До соседа -
только по имени сервиса.
-p host:container публикует порт. EXPOSE — только метаданные.
В следующем уроке свяжем всё через Docker Compose.
Дрилл · вопросы как на собесе
Проверь себя без подсказок
Сформулируй ответ вслух или письменно, потом раскрой и честно отметь: знал или нет.
Как контейнеры общаются между собой?
DOC-014Какие типы сетей бывают у Docker?
DOC-022Прошли материал до конца? Отметьте урок пройденным.
Прогресс сохранён в этом браузере
Почистишь историю или откроешь с телефона, и отметки пропадут. Оставь почту, они переедут в аккаунт. Пароль придумывать не нужно.
Готово. теперь в аккаунте: прогресс виден с любого устройства. Мой прогресс