Docker Desktop Networking Weirdness: LAN Access, Ports, and DNS Fixes That Actually Work

Було корисно?

Ви запускаєте docker run -p 8080:80, заходите на localhost:8080 і все працює. Передаєте URL колезі в тій самій Wi‑Fi мережі, і… нічого.
Або контейнер може робити curl в інтернет, але не досягає NAS у локальній мережі. Або DNS підкидає монету щоразу, коли підключається VPN.

Мережі Docker Desktop не «зламані». Просто це не та модель мереж Linux‑хоста, яку ви звикли уявляти.
Це віртуальна машина, NAT, набір платформозалежних прокладок і кілька спеціальних імен, які існують здебільшого, щоб зберегти нам розум.

Ментальна модель: чому Docker Desktop відрізняється

На Linux Docker зазвичай підключає контейнери до мостової мережі на хості, використовує iptables/nftables для NAT вихідного трафіку
і додає правила DNAT для опублікованих портів. Ваш хост — це хост. Ядро, що запускає контейнери, — те ж саме ядро, яке запускає ваш шелл.

Docker Desktop на macOS і Windows за замовчуванням інший. Він запускає невелику Linux‑VM (або середовище Linux через WSL2),
і контейнери живуть поза межею віртуалізації. Ця межа пояснює, чому «host‑networking» поводиться дивно,
чому доступ з LAN несиметричний і чому публікація портів може здаватися орієнтованою лише на localhost.

Думайте шарами:

  • Ваша фізична ОС (macOS/Windows): має інтерфейс Wi‑Fi/Ethernet, клієнт VPN і брандмауер.
  • Docker VM / WSL2: має власний віртуальний NIC, таблицю маршрутизації, власні iptables і поведінку DNS.
  • Мережі контейнерів: мости всередині того Linux‑середовища; контейнери рідко торкаються фізичного LAN напряму.
  • Прокладка публікації портів: Docker Desktop пересилає порти від ОС хоста до VM і до контейнера.

Тому коли хтось каже «контейнер не може дістатися LAN», перше питання: «Який шар не може дістатися якого шару?»

Цікаві факти та коротка історія (те, що пояснює сьогоднішній біль)

  1. Початкова модель мереж Docker припускала Linux. Ранній Docker популяризував патерн «мост + NAT + iptables», бо Linux робив це просто й портативно.
  2. macOS не може запускати Linux‑контейнери нативно. Docker Desktop на macOS завжди покладається на Linux‑VM, оскільки контейнери потребують функцій ядра Linux (namespaces, cgroups).
  3. Windows проходив два етапи. Спочатку був Docker Desktop на базі Hyper‑V; потім WSL2 став дефолтним шляхом для кращої роботи з файловою системою та ресурсами, з іншими мережевими особливостями.
  4. host.docker.internal існує тому, що «хост» неоднозначний. Всередині контейнера «localhost» — це контейнер; Docker Desktop потрібне стабільне ім’я для «ОС‑хоста».
  5. Опубліковані порти — це не просто правила iptables на Desktop. На Linux це саме вони; на Desktop часто це реалізовано через проксі/форвардер у просторі користувача через межу VM.
  6. Клієнти VPN люблять переписувати ваш DNS і маршрути. Вони часто встановлюють новий DNS‑сервер, блокують split DNS або додають віртуальний інтерфейс з вищим пріоритетом, ніж Wi‑Fi.
  7. Корпоративна захист часто інжектує локальний проксі. Це може ламати DNS контейнерів, MITM TLS або непомітно перенаправляти трафік на інспекційну інфраструктуру.
  8. ICMP вам бреше у віртуальних мережах. «Не пінгується» не reliably означає «не підключається», особливо коли брандмауер блокує ICMP, але дозволяє TCP.

Жарт №1: мережі Docker Desktop схожі на оргштаб — завжди є ще один шар, ніж ви думаєте, і ніхто не несе за нього відповідальності.

Швидка діагностика (перевірте перше/друге/третє)

Найшвидший шлях вирішити проблему — припинити гадати. Діагностуйте в такому порядку, бо це ізолює шари з мінімальними зусиллями.

1) Це проблема публікації порту чи маршрутизації/DNS?

  • Якщо localhost:PORT працює на вашій машині, але клієнти з LAN не дотягуються — ймовірно, справа в брандмауері хоста/адресі прив’язки/фільтрації маршрутів VPN.
  • Якщо контейнери не можуть резолвити імена або дістатися будь‑якого зовнішнього хоста — починайте з DNS і вихідної маршрутизації всередині контейнера/VM.

2) Визначте, де «вмирає» пакет (ОС хоста → VM → контейнер)

  • З ОС хоста: чи можете ви дістатись цілі в LAN?
  • Зсередини контейнера: чи можете ви дістатись тієї самої LAN‑цілі по IP?
  • Зсередини контейнера: чи резолвиться імʼя?

3) Перевірте реальну адресу прив’язки/прослуховування і форвардер

  • Чи сервіс прослуховує на 0.0.0.0 всередині контейнера, або тільки на 127.0.0.1?
  • Чи Docker публікує порт на всіх інтерфейсах або тільки на localhost?
  • Чи брандмауер хоста блокує вхідні з LAN?

