← Docker
14 мин · средне · Урок 6 из 8

Сети Docker

bridge default vs user-defined, DNS по имени сервиса, host и none, port publishing. Почему два разных network не видят друг друга.

Драйверы сети

Docker умеет несколько драйверов сети. На практике три из них реально используются.

DriverКогдаОсобенности
bridge defaultdocker run без флаговконтейнеры видят друг друга только по IP, DNS нет
bridge user-definedcompose создаёт сам, или docker network createвстроенный DNS, ходишь по имени сервиса. Дефолт.
host--network=hostконтейнер делит сетевой namespace с хостом. На macOS/Windows — с оговоркой, см. ниже
none--network=noneсети нет вообще, только loopback
overlaySwarmсеть между нодами кластера
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. Он:

  1. Сначала проверяет, есть ли контейнер с таким именем в той же сети.
  2. Если нет — форвардит запрос на 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 - напрямую к базе достучаться не может.

общая сеть app-net web db dig db работает две разные сети frontend web backend db NXDOMAIN localhost внутри контейнера web curl localhost:5432 → 127.0.0.1 это сам web → нет postgres → connection refused

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

Важные детали:

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 не резолвится:

  1. docker network inspect <name> — кто реально в этой сети.
  2. docker inspect <container> --format '{{json .NetworkSettings.Networks}}'
    • к каким сетям подключён контейнер.
  3. docker exec <container> cat /etc/resolv.conf — должен быть nameserver 127.0.0.11 для user-defined bridge.
  4. Если в контейнере нет 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.

Дрилл · вопросы как на собесе

Проверь себя без подсказок

Сформулируй ответ вслух или письменно, потом раскрой и честно отметь: знал или нет.

01

Как контейнеры общаются между собой?

DOC-014
02

Какие типы сетей бывают у Docker?

DOC-022

Прошли материал до конца? Отметьте урок пройденным.