Network drivers: bridge, host, none, overlay, macvlan
Контейнер по умолчанию изолирован — у него собственный network namespace, своя petля lo, своя таблица маршрутов. Чтобы он получил сеть, Docker подключает его к одному из network drivers. В Docker Engine 28 их пять стандартных: bridge, host, none, overlay, macvlan. Каждый — про разные компромиссы между изоляцией, скоростью и видимостью в LAN.
Список и быстрый обзор
docker network ls
# NETWORK ID NAME DRIVER SCOPE
# 7a8b9c4d2e1f bridge bridge local
# 2d4e8f1a0b3c host host local
# 1c2d3e4f5a6b none null local
Три сети существуют сразу после установки Docker:
bridge— default network, куда попадает любой контейнер без явного--network.host— особая сеть «нет изоляции, использовать host network stack».none— нет сети вообще, только loopback.
Можно создавать свои:
docker network create my-bridge # ещё один bridge, но user-defined
docker network create --driver overlay swarm-net # требует swarm mode
docker network create --driver macvlan \
--subnet=192.168.1.0/24 \
--gateway=192.168.1.1 \
-o parent=eth0 \
lan-net
Свитчи vs хабы — как кадр находит правильный порт
Bridge (default)
Самый частый driver. Технически — Linux bridge (docker0 для default-сети), к которому подключены veth-интерфейсы каждого контейнера. Из контейнера трафик идёт через eth0 -> vethXXX -> docker0 -> хост -> NAT -> внешний мир.
docker run --rm -it alpine sh
# Внутри:
ip addr show eth0
# inet 172.17.0.2/16 — типичный subnet для docker0
ip route
# default via 172.17.0.1 — это docker0
Между контейнерами в default bridge нет DNS-резолва по имени. Только по IP, который Docker не гарантирует (контейнер пересоздался — IP может смениться). Поэтому правило:
Для любых realных сценариев не используй default bridge. Создай user-defined bridge: docker network create mynet. На нём работает DNS by container name, и это меняет всё.
Host
Контейнер использует network stack хоста — никакого namespace, никаких veth. То же, что запустить процесс прямо на хосте.
docker run --rm --network host nginx:1.27-alpine
# nginx слушает 0.0.0.0:80 — но на хосте напрямую, без -p mapping.
# Если порт 80 на хосте занят — fail сразу.
Никакой публикации портов через -p — Docker её игнорирует. Контейнер сам решает, какие порты открыть, и они мгновенно на хосте.
| Когда полезен | Почему не подходит для большинства случаев |
|---|---|
| Performance-critical (Kafka broker, eBPF) | Нет изоляции — порт-конфликты с хостом |
| Контейнер должен видеть LAN напрямую | Контейнер видит ВСЕ интерфейсы хоста |
| Debug: контейнер делает много DNS-запросов | Атакующий внутри = почти full host access |
На macOS и Windows --network host работает частично: контейнер всё равно живёт в Linux VM, и «host» — это VM, а не твой macOS. То есть localhost:80 на маке не достучится до контейнера в host-network. Это известное расхождение.
None
Нет сети, кроме loopback. Контейнер изолирован полностью.
docker run --rm --network none alpine ping -c 2 8.8.8.8
# ping: bad address — нет route
Применений мало:
- Изолированные batch-jobs, которые читают данные из bind mount и пишут в bind mount, без сети.
- Кросс-компиляция (
docker run --network none ...). - Защитная мера для подозрительных образов: лучше запустить с
--network noneи потом дать сеть точечно.
NAT — как ваш домашний роутер прячет 50 устройств за одним IP
Overlay
Multi-host network. Контейнеры на разных физических хостах попадают в одну логическую сеть через VXLAN-туннели. Требует swarm mode или k8s (CNI-плагин).
# На swarm manager:
docker swarm init
docker network create -d overlay swarm-net
# Контейнер на ноде A:
docker service create --name web --network swarm-net nginx:1.27-alpine
# Контейнер на ноде B (на той же swarm-сети):
docker service create --name db --network swarm-net postgres:17
# web может пинговать db по имени, физически они на разных хостах.
Это уровень «production-кластер». Для junior DE — знать, что такое существует, и что k8s service-mesh устроен похожим образом (хотя там другой driver). Глубокое погружение в overlay — отдельная история про swarm.
Macvlan
Контейнер получает собственный MAC-адрес и IP в физической LAN. Снаружи он выглядит как ещё одно устройство в сети.
docker network create -d macvlan \
--subnet=192.168.1.0/24 \
--gateway=192.168.1.1 \
-o parent=eth0 \
lan-net
docker run --rm --network lan-net --ip 192.168.1.50 alpine ip addr
# eth0 имеет 192.168.1.50, MAC сгенерирован Docker'ом
Снаружи контейнер виден в LAN на IP 192.168.1.50, как будто это отдельная машина. Удобно, когда нужно дать контейнеру «настоящий» IP, например для IoT-устройств, которые ожидают broadcast в LAN.
Минусы:
- На некоторых сетевых картах не работает promiscuous mode — без него macvlan не функционирует.
- Контейнер с macvlan не видит host’а в LAN (известное ограничение Linux), потому что host выпадает из macvlan-loopback path.
- Сложен в setup, требует знаний LAN.
Для DE-задач macvlan нужен редко. Но знать, что такое есть, полезно — в production-стенде с устаревшими сервисами он иногда вылезает.
Сводная таблица
| Driver | Scope | Изоляция | DNS by name | Сложность | Типичный use |
|---|---|---|---|---|---|
| bridge | local | средняя | да (user-defined) | низкая | dev, compose |
| host | local | никакой | - | низкая | perf-edge |
| none | local | максимум | - | низкая | sandbox |
| overlay | swarm | средняя | да | высокая | multi-host |
| macvlan | local | средняя | нет | средняя | LAN-resident |
Что выбирает Docker по умолчанию
docker run -d --name pg postgres:17
# Куда подключился?
docker inspect pg --format '{{json .NetworkSettings.Networks}}'
# {"bridge":{...}}
Без явного --network — попадает в default bridge. На нём НЕТ DNS by container name. Чтобы получить адекватное окружение для dev:
docker network create etl
docker run -d --name pg --network etl postgres:17
docker run --rm --network etl alpine ping -c 2 pg
# pg резолвится в IP, ping проходит
Compose делает это автоматически. При запуске docker compose up создаётся network с именем <project>_default, и все сервисы подключаются туда. Поэтому в compose работает host: postgres из коробки, а в ручных docker run — нет (если не создал свою сеть).
Попробуй сам
# 1. Список сетей.
docker network ls
# 2. Inspect default bridge — какие контейнеры подключены?
docker network inspect bridge | grep -A 2 Containers
# 3. Создаём user-defined bridge с DNS.
docker network create demo-net
docker run -d --name a --network demo-net alpine sleep 1000
docker run -d --name b --network demo-net alpine sleep 1000
docker exec a ping -c 2 b
# 64 bytes from b (172.18.0.3): icmp_seq=1 ttl=64 time=0.05 ms
# DNS by container name работает.
# Сравним с default bridge:
docker rm -f a b
docker run -d --name a alpine sleep 1000
docker run -d --name b alpine sleep 1000
docker exec a ping -c 2 b
# ping: bad address 'b' — DNS не работает на default bridge
# 4. host network — порт сразу на хосте.
docker run -d --name h --network host nginx:1.27-alpine
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:80
# 200 — без -p mapping, на macOS не сработает.
# 5. none — изоляция.
docker run --rm --network none alpine sh -c 'wget -q -O - https://example.com'
# wget: bad address 'example.com' — сети нет
# Cleanup.
docker rm -f a b h
docker network rm demo-net
Default bridge оставлен для совместимости со старыми сценариями. Все compose-файлы и осмысленные docker run-команды должны создавать или использовать user-defined bridge. Считай default bridge «технический долг Docker».
В следующем уроке — bridge подробнее: docker0, veth, как работает -p 8080:80 через iptables.