Запустить один контейнер — просто. Но реальное приложение почти всегда состоит из нескольких: веб-сервис, база данных, кэш. Им нужно общаться между собой — и здесь важно понять, как Docker организует сетевое окружение контейнеров.
Зачем контейнерам своя сеть
По умолчанию каждый контейнер запускается в изолированном сетевом пространстве имён (network namespace). Это означает, что у контейнера есть собственный сетевой стек: свой адрес, свои порты, свой петлевой интерфейс (lo). Процессы внутри контейнера не видят сетевые интерфейсы хоста напрямую.
Такая изоляция даёт два важных свойства:
- Безопасность: контейнер не может случайно занять порт хоста или соседнего контейнера без явного разрешения.
- Предсказуемость: приложение внутри контейнера всегда видит одинаковое окружение — вне зависимости от того, что запущено на хосте рядом.
Короткая формула: контейнер = отдельная машина в сети, которую Docker сам соединяет с другими.
Bridge-сеть по умолчанию
Когда вы запускаете контейнер командой docker run без дополнительных флагов, Docker подключает его к bridge-сети по умолчанию — виртуальному коммутатору с именем bridge (на хосте виден как интерфейс docker0).
docker network ls
# NETWORK ID NAME DRIVER SCOPE
# abc123def456 bridge bridge local
# ...
Контейнеры в сети bridge могут достучаться друг до друга по IP-адресу, но не по имени. Адреса назначаются динамически при каждом запуске, а значит жёстко прописать их нельзя.
Пример: запускаем два контейнера и пробуем связь по имени:
docker run -d --name app eclipse-temurin:21-jre java -jar /app.jar
docker run -d --name db postgres:16
# Изнутри app обратиться к db по имени не получится —
# имя не резолвится в стандартной bridge-сети
Именно здесь лежит типичная ошибка: разработчик пишет в конфиге приложения localhost в расчёте достучаться до базы данных в соседнем контейнере. Но localhost внутри контейнера — это петлевой интерфейс самого контейнера, а не хоста. База данных там не слушает.
User-defined bridge и DNS по имени
Решение — создать пользовательскую bridge-сеть (user-defined bridge). В такой сети Docker автоматически поднимает встроенный DNS-резолвер: контейнеры обращаются друг к другу по имени контейнера или псевдониму сервиса.
# Создаём сеть
docker network create backend-net
# Запускаем базу в этой сети
docker run -d \
--name db \
--network backend-net \
postgres:16
# Запускаем приложение в той же сети
docker run -d \
--name app \
--network backend-net \
-e SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/mydb \
eclipse-temurin:21-jre java -jar /app.jar
Теперь app может обратиться к db по имени — Docker сам разрешит имя db в нужный IP-адрес. Это работает, потому что пользовательские сети имеют встроенный DNS, которого нет в сети bridge по умолчанию.
Преимущества user-defined bridge перед стандартной
| Свойство | bridge (по умолчанию) | user-defined bridge |
|---|---|---|
| Связь по IP | да | да |
| DNS по имени контейнера | нет | да |
| Изоляция от других сетей | нет | да |
| Возможность переподключить контейнер | нет | да |
Публикация портов наружу: флаг -p
Контейнер изолирован от хоста. Чтобы внешний мир (браузер, клиент, другой сервис вне Docker) мог достучаться до порта контейнера, нужно опубликовать порт через флаг -p:
docker run -d \
--name app \
--network backend-net \
-p 8080:8080 \
eclipse-temurin:21-jre java -jar /app.jar
# ^^^^ ^^^^
# хост контейнер
Формат: -p <порт_на_хосте>:<порт_в_контейнере>.
Теперь запрос на http://localhost:8080 хоста попадёт в порт 8080 контейнера.
Важно: базу данных (db) публиковать наружу не нужно — app обращается к ней через внутреннюю сеть Docker, а пробрасывать порт PostgreSQL в открытый интерфейс — риск безопасности.
# db остаётся только во внутренней сети — наружу порт не пробрасываем
docker run -d \
--name db \
--network backend-net \
postgres:16
Изоляция сетей
Контейнеры, подключённые к разным пользовательским сетям, не видят друг друга — даже если работают на одном хосте. Это удобно, когда нужно запустить несколько независимых стеков (например, два проекта) на одной машине без конфликтов.
docker network create project-a
docker network create project-b
# Контейнеры в project-a и project-b изолированы друг от друга
Один контейнер можно подключить к нескольким сетям сразу — например, API-шлюз, который должен общаться и с фронтом, и с бэкендом:
docker network connect project-b gateway
Режимы host и none
Кроме bridge, Docker поддерживает ещё два режима, которые стоит знать:
--network host — контейнер использует сетевой стек хоста напрямую, без изоляции. Полезно для диагностики или высоконагруженных случаев, когда накладные расходы bridge критичны. Работает только на Linux; на macOS и Windows — нет полноценной поддержки.
docker run --network host nginx
# Nginx слушает на портах хоста напрямую, без -p
--network none — контейнер полностью изолирован от сети. Используется для задач, которым сеть не нужна вообще: криптографические вычисления, офлайн-обработка данных.
docker run --network none alpine sh
Как это выглядит в связке: схема
Клиент видит только порт хоста. База данных спрятана внутри сети Docker — снаружи недоступна.
Коротко
- Каждый контейнер работает в изолированном сетевом пространстве: у него свой
localhost, и он не видит соседей без явной настройки. - Стандартная
bridge-сеть даёт связь по IP, но не по имени — для продакшн-стека она не подходит. - Пользовательская bridge-сеть — правильный выбор: DNS по имени контейнера, изоляция, гибкое подключение.
- Порты публикуются наружу через
-p хост:контейнер; внутренние сервисы (БД, кэш) публиковать не нужно. localhostвнутри контейнера — это сам контейнер, не хост и не соседний сервис.--network hostубирает изоляцию,--network noneполностью отключает сеть.
Что почитать дальше
- Запуск контейнеров — флаги
docker run, переменные окружения, режимы работы. - Docker Compose — декларативное описание многоконтейнерных приложений; сети создаются автоматически.
- Тома и данные — как хранить данные за пределами контейнера.