4) Рано перевіряйте VPN і поведінку перевизначення DNS

  • Якщо проблема з’являється/зникає разом із VPN — не вважайте це багом Docker. Це політики, маршрути, DNS або інспекція трафіку.

5) Лише тоді міняйте налаштування Docker Desktop

  • Зміна DNS‑серверів або діапазонів мереж може допомогти, але робіть це з доказами. Інакше створите нову загадку.

Практичні завдання: команди, виводи та рішення (12+)

Ось перевірки, які я насправді виконую. Кожна включає: команду, приклад виводу, що це означає і яке рішення приймати далі.
Команди показані з універсального запрошення; адаптуйте імена інтерфейсів та IP під своє середовище.

Task 1: Confirm which Docker context you’re using

cr0x@server:~$ docker context ls
NAME                DESCRIPTION                               DOCKER ENDPOINT
default *           Current DOCKER_HOST based configuration   unix:///var/run/docker.sock
desktop-linux       Docker Desktop                            unix:///Users/me/.docker/run/docker.sock

Значення: Якщо ви думаєте, що працюєте з Desktop, але насправді підключені до віддаленого демона (або навпаки), всі мережеві припущення будуть неправильні.
Рішення: Якщо контекст зі зірочкою не той, що ви очікували, переключіться: docker context use desktop-linux.

Task 2: Inspect a container’s IP and network attachment

cr0x@server:~$ docker ps --format 'table {{.Names}}\t{{.Ports}}'
NAMES          PORTS
web            0.0.0.0:8080->80/tcp
db             5432/tcp
cr0x@server:~$ docker inspect -f '{{.Name}} {{range .NetworkSettings.Networks}}{{.IPAddress}} {{.Gateway}}{{end}}' web
/web 172.17.0.2 172.17.0.1

Значення: Контейнер живе на внутрішньому мосту (тут 172.17.0.0/16). Це не ваш LAN.
Рішення: Якщо ви намагаєтеся дістатися до 172.17.0.2 з іншого ноутбука в Wi‑Fi — припиніть. Публікуйте порт або використайте інший мережевий режим.

Task 3: Check what address your service is actually listening on

cr0x@server:~$ docker exec -it web sh -lc "ss -lntp | head -n 5"
State  Recv-Q Send-Q Local Address:Port  Peer Address:Port Process
LISTEN 0      4096   0.0.0.0:80         0.0.0.0:*     users:(("nginx",pid=1,fd=6))

Значення: Прослуховування на 0.0.0.0 — добре; сервіс приймає трафік із мережі контейнера.
Якщо бачите 127.0.0.1:80, публікація порту «працюватиме» дивними способами або взагалі не спрацює.
Рішення: Якщо привʼязка до localhost — виправте конфігурацію програми: привʼяжіть до 0.0.0.0.

Task 4: Verify published port bindings on the Docker side

cr0x@server:~$ docker port web
80/tcp -> 0.0.0.0:8080

Значення: Docker вважає, що опублікував порт на всіх інтерфейсах.
Рішення: Якщо показує 127.0.0.1:8080, клієнти з LAN його не побачать. Перезапустіть з -p 0.0.0.0:8080:80 (або виправте файл compose).

Task 5: Confirm the host OS is listening on the expected port

cr0x@server:~$ ss -lntp | grep ':8080'
LISTEN 0      4096      0.0.0.0:8080     0.0.0.0:*    users:(("com.docker.backend",pid=2314,fd=123))

Значення: На Desktop часто видно процес бекенду Docker, а не PID контейнера. Це нормально.
Рішення: Якщо нічого не слухає — публікація не застосувалася або порт зайнятий іншим процесом.

Task 6: Test from the host OS to confirm the forward path works

cr0x@server:~$ curl -sS -D- http://127.0.0.1:8080/ | head
HTTP/1.1 200 OK
Server: nginx/1.25.3
Date: Sat, 03 Jan 2026 09:12:52 GMT
Content-Type: text/html

Значення: Форвардинг порту з хоста в контейнер працює локально.
Рішення: Якщо клієнти з LAN не підключаються — фокусуйтеся на брандмауері/VPN/привʼязці до localhost, а не на додатку в контейнері.

Task 7: Test from a LAN peer (simulate with another namespace/host if you can)

cr0x@server:~$ nc -vz 192.168.1.50 8080
Connection to 192.168.1.50 8080 port [tcp/http-alt] succeeded!

Значення: Порт доступний з LAN.
Рішення: Якщо таймаут — ймовірно брандмауер/маршрути. Якщо «refused» — щось слухає, але не приймає на цьому інтерфейсі або форвардер привʼязаний неправильно.

Task 8: Check container DNS configuration

cr0x@server:~$ docker exec -it web sh -lc "cat /etc/resolv.conf"
nameserver 192.168.65.5
search localdomain
options ndots:0

Значення: Docker Desktop часто інжектує stub‑резолвер IP (приклад: 192.168.65.5) всередині мережі VM.
Рішення: Якщо цей nameserver недосяжний або поводиться неправильно (часто з VPN), перевизначте DNS на рівні демона/compose.

