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

Мережі 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, взагалі не підтримує розв’язання імен. Саме тому поради 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 mode відмовляється від власного мережевого простору імен контейнера та дає процесу змогу безпосередньо використовувати інтерфейси хоста.

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

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

Недоліки залежать від конкретної ситуації.

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

Розпізнавання за іменем сервісу не працює в жодному напрямку. Контейнер не підключений до project network, тому не може розпізнати db, а інші сервіси не можуть розпізнати його. Він отримує доступ до них лише через порти, опубліковані на хості, зазвичай на 127.0.0.1.

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

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

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

Підключення двох 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 завершується помилкою, це означає, що сервіс запущений, але не прослуховує цей порт або прослуховує 127.0.0.1 усередині власного контейнера замість 0.0.0.0. Останній випадок часто трапляється з серверами розробки. Виправляти потрібно адресу прив’язки в застосунку, а не налаштування 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 блокує порт?

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