Короткий ответ

«Контейнер не видит интернет» разложите на три независимых отказа: нет L3 до шлюза bridge (172.17.0.1 / шлюз user-defined сети), нет NAT/FORWARD на host.example, нет DNS (часто путают с «нет интернета»). Проверка: ping -c 2 1.1.1.1 из контейнера vs getent hosts registry.example. IP жив, имя нет — это embedded DNS, не iptables. Не делайте iptables -F и не ufw disable: снесёте и хостовый фильтр, и чужие DNAT.

Проект app обычно сидит на app_default, не на docker0. Смотрите ту сеть, к которой подключён app-web-1.

Симптомы и как отличить

  • docker compose exec web ping -c 2 1.1.1.1 → 100% loss;
  • или ping ок, curl https://registry.example:5000/v2/ → resolve fail;
  • на хосте интернет есть, в контейнере нет;
  • после ufw enable или «hardening nft» пропал egress у всех контейнеров сразу.
ТестЗначение
ping шлюза bridgeL2/L3 внутри docker
ping 1.1.1.1NAT + FORWARD + uplink хоста
curl по IP:443firewall egress не DNS
curl по имениDNS

Pull образа с хоста падает — это registry, путь другой (демон хоста, не namespace web).

Возможные причины

  1. iptables: false в /etc/docker/daemon.json — Docker не вешает MASQUERADE.
  2. UFW DEFAULT_FORWARD_POLICY=DROP без правил для docker.
  3. Ручной nft flush ruleset после «уборки».
  4. DNS в контейнере указывает на недоступный 8.8.8.8 при блокировке egress UDP/53, либо битый /etc/docker/daemon.json dns:.
  5. network_mode: none / забытая сеть в compose.
  6. Хост без default route (сам «без интернета») — контейнеру брать неоткуда.
  7. Редко: sysctl net.ipv4.ip_forward=0 после тюнинга.

Диагностика

1. Маршрут хоста и forward

ip -4 route | head
sysctl net.ipv4.ip_forward
docker network inspect bridge --format '{{.IPAM.Config}} {{.Options}}'
docker inspect app-web-1 --format '{{json .NetworkSettings.Networks}}'

ip_forward=0 на хосте-роутере контейнеров — типичный выстрел в ногу.

2. Из контейнера: IP vs DNS

docker compose -f /opt/app/compose.yaml exec web sh -c 'ip -4 addr; ip -4 route; cat /etc/resolv.conf'
docker compose exec web sh -c 'ping -c 2 1.1.1.1 || true'
docker compose exec web sh -c 'getent hosts registry.example || true'

nameserver 127.0.0.11 — штатный embedded DNS Docker. Он форвардит на DNS хоста. Если хостовый resolv.conf указывает на внутренний резолвер без egress — контейнер тоже без внешних имён.

3. iptables/nft, не «отключить»

sudo iptables -t nat -S | grep -E 'DOCKER|MASQUERADE' | head
sudo iptables -S FORWARD | head -n 40
sudo nft list tables

На Ubuntu 22.04/24.04 часто iptables-nft. Пустые цепочки DOCKER после flush — демон нужно перезапустить, чтобы пересоздал правила, после того как вы поняли, кто flush сделал.

sudo systemctl reload docker || sudo systemctl restart docker

Reload предпочтительнее при live-restore; restart рвёт соединения.

Решение

Сценарий A. Нет MASQUERADE / ip_forward

sudo sysctl -w net.ipv4.ip_forward=1
grep -r ip_forward /etc/sysctl.conf /etc/sysctl.d/

В daemon.json не должно быть "iptables": false. Уберите, python3 -m json.tool, restart docker в окне.

Сценарий B. UFW режет FORWARD

В /etc/default/ufw политика forward должна позволять установленные docker-потоки, либо явные правила. Часто правят:

sudo grep DEFAULT_FORWARD_POLICY /etc/default/ufw

DROP без DOCKER-USER ACCEPT для 172.16.0.0/12 ломает egress. Добавляйте точечные правила, не DEFAULT_FORWARD_POLICY=ACCEPT на хосте с чужими VM, если не понимаете модель. Минимальный рабочий вариант на выделенном docker-хосте — политика, которую Docker документирует вместе с UFW; проверьте, что после изменения docker compose exec web curl заработал, а SSH всё ещё фильтруется.

Сценарий C. DNS, не NAT

Ping IP есть:

docker compose exec web sh -c 'getent hosts example.com; cat /etc/resolv.conf'

Правьте dns: в compose или хостовый stub, не 8.8.8.8 в каждом контейнере в обход корпоративного резолвера, если политика запрещает. Подробно: Docker DNS.

Сценарий D. Контейнер не в сети

# плохо: network_mode: none без нужды

Верните сервис на сеть проекта app. docker compose up -d пересоздаст veth.

Чините маршрут/DNS хоста. Контейнерный NAT не создаст интернет из пустоты.

Как проверить, что проблема устранена

docker compose exec web ping -c 2 1.1.1.1
docker compose exec web sh -c 'curl -sS -o /dev/null -w "%{http_code}\n" --max-time 8 https://1.1.1.1'
docker compose exec web getent hosts registry.example

ICMP может быть DROP на периметре — тогда достаточно TCP 443 по IP и резолв имени корпоративного registry. Хостовый UFW status всё ещё active.

Если не помогло

  • HTTPS по IP ок, по имени нет — DNS/split horizon, не bridge.
  • Только большой MTU/VPN: docker network inspect MTU vs overlay на VM. Симптом: ping мелкий ок, TCP висит. Не путать с «нет интернета».
  • IPv6 AAAA ломает curl при полуживом v6 — curl -4 для проверки.
  • Pull с хоста работает, из контейнера нет — разные пути (демон vs namespace).
  • После reboot снова ip_forward=0 — не записали sysctl.d.

Профилактика

  • Мониторинг: exec curl из служебного контейнера раз в минуту, не только ping хоста.
  • Запрет iptables: false и nft flush в runbook.
  • Документ: DNS хоста = то, что получат контейнеры через 127.0.0.11.
  • Не ставить второй firewall «поверх» без DOCKER-USER.
  • Проверка после hardening хоста: один compose exec egress.

FAQ

Нужен ли --privileged для ping?

Нет. ping в контейнере требует CAP_NET_RAW; если ping запрещён capabilities — используйте curl. Не privileged.

docker0 vs app_default

docker0 — default bridge. Compose создаёт user-defined сеть. Egress через MASQUERADE должен быть у обеих, если iptables Docker включён. Inspect именно той сети, где контейнер.

Можно ли network_mode: host чтобы точно был интернет?

Обход, ломающий DNS сервисов db/web. Не лечение FORWARD.

Почему 8.8.8.8 в daemon.json «всем помогло»?

Обойшли мёртвый корпоративный DNS ценой политики. Чините резолвер хоста.

restart docker безопасен для app_data?

Named volume не трогается. Обрыв соединений — да. Окно, live-restore если включён.