SSD Nodes Learn Hosting plans →
Руководства Matt ConnorАвтор: Matt Connor · Обновлено 2026-08-07

Как работает сеть в Docker Compose

Узнайте, как Docker Compose создает мост для сервисов, обеспечивает DNS по именам и почему порты в режиме host обходят UFW. Разбор работы сетей и типичных ошибок.

Что Docker Compose создает до запуска вашего приложения

Работа сети в Docker Compose строится на одном правиле: docker compose up создает частную сеть для проекта, подключает к ней каждый сервис и позволяет этим сервисам взаимодействовать друг с другом по имени сервиса. Вам не нужно писать ни одной строки networks:, чтобы это заработало. Большая часть путаницы вокруг сети в Compose возникает из-за того, что пользователи не знают о наличии настроек по умолчанию.

Вот небольшой файл. Сохраните его как compose.yaml в директории с названием shop.

services:
  web:
    image: nginx:1.27
    ports:
      - "8080:80"
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: example

Запустите его и посмотрите, что создал Docker:

docker compose up -d
docker network ls

В списке теперь есть сеть с названием shop_default. Compose называет её <project>_default, а имя проекта по умолчанию совпадает с именем директории в нижнем регистре. Его можно переопределить с помощью docker compose -p myproject up -d или через верхнеуровневый параметр name: myproject в файле. Драйвер сети — bridge, который представляет собой виртуальный коммутатор внутри хоста. Каждый контейнер получает адрес в частной подсети, а исходящий трафик при выходе транслируется в адрес хоста.

Команда docker compose down удаляет эту сеть. Именно поэтому «зависший» контейнер из старого проекта может удерживать сеть открытой: Docker выдаст ошибку error while removing network: network shop_default has active endpoints, и для её исправления нужно остановить или удалить контейнер, который всё ещё подключен к этой сети.

Если вы только начинаете работать с Compose, сначала стоит ознакомиться с структурой Compose-файла и командами жизненного цикла, так как всё, что описано ниже, предполагает, что вы умеете запускать и останавливать проект.

DNS по имени сервиса — это то, что часто упускают новички

В любой пользовательской сети Docker запускает встроенный DNS-сервер, который каждый контейнер видит по адресу 127.0.0.11. Он преобразует имена сервисов в текущие адреса контейнеров. Таким образом, web обращается к базе данных по имени хоста db на порту 5432 без какой-либо дополнительной настройки.

docker compose exec web getent hosts db

Эта команда выводит строку вида 172.18.0.2 db. Если вывод пуст, значит, сервисы находятся в разных сетях.

Ошибка, которую совершают почти все, — использование localhost в конфигурации приложения. Внутри контейнера localhost — это сам контейнер, а не хост и не другой сервис. Клиенты Postgres сообщают об этом достаточно ясно:

could not connect to server: Connection refused
	Is the server running on host "localhost" (127.0.0.1) and accepting
	TCP connections on port 5432?

Строка подключения должна выглядеть как postgresql://postgres:example@db:5432/postgres. Часть с именем хоста — это имя сервиса.

Две детали, которые сэкономят время в будущем. Имена разрешаются в то, что запущено в данный момент, поэтому docker compose up -d --scale web=3 выдаст одно имя с тремя адресами, и клиент, который кэширует DNS бесконечно долго, привяжется к нерабочему контейнеру. Кроме того, устаревшая сеть bridge, используемая при обычном docker run без --network, вообще не поддерживает разрешение имен, поэтому советы по связыванию контейнеров (links) из 2016 года не соответствуют тому, что вы видите сейчас.

Вам не нужен ports: для соединения двух сервисов

ports: публикует порт контейнера на хосте. Это необходимо для трафика, поступающего извне Docker. Это не имеет отношения к обмену данными между сервисами, который уже работает во всём диапазоне портов внутри сети проекта.

Поэтому ports: - "5432:5432", который многие добавляют к сервису базы данных, не приносит пользы и даже вредит: он открывает Postgres на публичном интерфейсе сервера. Удалите его. Если вам нужен доступ к базе с вашего ноутбука для миграции, привяжите её к loopback с помощью "127.0.0.1:5432:5432" и подключайтесь через SSH-туннель. Разница между слушающим сокетом, опубликованным портом и правилом межсетевого экрана описана в как работают порты и слушающие сервисы в Linux.

expose: в Compose является только документацией. Он ничего не открывает, так как между контейнерами в одной сети изначально ничего не было закрыто.

Когда network_mode host оправдан и во что это обходится

Режим host отключает собственное сетевое пространство имен контейнера и позволяет процессу напрямую использовать интерфейсы хоста.

services:
  probe:
    image: alpine:3.20
    network_mode: host
    command: sleep infinity