Task 9: Test DNS resolution inside the container (don’t guess)

cr0x@server:~$ docker exec -it web sh -lc "getent hosts example.com | head -n 2"
2606:2800:220:1:248:1893:25c8:1946 example.com
93.184.216.34 example.com

Значення: DNS працює достатньо, щоб резолвити AAAA і A.
Рішення: Якщо зависає або нічого не повертає — проблема з DNS‑шляхом. Наступний крок: спробуйте резолвити через конкретний сервер або перевизначте резолвери.

Task 10: Test direct IP connectivity to a LAN resource from inside the container

cr0x@server:~$ docker exec -it web sh -lc "nc -vz 192.168.1.10 445"
192.168.1.10 (192.168.1.10:445) open

Значення: Маршрутизація контейнер → VM → ОС хоста → LAN працює для тієї цілі.
Рішення: Якщо IP працює, а імʼя — ні, це DNS. Якщо ні — маршрутизація/VPN/політика.

Task 11: Check the container’s default route (basic but decisive)

cr0x@server:~$ docker exec -it web sh -lc "ip route"
default via 172.17.0.1 dev eth0
172.17.0.0/16 dev eth0 scope link  src 172.17.0.2

Значення: Контейнер має маршрут через gateway мосту. Gateway вирішує, як дістатися до LAN/інтернету.
Рішення: Якщо дефолтний маршрут відсутній або неправильний — у вас кастомна мережа; поверніться та протестуйте стандартний bridge.

Task 12: Check whether you’re colliding with a corporate/VPN subnet

cr0x@server:~$ ip route | head -n 12
default via 192.168.1.1 dev wlan0
10.0.0.0/8 via 10.8.0.1 dev tun0
172.16.0.0/12 via 10.8.0.1 dev tun0
192.168.1.0/24 dev wlan0 proto kernel scope link src 192.168.1.50

Значення: Якщо Docker‑мережі використовують 172.16.0.0/12, а ваш VPN також маршрутизує цей діапазон, ви створили неоднозначну маршрутизацію.
Desktop особливо чутливий до перекриття, бо вже робить NAT.
Рішення: Змініть внутрішні підмережі Docker, щоб уникнути перекриття з корпоративними маршрутами.

Task 13: Inspect Docker networks and their subnets

cr0x@server:~$ docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
a1b2c3d4e5f6   bridge    bridge    local
f1e2d3c4b5a6   host      host      local
123456789abc   none      null      local
cr0x@server:~$ docker network inspect bridge --format '{{(index .IPAM.Config 0).Subnet}}'
172.17.0.0/16

Значення: Ви тепер знаєте, які підмережі споживає Docker.
Рішення: Якщо це перекривається з VPN або вашим LAN — змініть їх.

Task 14: Validate that the container can reach the host OS via Docker Desktop’s special name

cr0x@server:~$ docker exec -it web sh -lc "getent hosts host.docker.internal"
192.168.65.2    host.docker.internal

Значення: Спеціальна відповідність існує й вказує на хост‑ендпоінт, який надає Docker.
Рішення: Якщо це імʼя не резолвиться — у вас старе середовище, кастомний режим мережі або DNS всередині контейнера змінений. Як крайній варіант використовуйте явні IP.

Шаблони доступу з LAN: що працює, а що ні

Є три поширені запити:

  • LAN → ваш сервіс у контейнері (колега хоче потрапити на ваш dev‑сервер).
  • Контейнер → ресурс у LAN (контейнер має дістати NAS, принтер, внутрішнє API, Kerberos тощо).
  • Контейнер → ОС хоста (контейнер викликає сервіс, що працює на вашому ноутбуці).

Шаблон A: LAN → контейнер через опубліковані порти (єдиний здравий дефолт)

Публікуйте порти на ОС‑хоста, а не намагайтеся роздавати IP контейнера.
У Docker Desktop ви не можете вважати IP контейнера маршрутизованим у фізичний LAN. Вони живуть за NAT, всередині VM, іноді за ще одним NAT якщо ваша ОС теж щось мудрує.

Що робити:

  • Привʼяжіть до всіх інтерфейсів: -p 0.0.0.0:8080:80 або в Compose "8080:80" і переконайтеся, що не публікуєте випадково тільки на localhost.
  • Відкрийте порт у брандмауері хоста (і обмежте сферу доступу; не відкривайте dev‑базу даних для кафе Wi‑Fi).
  • Якщо ваш VPN забороняє вхідні з LAN підключення, прийміть реальність: тестуйте без VPN або використайте належне dev‑середовище поза локалкою.

Шаблон B: контейнер → ресурси LAN (маршрутизація працює, поки не зламається)

Контейнери, що дістаються до LAN, зазвичай працюють «з коробки», бо Docker Desktop виконує NAT вихідного трафіку через ОС‑хоста.
Потім ви підключаєте VPN і ОС змінює DNS і маршрути. Раптом контейнер не може резолвити або дістатися підмереж, які тепер «належать» VPN.

