Короткий ответ
«Контейнер не видит интернет» разложите на три независимых отказа: нет 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 шлюза bridge | L2/L3 внутри docker |
| ping 1.1.1.1 | NAT + FORWARD + uplink хоста |
| curl по IP:443 | firewall egress не DNS |
| curl по имени | DNS |
Pull образа с хоста падает — это registry, путь другой (демон хоста, не namespace web).
Возможные причины
iptables: falseв/etc/docker/daemon.json— Docker не вешает MASQUERADE.- UFW
DEFAULT_FORWARD_POLICY=DROPбез правил для docker. - Ручной
nft flush rulesetпосле «уборки». - DNS в контейнере указывает на недоступный 8.8.8.8 при блокировке egress UDP/53, либо битый
/etc/docker/daemon.jsondns:. network_mode: none/ забытая сеть в compose.- Хост без default route (сам «без интернета») — контейнеру брать неоткуда.
- Редко:
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 dockerReload предпочтительнее при 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/ufwDROP без 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.
Сценарий E. Хост сам без uplink
Чините маршрут/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.exampleICMP может быть DROP на периметре — тогда достаточно TCP 443 по IP и резолв имени корпоративного registry. Хостовый UFW status всё ещё active.
Если не помогло
- HTTPS по IP ок, по имени нет — DNS/split horizon, не bridge.
- Только большой MTU/VPN:
docker network inspectMTU 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 execegress.
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 если включён.