Короткий ответ
На user-defined сети Compose встроенный DNS отдаёт имя сервиса (db, web) контейнерам той же сети. Если web и db проекта app в разных сетях, либо у web стоит network_mode: host, либо старый links: — «друг друга не видят». Лечение: одна общая сеть (часто достаточно default app_default) или явный networks: [backend] у обоих. Не публикуйте 5432 на хост «чтобы точно соединилось»: это обход и дыра. Не links — в Compose v2 это наследие.
Отдельно: DNS сломан даже на одной сети — Docker DNS. Egress в интернет — нет интернета.
Симптомы и как отличить
- в логе
web:could not connect to server: db; docker compose exec web getent hosts dbпусто,getent hosts app-db-1иногда работает (hostname контейнера);docker inspectпоказываетwebтолько вapp_frontend,dbтолько вapp_backendбез общего attachment;- ping IP контейнера
dbидёт, имя нет — тогда DNS, не «разные сети» в смысле L3.
| Наблюдение | Сеть | DNS |
|---|---|---|
| ping IP соседа ок, имя нет | одна | сломан 127.0.0.11 |
| ping IP нет | разные сети / none / fw | вторично |
| с хоста localhost:5432 ок, из web нет | вы лечите не тот путь | publish ≠ service DNS |
Возможные причины
- Явные сети без общего пересечения:
web→frontend,db→backend. network_mode: hostу одного из сервисов — нет namespace DNS Compose.network_mode: service:.../ extra контейнер sidecars.- Второй файл compose без тех же
networks:. - Ручной
docker run --network bridge(default bridge без embedded DNS для произвольных имён). - Опечатка
DB_HOST=databaseпри сервисеdb. - Редко:
internal: trueна сети плюс ожидание выхода — не east-west, но путают.
Диагностика
Хост host.example, /opt/app.
1. К каким сетям приклеены
cd /opt/app
docker compose ps
docker inspect app-web-1 --format '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}'
docker inspect app-db-1 --format '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}'
docker network inspect app_default --format '{{range $k,$v := .Containers}}{{$v.Name}} {{end}}'Нужно непустое пересечение имён сетей.
2. DNS из web
docker compose exec web getent hosts db
docker compose exec web getent hosts app-db-1
docker compose exec web sh -c 'cat /etc/resolv.conf; ping -c 1 db || true'Пусто на db при живом IP в inspect — либо не та сеть, либо DNS.
3. YAML сетей
docker compose config | grep -n -A20 '^networks:'
docker compose config | grep -n -A15 ' web:' | head -n 40Ищите network_mode: и разные списки networks:.
4. Default bridge ловушка
docker inspect app-web-1 --format '{{.HostConfig.NetworkMode}}'bridge (default) не даёт стабильный DNS имени сервиса как user-defined. Compose должен ставить app_default или вашу named.
Решение
Сценарий A. Разнести frontend/backend без общего канала
Либо добавьте db во frontend (хуже по изоляции), либо web в backend (обычно правильно):
services:
web:
networks: [frontend, backend]
db:
networks: [backend]
networks:
frontend:
backend:web резолвит db в backend. Не публикуйте db на frontend-сеть наружу через ports.
Сценарий B. Случайный host network
Уберите network_mode: host у web. Верните ports: для HTTP. Пересоздание:
docker compose up -d web
docker compose exec web getent hosts dbСценарий C. Ручные контейнеры вне compose
Подключите к сети проекта:
docker network connect app_default leftoverДолгосрочно — внесите сервис в compose. Default bridge не используйте для multi-container app.
Сценарий D. Неверное имя хоста в env
DB_HOST=db, не localhost (localhost внутри web — сам web) и не IP, который сменится. Не хардкодьте 172.18.0.2.
Сценарий E. db ещё не слушает
Сеть ок, connection refused на 5432 — это старт Postgres, не «не видят друг друга». Healthcheck + depends_on condition: compose стек, healthcheck.
Как проверить, что проблема устранена
docker compose exec web getent hosts db
docker compose exec web sh -c 'command -v nc >/dev/null && nc -zv db 5432 || timeout 3 bash -c "echo >/dev/tcp/db/5432"'Имя → IP сети compose, TCP open. С хоста проверка localhost:5432 не обязательна и часто нежелательна.
Если не помогло
- Резолв есть, TCP timeout: firewall внутри образа/
iptablesв контейнере, Postgreslisten_addresses. - Резолв на старый IP: кэш приложения, не Docker TTL; перезапуск
web. - IPv6 AAAA без слушателя:
getentдал v6. Форсируйте в приложении IPv4 или почините v6 в daemon. - Два проекта
appна одной сети external — коллизия DNS имён. РазведитеCOMPOSE_PROJECT_NAME. linksв YAML: удалите, перейдите на общую сеть.
Профилактика
- Шаблон: все сервисы стека в
backend, proxy отдельно вfrontend+backend. - Запрет
network_mode: hostв review. - CI: скрипт
compose exec web getent hosts db. - Никаких IP в
DATABASE_URL. - Не использовать default bridge для приложений.
FAQ
Почему ping db не работает, а getent работает?
В образе нет ping или нет CAP_NET_RAW. Для «видят друг друга» достаточно TCP на порт сервиса.
Нужны ли links?
Нет в новом Compose. User-defined сеть заменяет.
container_name: db и DNS
Можно, но теряете масштабирование и ловите конфликт имён. Предпочтительнее имя сервиса.
Два стека на одной external сети
DNS общее: оба web будут драться за имя. Либо разные имена сервисов, либо разные сети.
internal: true
Сеть без default egress. East-west внутри работает. Не используйте как «магическую изоляцию вместо правил», если нужен pull обновлений из контейнера.
На Compose v2 имя сети в DNS — не container_name и не hostname гипервизора. После docker compose -p app up сеть почти всегда app_default или app_backend. Если второй стек подняли с тем же COMPOSE_PROJECT_NAME=app из /tmp/app, вы получите коллизию имён web/db на одной сети: «то видят, то нет» в зависимости от того, кто последний зарегистрировался в embedded DNS. Лечится уникальным именем проекта, не links.
Проверка с двух сторон обязательна: exec web getent hosts db и exec db getent hosts web. Односторонний резолв бывает при internal плюс отдельной сети только у одного сервиса. IP из inspect не прописывайте в DATABASE_URL: после recreate адрес сменится, симптом вернётся как «опять не видят». Для TCP используйте порт, который слушает процесс внутри netns (5432), не publish хоста.