0% прочитано

Чому localhost не працює між контейнерами Docker

У браузері localhost:9001 може відкривати MinIO, але той самий localhost усередині Laravel-контейнера вже означає сам контейнер застосунку. Розбираю, чому так відбувається і як контейнери знаходять один одного через мережу Docker.

8 жовтня 2026 р. 8 хв читанняLaradock

Під час підключення MinIO локально я зіткнулася з дуже типовою Docker-плутаниною.

У браузері веб-консоль MinIO відкривалася через localhost:9001. Здавалося логічним, що Laravel теж зможе звернутися до MinIO через localhost.

Але з контейнера застосунку ця адреса вже означає зовсім інше місце.

У кожного контейнера свій localhost.

Коли я це розділила, схема стала простою: з комп’ютера використовую опублікований порт, а між контейнерами - ім’я сервісу в мережі Docker.

localhost не означає "цей комп’ютер у широкому сенсі". Він означає поточне мережеве середовище, з якого виконується звернення.

Чому localhost:9001 працює в браузері

Звернення з комп’ютера до опублікованого порту
Browser   |   | http://localhost:9001   vComputer running Docker   |   | published port 9001   vMinIO container

Браузер працює на комп’ютері, де запущено Docker.

Тому для браузера localhost означає саме цей комп’ютер.

Docker публікує порт контейнера назовні. Завдяки цьому звернення до localhost:9001 потрапляє у веб-консоль MinIO.

У цьому сценарії localhost працює правильно, тому що запит починається поза контейнерами.

Усередині контейнера localhost означає сам контейнер

localhost не переходить у сусідній контейнер
Laravel container      |      | http://localhost:9000      vLaravel container itself  MinIO container      X Запит до ньогонавіть не дійшов.

Контейнер має власний мережевий простір.

Якщо PHP усередині контейнера відкриває http://localhost:9000, він шукає порт 9000 у цьому ж контейнері.

MinIO працює в іншому контейнері, тому такий запит до нього не потрапляє.

Проблема тут не в Laravel і не в MinIO. Адреса просто вказує не на те мережеве середовище.

127.0.0.1 має ту саму проблему

Заміна localhost на 127.0.0.1 тут нічого не виправляє.

Обидві адреси вказують назад на поточне мережеве середовище.

У контейнері це знову буде сам контейнер, а не інший сервіс Docker.

Контейнери знаходять один одного за ім’ям сервісу

Звернення між контейнерами
Laravel container      |      | http://minio:9000      vDocker network      |      | service name: minio      vMinIO container

Якщо обидва сервіси підключені до однієї мережі Docker, вони можуть звертатися один до одного за ім’ям сервісу.

Для MinIO такою адресою в моєму випадку стала:

http://minio:9000

Docker сам знаходить контейнер, який відповідає імені minio.

Мені не потрібно знати його поточну внутрішню IP-адресу.

Я не прив’язую конфігурацію до IP контейнера

Крихка адреса
http://172.20.0.7:9000
Стабільна адреса через ім’я сервісу
http://minio:9000

Внутрішня IP-адреса контейнера може змінитися після повторного створення контейнера.

Ім’я сервісу є значно стабільнішою межею.

Тому в конфігурації застосунку я використовую minio, а не адресу виду 172.x.x.x.

Один сервіс може мати різні адреси залежно від точки входу

Адреса залежить від того, звідки йде запит
З браузера: http://localhost:9001 для веб-консолі  З контейнера Laravel: http://minio:9000 для S3-сумісного API

Спочатку це може виглядати як суперечність: MinIO ніби має дві різні адреси.

Насправді сервіс один. Відрізняється шлях до нього.

Комп’ютер входить через опублікований порт Docker, а інший контейнер використовує внутрішню мережу Docker.

Тому ці адреси не конкурують між собою. Вони належать різним мережевим контекстам.

Порт контейнера і порт на комп’ютері теж не обов’язково однакові