Существуют веские причины для использования этого режима. Процесс, которому необходимо видеть широковещательный (broadcast) или многоадресный (multicast) трафик в локальной сети, например, для обнаружения устройств медиасервером или хабом умного дома, не увидит его из-за моста, так как мост не передает такой трафик в контейнер. Агенту мониторинга, считывающему счетчики интерфейсов хоста, необходим доступ к этим интерфейсам. Кроме того, вы исключаете этап трансляции адресов, что важно при высокой интенсивности пакетов.

Издержки вполне конкретны.

ports: перестает работать. Docker предупреждает, что при использовании сетевого режима host опубликованные порты игнорируются, и контейнер привязывается к тому порту, который использует его процесс. Два контейнера в режиме host, пытающиеся занять порт 8080, конфликтуют, и второй завершается с ошибкой bind: address already in use.

Разрешение имен по имени сервиса перестает работать в обе стороны. Контейнер не находится в сети проекта, поэтому он не может разрешить db, а другие сервисы не могут разрешить его имя. Он достигает их только через порты, опубликованные на хосте, обычно по адресу 127.0.0.1.

Изоляция пропадает. Процесс, который привязывается к 0.0.0.0 внутри контейнера в режиме host, начинает прослушивать все интерфейсы вашего сервера, включая публичный, точно так же, как пакет, установленный через apt. У этого есть один плюс: такой трафик проходит по стандартному пути обработки входящих соединений, поэтому правила UFW применяются к нему, что неверно для опубликованных портов.

Режим host — это функция Linux Docker Engine. Docker Desktop поддерживает его только начиная с версии 4.34 и только после включения, с дополнительными ограничениями: контейнеры не могут привязываться к IP-адресам хоста, а поддерживаются только протоколы TCP и UDP. Если часть вашей команды работает на Linux-серверах, а часть — на Docker Desktop, будьте готовы к тому, что один и тот же файл будет вести себя по-разному.

Используйте режим host, когда вам необходим доступ к интерфейсам хоста. Не используйте его для решения проблем с подключением, так как обычно это заменяет одну проблему другой, более сложной.

Соединение двух проектов Compose через внешнюю сеть

Сеть, созданная одним проектом, не видна другому. Именно поэтому обратный прокси в proxy/compose.yaml не может обнаружить приложение в app/compose.yaml, даже если они находятся на одном сервере. Решение заключается в использовании сети, которая не принадлежит ни одному из проектов.

Создайте её вручную один раз:

docker network create edge

Затем объявите её как внешнюю (external) в каждом проекте. На стороне прокси:

services:
  proxy:
    image: traefik:v3.1
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - edge

networks:
  edge:
    name: edge
    external: true

На стороне приложения:

services:
  app:
    image: nginx:1.27
    networks:
      - edge
      - internal
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: example
    networks:
      - internal

networks:
  edge:
    name: edge
    external: true
  internal:

external: true указывает Compose подключаться к существующей сети вместо создания новой и оставлять её после выполнения docker compose down. Отдельный ключ name: важнее, чем кажется: без него Compose ищет сеть с именем, в точности совпадающим с edge, а с ним вы можете задать сети одно имя в файле конфигурации, а другое — на хосте.

Если сеть не существует, Compose отказывается запускаться и сообщает, что сеть объявлена как внешняя, но не найдена. Создавайте её заранее.

Обратите внимание, как файл приложения использует internal. База данных находится только в этой локальной сети проекта, поэтому прокси не может получить к ней доступ, а может только app. Добавление internal: true в секцию сети идет еще дальше и полностью удаляет маршрут к внешнему миру. Это хороший стандартный параметр для базы данных, но перед его установкой стоит учесть один нюанс: контейнер во внутренней сети не может ничего скачать, поэтому точка входа (entrypoint), запускающая apt-get update или pip install при старте, зависнет и завершится по тайм-ауту.

Полный пример настройки с правилами маршрутизации и сертификатами см. в запуске нескольких приложений за одним экземпляром Traefik.

Опубликованные порты обходят UFW

Это особенность работы сети в Compose, которая часто приводит к инцидентам безопасности. Вы публикуете порт, проверяете, что UFW активен и блокирует всё, кроме SSH, но сервис всё равно доступен из Интернета.

sudo ufw status
curl http://203.0.113.10:8080

UFW сообщает, что порт закрыт. Команда curl всё равно возвращает страницу. Ничего не сломалось. Docker записывает правила трансляции адресов и пересылки напрямую в iptables. Трафик, направленный на опубликованный порт контейнера, пересылается в контейнер, а не доставляется на хост, поэтому он не проходит через цепочку, которой UFW управляет для локального трафика. Кроме того, правила Docker проверяются раньше правил UFW.