Коли це ламається, воно ламається в кілька повторюваних способів:

  • Перекриття підмереж: Docker обирає приватний діапазон, який маршрутується вашим VPN. Пакети зникають у тунелі.
  • Несумісність split DNS: хост резолвить внутрішні імена через корпоративний DNS, але контейнери застрягають на stub‑резолвері, що не форвардить потрібні домени.
  • Політика брандмауера: корпоративний endpoint блокує трафік з «невідомих» віртуальних інтерфейсів.

Шаблон C: контейнер → сервіси ОС‑хоста (використовуйте спеціальні імена)

Використовуйте host.docker.internal. Саме для цього воно й існує.
Це не найелегантніше, але стабільніше при DHCP‑змінах і менш крихке, ніж хардкодити 192.168.x.y.

Якщо ви на Linux (не Desktop), цього імені може не бути; на Desktop воно зазвичай доступне.

Порти: публікація, адреси прив’язки і чому колеги не бачать ваш dev‑сервер

Опубліковані порти — це валюта «зробіть мій контейнер доступним». Все інше — заборгованість.

Localhost — це не моральна цінність, а адреса прив’язки

Дві речі постійно плутають:

  • Де програма слухає всередині контейнера (127.0.0.1 проти 0.0.0.0).
  • Де Docker привʼязує опублікований порт на хості (127.0.0.1:PORT vs 0.0.0.0:PORT).

Якщо будь‑яка з цих двох речей «тільки localhost», клієнти з LAN втрачають доступ. І ви витратите час, звинувачуючи інший шар.

Порада для Compose: не привʼязуйте випадково до localhost

Compose підтримує явну привʼязку IP хоста. Це чудово, коли ви маєте таку мету, і жахливо, коли ні.

cr0x@server:~$ cat docker-compose.yml
services:
  web:
    image: nginx:alpine
    ports:
      - "127.0.0.1:8080:80"

Значення: Цей сервіс навмисно доступний тільки з ОС‑хоста.
Рішення: Якщо ви хочете доступ із LAN, змініть на "8080:80" або "0.0.0.0:8080:80", а потім налаштуйте брандмауер за потребою.

Коли опубліковані порти все ще недоступні з LAN

Якщо Docker показує 0.0.0.0:8080, але клієнти з LAN не підключаються:

  • Брандмауер хоста: Application Firewall на macOS, Windows Defender Firewall, сторонні endpoint‑інструменти.
  • Вибір інтерфейсу: порт може бути привʼязаний, але ОС блокує вхідні на Wi‑Fi і дозволяє на Ethernet (або навпаки).
  • Політика VPN: деякі клієнти примусово «блокують локальний LAN» щоб зменшити ризик латерального руху.
  • Питання NAT hairpin: деякі мережі не дозволяють достукатися до власного публічного IP зсередини; це зробить ваш маршрутизатор, а не Docker.

Жарт №2: Нічого так не покращує командну роботу, як сказати «в мене працює» і мати це на увазі як архітектурне твердження.

DNS‑виправлення: від «воно нестабільне» до «воно детерміністичне»

DNS — це місце, куди Docker Desktop‑дивність йде ставати фольклором. Проблема зазвичай не в тому, що «Docker не вміє DNS».
Проблема в тому, що тепер у вас щонайменше два резолвери (ОС‑хост і VM), іноді три (VPN), і вони не узгоджені щодо split‑horizon правил.

Режим помилки 1: контейнер резолвить публічні імена, але не внутрішні

Класичний корпоративний split DNS: git.corp резолвиться лише через внутрішні DNS‑сервери, доступні тільки по VPN.
Ваша ОС‑хост робить усе правильно. Контейнер використовує stub‑резолвер, який не форвардить потрібні домени.

Варіанти виправлення, від кращого до гіршого:

  1. Сконфігуруйте DNS у Docker Desktop, щоб використовувати ваші внутрішні резолвери, коли ви на VPN, і публічні резолвери коли не на VPN. Іноді це треба робити вручну, бо «авто» ненадійне.
  2. DNS на проєкт у Compose:
    • Встановіть dns: на IP тих резолверів, які можуть відповідати і на внутрішні, і на зовнішні імена (часто ті, що надає VPN).
  3. Захардкодити /etc/hosts всередині контейнерів. Це тактичний хак, не стратегія.

Task 15: Override DNS in Compose and verify inside container

cr0x@server:~$ cat docker-compose.yml
services:
  web:
    image: alpine:3.20
    command: ["sleep","infinity"]
    dns:
      - 10.8.0.53
      - 1.1.1.1
cr0x@server:~$ docker compose up -d
[+] Running 1/1
 ✔ Container web-1  Started
cr0x@server:~$ docker exec -it web-1 sh -lc "cat /etc/resolv.conf"
nameserver 10.8.0.53
nameserver 1.1.1.1

Значення: Контейнер тепер використовує вказані DNS‑сервери.
Рішення: Якщо внутрішні домени почали резолвитись — ви довели, що проблема в шляху DNS/split DNS, а не в додатку.

Режим помилки 2: DNS працює, але лише інколи (таймаути, повільні збірки, нестабільні встановлення пакетів)

