SSD Nodes Learn Hosting plans →
Посібники Matt ConnorВід Matt Connor · Оновлено 2026-10-23

Мережі Docker Compose: DNS, host mode і порти

Пояснення мереж Docker Compose: bridge за замовчуванням, DNS за іменем сервісу, host mode, спільна мережа проєктів і порт, який обходить UFW.

Що 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. Частина host — це ім’я сервісу.

Є дві деталі, які згодом заощадять час. Імена зіставляються з тим, що запущено зараз, тому docker compose up -d --scale web=3 повертає одне ім’я з трьома адресами, а клієнт, який безстроково кешує DNS, прив’яже себе до непрацюючого контейнера. Крім того, застаріла мережа bridge, яку використовує звичайний docker run без --network, не може зіставляти імена контейнерів. Контейнери в ній знаходять один одного лише за IP-адресами, а зовнішні імена хостів визначають через копію файлу /etc/resolv.conf з хоста. Тому поради щодо links між контейнерами з 2016 року не відповідають тому, що ви бачите зараз.

Вам не потрібен ports:, щоб з’єднати два сервіси

ports: публікує порт контейнера на хості. Це потрібно для мережевого трафіку, що надходить із-за меж Docker. До трафіку між сервісами цей параметр не має жодного стосунку: такий трафік уже працює через увесь діапазон портів у мережі проєкту.

Тому ports: - "5432:5432", який багато хто додає до сервісу бази даних, не дає користі й створює реальну проблему: він відкриває Postgres на публічному інтерфейсі сервера. Видаліть його. Якщо потрібно підключитися до бази з ноутбука для міграції, прив’яжіть порт до loopback за допомогою "127.0.0.1:5432:5432" і підключайтеся через SSH-тунель. Різницю між listening socket, опублікованим портом і правилом firewall описано в розділі як працюють порти та сервіси, що прослуховують порти, у Linux.

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

Коли потрібен network_mode host і якою є його ціна

Режим host прибирає власний мережевий простір імен контейнера та дає процесу прямий доступ до інтерфейсів хоста.

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

Для цього є практичні причини. Процес, якому потрібно бачити broadcast- або multicast-трафік у локальній мережі, наприклад для виявлення пристроїв медіасервером або системою домашньої автоматизації, не може бачити його через bridge, оскільки bridge не передає такий трафік контейнеру. Агент моніторингу, який читає лічильники інтерфейсів хоста, потребує доступу до інтерфейсів хоста. Також ви оминаєте етап трансляції адрес, що має значення за високої частоти пакетів.

Ціна цього режиму конкретна.

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

Визначення імен за іменами сервісів не працює в обох напрямках. Контейнер не підключений до мережі проєкту, тому не може визначити db, а інші сервіси не можуть визначити його. Водночас він може підключатися до них через порти, опубліковані на хості, зазвичай через 127.0.0.1. У Linux він також може напряму підключатися до bridge-контейнера за IP-адресою цього контейнера на будь-якому порту, який прослуховується, оскільки хост має відкритий доступ до контейнерів у своїх bridge-мережах, а контейнер у режимі host використовує мережевий стек хоста. Ці адреси змінюються після повторного створення контейнерів, тому порт, опублікований на loopback, є стабільним способом підключення.

Ізоляцію втрачено. Процес, який прив’язується до 0.0.0.0 усередині контейнера в режимі host, прослуховує всі інтерфейси сервера, зокрема публічний, так само як пакет, установлений через apt. Є і перевага: такий трафік проходить стандартним вхідним шляхом, тому на нього поширюються правила UFW. Для опублікованих портів це не так.

Режим host — функція Linux Docker Engine. Docker Desktop підтримує його лише починаючи з версії 4.34 і лише після ввімкнення. Додатково контейнери не можуть прив’язуватися до IP-адрес хоста, а обробляються лише TCP і UDP. Якщо частина команди працює на Linux-серверах, а частина — у Docker Desktop, той самий файл може поводитися по-різному.

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

Підключення двох Compose-проєктів до зовнішньої мережі

Мережа, створена одним проєктом, не видима іншому. Тому reverse proxy у proxy/compose.yaml не бачить застосунок у app/compose.yaml, навіть якщо вони працюють на одному сервері. Потрібно створити мережу, якою не володіє жоден із проєктів.

Створіть її один раз вручну:

docker network create edge

Потім оголосіть її зовнішньою в кожному проєкті. Сторона проксі:

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 завершується помилкою, ім’я та мережа налаштовані правильно, а проблема саме в підключенні. Якщо nc повідомляє Connection refused, найчастіше сервіс не прослуховує цей порт або прослуховує 127.0.0.1 усередині власного контейнера замість 0.0.0.0. Це часто трапляється з серверами для розробки. Виправлення потрібно шукати в адресі прив’язки застосунку, а не в Docker. Якщо nc зависає й завершується через тайм-аут, перевірте, чи не блокує трафік щось на шляху, наприклад правило firewall, або чи не призупинений контейнер і чи відповідає він на запити.

Є ще одна проблема, схожа на помилку 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 блокує порт?

Тому що опублікований порт обробляється правилами перенаправлення, які Docker додає до iptables. Ці правила перевіряються перед правилами UFW, а перенаправлений трафік у будь-якому разі не проходить через ланцюжок, який фільтрує UFW. Прив’яжіть порт на стороні хоста до loopback за допомогою "127.0.0.1:8080:80", а всі публічні сервіси розмістіть за reverse proxy на портах 80 і 443.

Чи була ця стаття корисною?