Краткое решение — публиковать порты только там, где это необходимо:

    ports:
      - "127.0.0.1:8080:80"

Это привязывает порт на стороне хоста к интерфейсу loopback. Теперь порт доступен только с самого сервера или через SSH-туннель, но не извне. Разместите публичную точку входа за reverse proxy, который намеренно публикует порты 80 и 443. Полное объяснение, включая цепочку DOCKER-USER для случаев, когда необходимо фильтровать опубликованный порт, приведено в почему Docker публикует порты в обход UFW и как это исправить.

Как выполнить отладку за четыре команды

Начните с проверки того, в какой сети на самом деле находится каждый контейнер:

docker network inspect shop_default

Блок Containers выводит список всех подключенных контейнеров с их адресами. Если сервис отсутствует в этом списке, значит, он находится в другой сети, работает в режиме host или вовсе не запущен.

Проверьте разрешение имен с помощью временного контейнера, подключенного к той же сети. Это избавит вас от необходимости устанавливать дополнительные инструменты в ваши собственные образы:

docker run --rm --network shop_default busybox nslookup db
docker run --rm --network shop_default busybox nc -zv db 5432

nslookup, завершающийся ошибкой, указывает на проблемы с разрешением имен или принадлежностью к сети. Если nslookup проходит успешно, а nc выдает ошибку, значит, сервис запущен, но не слушает этот порт или слушает 127.0.0.1 внутри собственного контейнера вместо 0.0.0.0. Последнее часто встречается при использовании серверов для разработки; решение заключается в изменении адреса привязки (bind address) в самом приложении, а не в Docker.

Еще одна проблема, которая выглядит как ошибка Docker: если контейнеры могут обмениваться данными друг с другом, но не могут связаться с машиной в вашей офисной сети или VPN, вероятно, подсеть Docker пересекается с этой сетью. По умолчанию Docker выделяет адреса, начиная с 172.17.0.0/16. Измените пул в /etc/docker/daemon.json:

{
  "default-address-pools": [
    { "base": "10.200.0.0/16", "size": 24 }
  ]
}

Затем выполните sudo systemctl restart docker и пересоздайте затронутые сети, так как существующая сеть сохраняет ту подсеть, с которой была создана.

FAQ

Почему мои контейнеры не могут связаться друг с другом по имени сервиса?

Они находятся в разных сетях. Compose автоматически помещает каждый сервис в <project>_default, но как только вы добавляете список networks: к сервису, этот список становится полным набором сетей для него, и сеть по умолчанию больше не используется. Выполните docker network inspect <network> и убедитесь, что оба контейнера присутствуют в блоке Containers. Также проверьте, что ни один из сервисов не использует network_mode: host, так как контейнер в режиме host не находится ни в одной сети Docker и не может разрешать имена сервисов.

Нужно ли публиковать порты, чтобы один сервис мог связаться с другим?

Нет. В сети Compose каждый порт любого контейнера доступен другим контейнерам в этой же сети. ports: существует только для того, чтобы открыть контейнер для трафика извне Docker, а expose: носит документальный характер. Публикация порта базы данных — распространенная и опасная привычка, так как она выставляет базу данных на публичный интерфейс вашего сервера.

В чем разница между bridge и host сетями?

Bridge предоставляет контейнеру собственное сетевое пространство имен и адрес на виртуальном коммутаторе, обеспечивая автоматическое разрешение имен между контейнерами и трансляцию исходящего трафика. Host предоставляет контейнеру прямой доступ к сетевому стеку хоста: нет отдельного адреса, нет разрешения по имени сервиса, нет публикации портов и нет изоляции от других процессов, слушающих порты на хосте. Bridge — это стандартный и правильный выбор, если только процессу не требуется прямой доступ к интерфейсам хоста.

Как соединить контейнеры из двух разных файлов Compose?

Создайте общую сеть с помощью docker network create edge, затем объявите её в обоих файлах через external: true и подключите нужные сервисы. Compose не будет ни создавать, ни удалять эту сеть. Если пропустить этап создания, Compose откажется запускаться и сообщит, что сеть объявлена как внешняя, но не найдена.

Почему мой контейнер доступен из Интернета, хотя UFW блокирует порт?

Потому что опубликованный порт обрабатывается правилами пересылки (forwarding), которые Docker добавляет в iptables. Эти правила проверяются раньше правил UFW, к тому же пересылаемый трафик в любом случае не проходит через цепочку фильтрации UFW. Привязывайте хостовую сторону к loopback с помощью "127.0.0.1:8080:80", а всё публичное содержимое размещайте за reverse proxy на портах 80 и 443.