Періодичні проблеми з DNS часто походять від:

  • DNS‑серверів VPN, які гублять UDP під навантаженням або вимагають TCP для великих відповідей.
  • Корпоративних агентів безпеки, які перехоплюють DNS і інколи таймаутять.
  • Проблем MTU/MSS на тунельних лінках (DNS по UDP фрагментується і тихо вмирає).

Task 16: Detect DNS timeouts vs NXDOMAIN inside container

cr0x@server:~$ docker exec -it web-1 sh -lc "time getent hosts pypi.org >/dev/null; echo $?"
real    0m0.042s
user    0m0.000s
sys     0m0.003s
0

Значення: Швидкий успіх.
Рішення: Якщо це займає секунди або падає періодично — віддавайте перевагу зміні резолверів (або примусу TCP через інший резолвер), а не нескінченним повторним спробам у скриптах збірки.

Режим помилки 3: внутрішній сервіс працює по IP, але не по імені (і лише на VPN)

Це знову split DNS, але зі складнішим ефектом: інколи VPN штовхає DNS‑суфікс і search‑domain на хості,
але резолвер Docker Desktop не успадковує це коректно.

Task 17: Confirm search domains inside container

cr0x@server:~$ docker exec -it web-1 sh -lc "cat /etc/resolv.conf"
nameserver 10.8.0.53
search corp.example
options ndots:0

Значення: Search domain присутній.
Рішення: Якщо його немає — FQDN можуть працювати, а короткі імена — ні. Використовуйте FQDN або налаштуйте search domains на рівні контейнера.

VPN, split tunnels, і «допомога» корпоративних endpoint‑ів

VPN спричиняє дві загальні групи проблем: зміни маршрутизації і зміни DNS. Docker Desktop посилює обидві, бо фактично це вкладена мережа.

Маршрутизація: коли VPN «краде» ваш RFC1918 простір

Багато корпоративних мереж маршрутизують великі приватні діапазони типу 10.0.0.0/8 або 172.16.0.0/12 через тунель.
Docker за замовчуванням часто використовує 172.17.0.0/16 для bridge і інші 172.x для користувацьких мереж.

На чистому Linux‑хості ви зазвичай можете керувати цим за допомогою налаштувань мосту і iptables. На Desktop це теж можливо, але треба трактувати як конфігурацію першого класу.

Task 18: Create a user-defined network on a “safe” subnet

cr0x@server:~$ docker network create --subnet 192.168.240.0/24 devnet
9f8c7b6a5d4e3c2b1a0f
cr0x@server:~$ docker run -d --name web2 --network devnet -p 8081:80 nginx:alpine
b1c2d3e4f5a6
cr0x@server:~$ docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' web2
192.168.240.2

Значення: Ви перемістили мережу контейнера поза загальновживаними корпоративними маршрутами.
Рішення: Якщо проблеми, повʼязані з VPN, покращилися — введіть політику підмереж для dev‑мереж.

Endpoint‑security: невидима середова коробка

Деякі endpoint‑інструменти трактують віртуальні NIC як «недовірені». Вони можуть блокувати вхідні або вихідні зʼєднання, або змушувати весь трафік йти через проксі.
Симптоми: опубліковані порти працюють лише коли агент призупинений, DNS повільний, або внутрішні сервіси ламаються через інспекцію TLS.

Ви не зможете «SRE‑шити» політику. Але ви можете швидко зібрати докази і ескалувати з конкретними даними.

Task 19: Prove it’s local firewall/policy with a quick inbound test

