← назад к разделу

Запустить один контейнер — просто. Но реальное приложение почти всегда состоит из нескольких: веб-сервис, база данных, кэш. Им нужно общаться между собой — и здесь важно понять, как 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

Как это выглядит в связке: схема

diagram

Клиент видит только порт хоста. База данных спрятана внутри сети Docker — снаружи недоступна.

Коротко

  • Каждый контейнер работает в изолированном сетевом пространстве: у него свой localhost, и он не видит соседей без явной настройки.
  • Стандартная bridge-сеть даёт связь по IP, но не по имени — для продакшн-стека она не подходит.
  • Пользовательская bridge-сеть — правильный выбор: DNS по имени контейнера, изоляция, гибкое подключение.
  • Порты публикуются наружу через -p хост:контейнер; внутренние сервисы (БД, кэш) публиковать не нужно.
  • localhost внутри контейнера — это сам контейнер, не хост и не соседний сервис.
  • --network host убирает изоляцию, --network none полностью отключает сеть.

Что почитать дальше

  • Запуск контейнеров — флаги docker run, переменные окружения, режимы работы.
  • Docker Compose — декларативное описание многоконтейнерных приложений; сети создаются автоматически.
  • Тома и данные — как хранить данные за пределами контейнера.