Docker — платформа для контейнеризации приложений. Контейнеризация — упаковка программы со всеми её зависимостями в изолированную среду, собственно, контейнер. В отличие от виртуальной машины, которая эмулирует целый компьютер с отдельной ОС, контейнер использует ядро хост‑системы и изолирует только процессы приложения.
В мире контейнеризации Docker сеть — не просто способ подключения к интернету. Это механизм, который определяет, как контейнеры видят друг друга, хост и внешние сети. Одним из базовых, но ключевых элементов этой архитектуры является bridge-сеть — программный сетевой мост, который по умолчанию связывает контейнеры на одном Docker-хосте.
Bridge-сеть обеспечивает:
- изоляцию контейнеров от остальной сети,
- возможность взаимодействия контейнеров внутри одной подсети,
- NAT-маршрутизацию для выхода в интернет,
- публикацию портов и доступ извне при необходимости.
В этой статье мы подробно разберём, как устроена bridge-сеть в Docker, как пакеты проходят через неё и что происходит “под капотом”, когда контейнер общается с внешним миром или с другим контейнером на том же хосте.
Общая архитектура bridge-сети #
Текущие настройки сети на сервере с Docker #
# Просмотр запущенные контейнеры
~# docker ps
CONTAINER ID IMAGE PORTS NAMES
31941b774406 nginx 0.0.0.0:8082->80/tcp, [::]:8082->80/tcp test-nginx
cf2a84c799b4 docker/getting-started 0.0.0.0:80->80/tcp, [::]:8080->80/tcp test-tutorial
# Просмотр полной конфигурации контейнера
~# docker inspect test-nginx | jq .
# Просмотр только сетевой конфигурации контейнера
~# docker inspect --format='{{json .NetworkSettings}}' test-nginx | jq .
# Просмотр veth-интерфейса контейнера
~# docker inspect --format='{{.NetworkSettings.SandboxKey}}' test-nginx | xargs basename
# Просмотр сетевых интерфейсов внутри контейнера
~# nsenter -t $(docker inspect --format='{{.State.Pid}}' test-nginx) -n ip addr
# Просмотр сетевых интерфейсов на сервере
~# ip -br a
lo UNKNOWN 127.0.0.1/8 ::1/128
eth0 UP 10.130.0.5/24 metric 100 fe80::d20d:1dff:fe15:a32f/64
docker0 UP 172.17.0.1/16 fe80::80f8:64ff:fe5b:40d9/64
veth883b6aa@if2 UP fe80::9cda:1dff:fe14:4d9/64
veth8027757@if2 UP fe80::dc1a:d4ff:fe20:d925/64
# Просмотр bridge-интерфейсов с подключенными veth
~# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.82f8645b40d9 no veth883b6aa
veth8027757
# Просмотр какие сети доступы в Docker
~# docker network ls
NETWORK ID NAME DRIVER SCOPE
03478b020492 bridge bridge local
081f803a9ecb host host local
58f54b28e459 none null local
# Просмотр подключенных контейнеров к мосту bridge и имя моста системе (docker0)
~# docker network inspect bridge | jq .
[
{
"Name": "bridge",
"Id": "143c484b8cbb717475dbd1f113f6da0e21d29561c2520b288b99bc69d3e5fbdb",
"Created": "2026-02-22T17:17:52.117740329Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv4": true,
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"31941b774406e219a23464c09df814f6c835d7d559ccd85d57d79694caac09d1": {
"Name": "test-nginx",
"EndpointID": "5cf9d13c0c334ad68b2e4950725c300590ac3f8f8b98ca86a6f65a70dcec4478",
"MacAddress": "8e:29:57:b7:a2:84",
"IPv4Address": "172.17.0.6/16",
"IPv6Address": ""
},
"cf2a84c799b46ed1a8287f589b4bdfe3fceeeb8f8b5f6d4ad76df8b982c9c23c": {
"Name": "test-tutorial",
"EndpointID": "a4654cd43299cc7b3b96b75bffc3f7450182ca98370fe4c475a9493c26297332",
"MacAddress": "a6:3d:2b:1c:60:84",
"IPv4Address": "172.17.0.5/16",
"IPv6Address": ""
}
},
"Options": {
"com.docker.network.bridge.default_bridge": "true",
"com.docker.network.bridge.enable_icc": "true",
"com.docker.network.bridge.enable_ip_masquerade": "true",
"com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
"com.docker.network.bridge.name": "docker0",
"com.docker.network.driver.mtu": "1500"
},
"Labels": {}
}
]
# Просмотр какой veth-интерфейс на хосту к какому контейнеру относится
for container in $(docker ps -q); do
echo "Контейнер: $(docker inspect --format='{{.Name}}' $container | cut -c2-)"
echo "IP: $(docker inspect --format='{{.NetworkSettings.IPAddress}}' $container)"
echo "veth: $(docker inspect --format='{{.NetworkSettings.SandboxKey}}' $container | xargs basename 2>/dev/null || echo 'N/A')"
echo "---"
done
Путешествие пакета: как трафик проходит через Docker bridge #
Исходящий трафик (контейнер → Интернет):
172.17.0.5:42495 -(SNAT)-> 10.130.0.5:42495 -(SNAT)-> 158.160.151.151:42495 -(Интернет)-> 137.74.42.42:80
Входящий трафик (Интернет → контейнер):
137.74.42.42:45448 -(Интернет)-> 158.160.151.151:80 -(DNAT)-> 10.130.0.5:80 -(DNAT)-> 172.17.0.5:80
Трафик из контейнера в Интернет #
Генерируем запрос через утилиту netcat: nc -lv 80 и nc -vz 137.74.42.42 80
Источник: 172.17.0.5:42495 (контейнер)
Назначение: 137.74.42.42:80 (внешний сервер)
Путь исходящего пакета
┌─────────────┐ 1. SYN ┌─────────────┐ 2. SYN ┌─────────────┐
│ Контейнер │ ──────────> │ veth пара │ ──────────> │ docker0 │
│ 172.17.0.5 │ │ veth883b6aa │ │ bridge │
└─────────────┘ └─────────────┘ └──────┬──────┘
3. SYN│
(SNAT)│
↓
┌─────────────┐ 5. SYN ┌─────────────┐ 4. SYN ┌─────────────┐
│ Внешний │ <────────── │ eth0 │ <────────── │ NAT │
│ сервер │ │ 10.130.0.5 │ │ iptables │
│ 137.74.42.42│ └─────────────┘ └─────────────┘
└─────────────┘
Реальный дамп трафика (исходящее соединение)
# TCP SYN (SNAT)
# Three-Way Handshake
veth883b6aa P IP 172.17.0.5.42495 > 137.74.42.42.80: Flags [S]
docker0 In IP 172.17.0.5.42495 > 137.74.42.42.80: Flags [S]
eth0 Out IP 10.130.0.5.42495 > 137.74.42.42.80: Flags [S]
# TCP SYN-ACK (DNAT)
eth0 In IP 137.74.42.42.80 > 10.130.0.5.42495: Flags [S.]
docker0 Out IP 137.74.42.42.80 > 172.17.0.5.42495: Flags [S.]
veth883b6aa Out IP 137.74.42.42.80 > 172.17.0.5.42495: Flags [S.]
# TCP ACK (SNAT)
veth883b6aa P IP 172.17.0.5.42495 > 137.74.42.42.80: Flags [.]
docker0 In IP 172.17.0.5.42495 > 137.74.42.42.80: Flags [.]
eth0 Out IP 10.130.0.5.42495 > 137.74.42.42.80: Flags [.]
# TCP FIN-ACK (SNAT)
# Four-Way Handshake
veth883b6aa P IP 172.17.0.5.42495 > 137.74.42.42.80: Flags [F.]
docker0 In IP 172.17.0.5.42495 > 137.74.42.42.80: Flags [F.]
eth0 Out IP 10.130.0.5.42495 > 137.74.42.42.80: Flags [F.]
# TCP ACK + FIN-ACK (DNAT)
eth0 In IP 137.74.42.42.80 > 10.130.0.5.42495: Flags [.]
docker0 Out IP 137.74.42.42.80 > 172.17.0.5.42495: Flags [.]
veth883b6aa Out IP 137.74.42.42.80 > 172.17.0.5.42495: Flags [.]
eth0 In IP 137.74.42.42.80 > 10.130.0.5.42495: Flags [F.]
docker0 Out IP 137.74.42.42.80 > 172.17.0.5.42495: Flags [F.]
veth883b6aa Out IP 137.74.42.42.80 > 172.17.0.5.42495: Flags [F.]
# TCP ACK (SNAT)
veth883b6aa P IP 172.17.0.5.42495 > 137.74.42.42.80: Flags [.]
docker0 In IP 172.17.0.5.42495 > 137.74.42.42.80: Flags [.]
eth0 Out IP 10.130.0.5.42495 > 137.74.42.42.80: Flags [.]
Трафик из Интернета в контейнер #
Генерируем запрос через утилиту netcat: nc -lv 80 и nc -vz 158.160.151.151 80
Источник: 137.74.42.42.45448 (внешний сервер)
Назначение: 172.17.0.5:80:80 (контейнер) nc -lv 80
Путь входящего пакета
┌─────────────┐ 1. SYN ┌─────────────┐ 2. SYN ┌─────────────┐
│ Внешний │ ──────────> │ eth0 │ ──────────> │ NAT │
│ клиент │ │ 10.130.0.5 │ │ iptables │
│ 137.74.42.42│ └─────────────┘ └──────┬──────┘
└─────────────┘ 3. SYN│
(DNAT)│
↓
┌─────────────┐ 5. SYN ┌─────────────┐ 4. SYN ┌─────────────┐
│ Контейнер │ <────────── │ veth пара │ <────────── │ docker0 │
│ 172.17.0.5 │ │ veth883b6aa │ │ bridge │
└─────────────┘ └─────────────┘ └─────────────┘
Реальный дамп трафика (входящее соединение)
# TCP SYN (DNAT)
# Three-Way Handshake
eth0 In IP 137.74.42.42.45448 > 10.130.0.5.80: Flags [S]
docker0 Out IP 137.74.42.42.45448 > 172.17.0.5.80: Flags [S]
veth883b6aa Out IP 137.74.42.42.45448 > 172.17.0.5.80: Flags [S]
# TCP SYN-ACK (SNAT)
veth883b6aa P IP 172.17.0.5.80 > 137.74.42.42.45448: Flags [S.]
docker0 In IP 172.17.0.5.80 > 137.74.42.42.45448: Flags [S.]
eth0 Out IP 10.130.0.5.80 > 137.74.42.42.45448: Flags [S.]
# TCP ACK (DNAT)
eth0 In IP 137.74.42.42.45448 > 10.130.0.5.80: Flags [.]
docker0 Out IP 137.74.42.42.45448 > 172.17.0.5.80: Flags [.]
veth883b6aa Out IP 137.74.42.42.45448 > 172.17.0.5.80: Flags [.]
# TCP FIN-ACK (DNAT)
# Four-Way Handshake
eth0 In IP 137.74.42.42.45448 > 10.130.0.5.80: Flags [F.]
docker0 Out IP 137.74.42.42.45448 > 172.17.0.5.80: Flags [F.]
veth883b6aa Out IP 137.74.42.42.45448 > 172.17.0.5.80: Flags [F.]
# TCP FIN-ACK (SNAT)
veth883b6aa P IP 172.17.0.5.80 > 137.74.42.42.45448: Flags [F.]
docker0 In IP 172.17.0.5.80 > 137.74.42.42.45448: Flags [F.]
eth0 Out IP 10.130.0.5.80 > 137.74.42.42.45448: Flags [F.]
# TCP ACK (DNAT)
eth0 In IP 137.74.42.42.45448 > 10.130.0.5.80: Flags [.]
docker0 Out IP 137.74.42.42.45448 > 172.17.0.5.80: Flags [.]
veth883b6aa Out IP 137.74.42.42.45448 > 172.17.0.5.80: Flags [.]
Трафик из контейнера в контейнер #
# ICMP echo request
veth883b6aa P IP 172.17.0.5 > 172.17.0.6: ICMP echo request
veth8027757 Out IP 172.17.0.5 > 172.17.0.6: ICMP echo request
# ICMP echo reply
veth8027757 P IP 172.17.0.6 > 172.17.0.5: ICMP echo reply
veth883b6aa Out IP 172.17.0.6 > 172.17.0.5: ICMP echo reply
Полезная статья 1 (оригинал)
Полезная статья 1 (перевод)
Полезная статья 2