cr0x@server:~$ python3 -m http.server 18080 --bind 0.0.0.0
Serving HTTP on 0.0.0.0 port 18080 (http://0.0.0.0:18080/) ...

Значення: Це не Docker. Це звичайний процес на хості.
Рішення: Якщо пір‑peer у LAN теж не дотягується — припиняйте дебажити Docker і дивіться у бік брандмауера/VPN налаштувань «block local network».

Windows + WSL2 специфіка (куди пакети «йдуть на пенсію»)

На сучасному Windows Docker Desktop часто запускає рушій всередині WSL2. WSL2 має власну віртуальну мережу (NAT за Windows).
Це означає, що у вас може бути: NAT контейнера за Linux, за WSL2 NAT, за правилами брандмауера Windows. NAT до самого дна.

Типові симптоми на Windows

  • Опублікований порт доступний з Windows localhost, але не з LAN. Зазвичай це правила Windows Defender Firewall або привʼязка лише до loopback.
  • Контейнери не можуть дістатися підмережі LAN, яку Windows бачить. Зазвичай маршрути VPN не передаються у WSL2 або політика блокує WSL‑інтерфейси.
  • DNS відрізняється між Windows і WSL2. WSL2 пише власний /etc/resolv.conf; інколи він вказує на Windows‑сторінний резолвер, який не бачить DNS VPN.

Task 20: Check WSL2’s resolv.conf and route table (from inside WSL)

cr0x@server:~$ cat /etc/resolv.conf
nameserver 172.29.96.1
cr0x@server:~$ ip route | head
default via 172.29.96.1 dev eth0
172.29.96.0/20 dev eth0 proto kernel scope link src 172.29.96.100

Значення: WSL2 використовує Windows‑сторінний віртуальний шлюз/резолвер.
Рішення: Якщо DNS ламається лише з VPN — подумайте про фіксацію DNS в WSL2 (статичний resolv.conf) і узгодження DNS Docker з резолверами VPN.

macOS специфіка (pf, vmnet і ілюзія localhost)

На macOS Docker Desktop запускає Linux‑VM і форвардить порти назад на macOS.
Ваші контейнери не є повноцінними резидентами фізичного LAN. Вони гості за дуже ввічливим консьєржем.

На що плутаються користувачі macOS

  • «Працює на localhost, але не з мого телефона.» Зазвичай брандмауер macOS або порт опубліковано лише на loopback.
  • DNS змінюється при перемиканні Wi‑Fi мереж. Резолвер хоста змінюється швидко; VM іноді відстає або кешує дивно.
  • Корпоративний VPN блокує локальний підмережевий доступ. Ваш телефон не дістанеться до ноутбука, коли VPN підключено — і це не обовʼязково вина Docker.

Task 21: Confirm the host OS has the right IP and interface for LAN testing

cr0x@server:~$ ip addr show | sed -n '1,25p'
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    inet 127.0.0.1/8 scope host lo
2: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    inet 192.168.1.50/24 brd 192.168.1.255 scope global dynamic wlan0

Значення: Ваша LAN‑IP — 192.168.1.50.
Рішення: Це адреса, яку повинен використовувати LAN‑peer, щоб дістатися до вашого опублікованого порту. Якщо колеги використовують старий IP — вони тестують не ту машину.

Типові помилки: симптом → корінь проблеми → виправлення

1) Симптом: localhost:8080 працює, колега не дотягується до 192.168.x.y:8080

  • Корінь: Порт опубліковано тільки на 127.0.0.1, або брандмауер хоста блокує вхідні.
  • Виправлення: Публікуйте на всіх інтерфейсах (-p 0.0.0.0:8080:80), потім дозвольте вхідні для цього порту у брандмауері для відповідного мережевого профілю.

2) Симптом: контейнер дістається в інтернет, але не до 192.168.1.10 (LAN NAS)

  • Корінь: Політика VPN «block local LAN» або маршрути, що штовхають LAN‑підмережі у тунель.
  • Виправлення: Тестуйте з відключеним VPN; якщо це допомагає — попросіть виключення для split‑tunnel або запускайте навантаження в належному середовищі (віддалена dev VM, staging). Не намагайтеся обходити політику хак‑методами.

3) Симптом: контейнер дістається до LAN IP, але внутрішні імена не працюють

  • Корінь: Split DNS не поширюється у Docker Desktop; контейнери використовують stub‑резолвер, що не бачить внутрішніх зон.
  • Виправлення: Налаштуйте DNS на проєкт (dns: у Compose) з корпоративними DNS, доступними через VPN; перевіряйте через getent hosts.

4) Симптом: DNS флапає під час збірок (apt/npm/pip випадково падають)

  • Корінь: Ненадійний UDP DNS через VPN, MTU проблеми, перехоплення endpoint‑ом.
  • Виправлення: Віддавайте перевагу стабільним резолверам; використовуйте два резолвери (внутрішній + публічний) де дозволено; зменшіть ризик фрагментації, налаштувавши MTU на VPN‑рівні, якщо це під контролем.

5) Симптом: сервіс опублікований, але з LAN приходить «connection refused»

  • Корінь: Додаток слухає тільки localhost контейнера або опубліковано неправильний порт.
  • Виправлення: Перевірте ss -lntp всередині контейнера; виправте адресу привʼязки; перевірте docker port і мапінг портів контейнера.

6) Симптом: не вдається підключитися до host.docker.internal з контейнера

  • Корінь: DNS‑перенапис видалив спеціальне імʼя, або ви використовуєте режим мережі, де Desktop його не інʼєкцює.
  • Виправлення: Уникайте сліпого перевизначення DNS; якщо потрібно, забезпечте, щоб спеціальне імʼя резолвилось (або додайте явну хост‑запис через extra_hosts як останній варіант).

7) Симптом: все ламається лише в одній Wi‑Fi мережі

  • Корінь: Та мережа ізолює клієнтів (AP isolation) або блокує вхідні з’єднання між пристроями.
  • Виправлення: Використовуйте нормальну мережу (або провідну), або запускайте сервіс за зворотним тунелем; не припускайте, що «та сама Wi‑Fi» означає «взаємна досяжність».

Три корпоративні міні‑історії (реалістично, анонімізовано, болісно)

Міні‑історія 1: Інцидент від неправильної припущення

Команда продукту збудувала демо‑оточення на ноутбуках для onsite‑воркшопу. План був простий: запустити кілька сервісів у Docker Desktop, опублікувати порти
і дозволити учасникам підключатися через готельний Wi‑Fi. Усі не раз робили «-p 8080:8080».
Неправильне припущення: Docker Desktop поводиться як Linux‑хост в плоскій LAN.