Зовнішній порт може відрізнятися від внутрішнього
Host localhost:19001        |        vDocker port mapping        |        vcontainer:9001

Docker може опублікувати внутрішній порт контейнера під іншим номером на комп’ютері.

Наприклад, зовні це може бути 19001, а всередині контейнер усе одно слухає 9001.

Між контейнерами зазвичай використовується саме внутрішній порт сервісу, а не номер, опублікований для комп’ютера.

Я перевіряю доступ саме з контейнера застосунку

Перевірка MinIO з контейнера
curl -i \  http://minio:9000/minio/health/live

Якщо Laravel повинен звертатися до MinIO з PHP-контейнера, я перевіряю саме цей маршрут.

Відкрита веб-консоль у браузері доводить лише те, що працює шлях від комп’ютера до опублікованого порту.

Вона нічого не говорить про зв’язок між двома контейнерами.

Тому health check з середовища застосунку є набагато кориснішою перевіркою.

Ім’я сервісу можна перевірити окремо

Перевірка пошуку сервісу за ім’ям
getent hosts minio

Якщо ім’я minio не знаходиться, проблема може бути ще до HTTP.

Наприклад, контейнери можуть не мати спільної мережі або сервіс має інше ім’я.

Тому я відділяю дві перевірки: спочатку чи знаходиться сервіс за ім’ям, потім чи відповідає потрібний порт.

У Laravel endpoint має відповідати мережі самого застосунку

Неправильно для Laravel усередині контейнера
AWS_ENDPOINT=http://localhost:9000
Адреса MinIO в мережі Docker
AWS_ENDPOINT=http://minio:9000AWS_USE_PATH_STYLE_ENDPOINT=true

Якщо Laravel виконується всередині Docker, його S3 endpoint повинен бути доступний саме звідти.

Тому для локального MinIO я використовую ім’я сервісу minio.

Це не спеціальна вимога Laravel. Це наслідок того, де саме виконується PHP.

Працює в браузері не означає працює з контейнера

Це головна практична помилка, яку я тепер перевіряю першою.

Браузер, PHP-контейнер, черга, окремий сервіс і сам MinIO можуть знаходитися в різних мережевих контекстах.

Тому адреса, яка працює в одному місці, не зобов’язана працювати в іншому.

Перш ніж змінювати Laravel-конфігурацію або шукати проблему в S3-драйвері, я дивлюся, звідки саме виконується запит.

Модель, яка допомагає мені не плутатися

Три правила, яких зазвичай достатньо
Якщо запит іде з комп'ютера: localhost-> сам комп'ютер  Якщо запит іде з контейнера: localhost-> цей контейнер  Якщо контейнеру потрібенінший контейнер: ім'я сервісу

Що я перевіряю, коли контейнер не бачить інший сервіс

Мій короткий список:

  • Звідки насправді виконується запит?
  • Що означає localhost саме в цьому середовищі?
  • Чи запущений потрібний контейнер?
  • Чи підключені обидва контейнери до спільної мережі Docker?
  • Яке ім’я має потрібний сервіс?
  • Чи знаходиться це ім’я всередині контейнера?
  • Який внутрішній порт слухає сервіс?
  • Чи не використовую я зовнішній порт там, де потрібен внутрішній?
  • Чи відповідає сервіс при прямій перевірці з контейнера?

localhost завжди залежить від точки, з якої дивимося

Проблема з localhost між контейнерами стає набагато простішою, коли перестати сприймати його як універсальну адресу комп’ютера.

Для браузера localhost означає комп’ютер.

Для Laravel усередині контейнера localhost означає контейнер Laravel.

А щоб Laravel звернувся до MinIO в сусідньому контейнері, потрібне ім’я сервісу в спільній мережі Docker.

Тому питання "яку адресу використати?" я тепер починаю не з сервісу, а з іншого питання: "звідки саме виконується цей запит?".

Перед тим як міняти порт або конфігурацію, я спочатку визначаю, для кого саме localhost є localhost.