Вранці половина учасників не могла підключитися. Сервіси були запущені. Локальний curl працював. Ведучі іноді могли дістатися одне одного.
Люди почали перезавантажуватися, ніби 1998 рік. Проблема не в Docker; це був готельний Wi‑Fi з клієнтською ізоляцією — пристрої могли виходити в інтернет, але не бачити одне одного.

Друге неправильне припущення з’явилося миттєво: «Давайте використовувати IP контейнера і уникнути порт‑мапінгу.»
Вони роздали 172.17.x.x адреси, які були видимі всередині Docker VM — але звісно не були досяжні з інших ноутбуків.
Це призвело до десяти хвилин впевненого нісеніття і однієї сильно шкодуючої діаграми на дошці.

Виправлення було нудне: створили локальний хот‑спот на телефоні, який дозволяв peer‑to‑peer трафік,
і явно опублікували потрібні порти на 0.0.0.0 з швидким правилом у брандмауері. Сервіси працювали. Припущення про «та сама мережа» було реальною причиною відмови.

Міні‑історія 2: Оптимізація, що відстріляла собі в ногу

Платформена команда хотіла швидше виконувати CI на машинах розробників. Вони помітили багато DNS‑запитів під час збірок і вирішили «оптимізувати», примусивши контейнери використовувати публічний DNS‑резолвер.
На кавовому‑шопі це виглядало чудово: швидші резолви, менше таймаутів, гарні графіки.

Потім інженер спробував зібрати проект під VPN. Внутрішні реєстри пакетів доступні тільки через корпоративний DNS і внутрішні маршрути.
Раптом збірки почали падати з «host not found», хоча ОС‑хост резолвив правильно. Робочим обходом стало «відключити VPN» — чудовий шлях створити наступний інцидент.

Ситуація ускладнилася тим, що деякі внутрішні імена резолвилися публічно в заглушкові IP (з міркувань безпеки), тому «DNS‑успіх» приводив до чорної діри.
Налагодження було жорстким: бачиш записи A, додаток таймаутить, і всі у випадковому порядку звинувачують TLS, проксі та Docker.

Остаточне рішення — припинити глобальні «оптимізації» DNS. Перейшли на DNS на проєкт: внутрішні резолвери першими під час VPN, публічні тільки коли VPN відсутній.
Документували, як тестувати резолв всередині контейнерів, бо «в мене на хості резолвиться» — не аргумент у вкладеній мережі.

Міні‑історія 3: Нудна, але правильна практика, що врятувала день

Сервіс з високими вимогами до безпеки використовував Docker Desktop для локального інтеграційного тестування. Потрібно було викликати внутрішнє API і одночасно приймати вебхуки від тестового стенду на іншій машині в офісі.
Команда мала звичку, яку я поважаю: перед змінами вони знімали «known‑good» базовий стан — маршрути, конфігурацію DNS, мапінги портів.

Одного понеділка все зламалося після оновлення ОС. Контейнери не могли резолвити внутрішні імена. Вебхуки перестали доходити.
Замість гадань вони порівняли поточний стан з базовим: опубліковані порти тепер були привʼязані лише до localhost, а stub‑DNS всередині контейнерів вказував на новий VM‑IP, який не форвардив split DNS.

Виправили привʼязку портів у Compose, потім зафіксували DNS контейнерів на внутрішні резолвери при роботі з VPN.
Оскільки у них був baseline, вони могли показати команді endpoint‑безпеки саме те, що змінилося і чому. Інцидент не перетворився на тижневу битву звинувачень.

Така практика — зафіксувати базовий стан і робити diff при збоях — нудна, але дієва.

Контрольні списки / покроковий план (спеціально нудно)

Checklist 1: Надійно відкрити сервіс Docker Desktop у LAN

  1. Переконайтеся, що додаток слухає на 0.0.0.0 всередині контейнера (ss -lntp).
  2. Опублікуйте порт на всіх інтерфейсах: -p 0.0.0.0:8080:80 (або Compose "8080:80").
  3. Підтвердіть, що Docker бачить мапінг: docker port CONTAINER.
  4. Переконайтеся, що ОС‑хоста слухає на цьому порту: ss -lntp | grep :8080.
  5. Тест локально: curl http://127.0.0.1:8080.
  6. Тест з LAN‑peer: nc -vz HOST_LAN_IP 8080.
  7. Якщо LAN‑тест провалюється — запустіть не‑Docker слухач (python3 -m http.server), щоб відокремити брандмауер/VPN від проблем Docker.

Checklist 2: Зробити контейнерам доступ до внутрішніх ресурсів LAN (NAS, внутрішні API)

  1. З ОС‑хоста перевірте, що ціль досяжна по IP.
  2. Зсередини контейнера протестуйте IP‑зʼєднання (nc -vz або curl).
  3. Якщо IP не працює лише при VPN — перевірте перекриття маршрутів (ip route) і політику VPN («block local LAN»).
  4. Якщо IP працює, але імʼя ні — перевірте /etc/resolv.conf і резольв через getent hosts.
  5. За потреби перевизначте DNS на проєкт через Compose dns:.
  6. Уникайте перекриття підмереж: перемістіть Docker‑мережі в діапазон, який не маршрутизує ваш VPN.

Checklist 3: Стабілізувати DNS для dev‑зборок (коли pip/npm/apt флапає)

  1. Виміряйте час резолву всередині контейнера з time getent hosts.
  2. Перевірте поточні резолвери в /etc/resolv.conf.
  3. Якщо на VPN — віддавайте перевагу резолверам, що надає VPN (і додайте публічний fallback, якщо дозволено).
  4. Не хардкодьте публічний DNS глобально для всіх проєктів; ви зламаєте split DNS робочі процеси.
  5. Перепроводьте тест всередині контейнера після змін; не довіряйте лише результатам ОС‑хоста.

FAQ

1) Чому я не можу просто використати IP контейнера з іншої машини в LAN?

Тому що в Docker Desktop цей IP знаходиться на внутрішньому мосту всередині Linux‑VM (або WSL2 середовища). Ваш LAN до нього не маршрутизований. Публікуйте порти замість цього.

2) Чому -p 8080:80 працює локально, але не з мого телефону?

Зазвичай або порт привʼязано тільки до localhost (явно або через Compose), або брандмауер хоста/VPN блокує вхідні з LAN.

3) У чому різниця між 127.0.0.1 і 0.0.0.0 в цьому контексті?

127.0.0.1 означає «приймати з цього самого стеку мережі». 0.0.0.0 означає «слухати на всіх інтерфейсах».
Потрібен 0.0.0.0, якщо ви очікуєте підключень з інших пристроїв.

4) Чи вирішить проблему опція --network host у Docker Desktop?

Ні. На Docker Desktop «host network» не те саме, що Linux host networking, і часто не дає бажаного результату. Краще використовувати bridge + публікацію портів.

5) Чому DNS працює на хості, але не всередині контейнерів?

Контейнер може використовувати інший шлях резолвінгу (stub всередині VM), і він може не успадковувати split DNS настройки VPN. Перевірте cat /etc/resolv.conf і getent hosts всередині контейнера, потім перевизначте DNS на проєкт якщо потрібно.

6) Чи варто встановити DNS Docker Desktop на публічний резолвер, щоб «виправити все»?

Лише якщо вам ніколи не потрібен внутрішній DNS. Публічні резолвери можуть зламати корпоративні домени, внутрішні реєстри та split‑horizon сценарії. Використовуйте DNS на проєкт або умовну поведінку залежно від стану VPN.

7) Мій контейнер не може дістатися пристрою LAN лише коли підключений VPN. Це вина Docker?

Майже завжди — ні. Клієнти VPN можуть маршрутизувати приватні підмережі через тунель або блокувати локальний LAN. Доведіть це, протестувавши те саме з ОС‑хоста і відключивши VPN як контроль.

8) Який найнадійніший спосіб, щоб контейнер викликав сервіс на моєму ноуті?

Використовуйте host.docker.internal і тримайте це як стандарт між середовищами. Уникайте хардкодних IP, що гуляють з Wi‑Fi мережами.

9) Як дізнатися, чи проблема в брандмауері чи в мапінгу порту Docker?

Запустіть не‑Docker слухач на хості (наприклад python3 -m http.server). Якщо LAN не дістається і до нього — проблема не в Docker.

10) Добре правило для здорового глузду мереж Docker Desktop?

Думайте про Docker Desktop як про «контейнери за VM за вашою ОС». Публікуйте порти, уникайте перекриття підмереж і перевіряйте DNS всередині контейнера.

Висновок: що зробити сьогодні

Мережі Docker Desktop перестають бути дивними, коли ви припините очікувати від них поведінки Linux‑хоста. Це межа VM з шаром форвардингу.
Як тільки ви це приймете, більшість проблем зводиться до трьох груп: адреси привʼязки, політики брандмауера/VPN і дрейф DNS/резолверів.

Практичні наступні кроки:

  1. Виберіть один тестовий сервіс, опублікуйте його на 0.0.0.0 і перевірте доступність з LAN end‑to‑end за допомогою ss, curl і nc.
  2. Зніміть базовий стан, коли все працює: docker port, /etc/resolv.conf контейнера і таблицю маршрутів хоста.
  3. Якщо використовуєте VPN, не допускайте, щоб Docker‑мережі перекривалися з корпоративними маршрутами. Стандартизуйте «безпечний» діапазон для dev‑мереж.
  4. Налаштовуйте DNS на проєкт, коли важливі внутрішні імена. Глобальні «фікси» — шлях до міжкомандних зламів.

Парафраз вислову Вернера Вогельса (CTO Amazon): «Усе ламається; проєктуйте системи — і операції — так, щоб вони вбирали ці відмови.»
Мережі Docker Desktop не якісь особливі. Це просто відмови з додатковими шарами.

← Попередня
Ubuntu 24.04: «Failed to get D-Bus connection» — виправте зламані сесії й служби (випадок №48)
Наступна →
SLI/CrossFire: чому мульти‑GPU були мрією — і чому вона померла

Залишити коментар