MAATRIX / Блог / Docker: не пробрасывается порт — причины и решение

Docker: не пробрасывается порт — причины и решение

Docker: не пробрасывается порт — причины и решение

MAATRIX

Контейнер запущен, приложение внутри работает, а снаружи сервис недоступен: Docker не пробрасывается порт. Это одна из самых частых и быстро решаемых проблем. Почти всегда причина в одном из четырёх: забыли опубликовать порт, приложение слушает только localhost внутри контейнера, порт закрыт файрволом или занят другим процессом. Разберём по порядку, как найти барьер и открыть доступ.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Первое действие: проверьте, опубликован ли порт

Сначала убедитесь, что порт вообще проброшен наружу. Docker изолирует сеть контейнера, и порт доступен снаружи, только если вы явно его опубликовали. Посмотрите, что показывает Docker:

docker ps

В колонке PORTS для контейнера должно быть что-то вроде 0.0.0.0:8080->80/tcp — это значит, что порт 8080 хоста проброшен на 80 контейнера и доступен извне. Если там пусто или только 80/tcp без стрелки и адреса — порт не опубликован, и снаружи сервис недоступен, как бы хорошо он ни работал внутри. Это первое, что нужно проверить: очень часто вся проблема именно здесь. Определив, опубликован порт или нет, вы сразу понимаете, в каком из разделов ниже ваше решение.

Причина 1: забыли опубликовать порт (-p)

Самый частый случай — контейнер запущен без публикации порта. Порт открывают флагом -p хост:контейнер при запуске или через ports в compose. Без этого порт живёт только внутри Docker-сети. Запустите контейнер с публикацией нужного порта:

docker run -d -p 8080:80 myimage

Здесь 8080 — порт на хосте, 80 — порт внутри контейнера. Обращаться снаружи нужно к порту хоста (8080). В compose это выглядит как ports: - "8080:80". Частая путаница — указать порты наоборот или обращаться к внутреннему порту вместо хостового. Ещё важный момент: EXPOSE в Dockerfile сам по себе порт не публикует — это лишь документация, реальную публикацию делает только -p или ports. Если вы полагались на EXPOSE, порт останется закрытым снаружи. Добавьте явную публикацию, пересоздайте контейнер — и сервис станет доступен.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США и РФ. Оплата картой РФ и по СБП.

Арендовать VPS под Docker

Причина 2: приложение слушает только localhost внутри контейнера

Коварный случай: порт опубликован через -p, но снаружи всё равно пусто или connection refused. Частая причина — приложение внутри контейнера слушает 127.0.0.1 (localhost), а не 0.0.0.0 (все интерфейсы). Тогда оно доступно только внутри контейнера самому себе, и проброс порта ничего не даёт, потому что на внешнем интерфейсе контейнера никто не слушает. Проверьте, на каком адресе слушает приложение изнутри:

docker exec -it имя_контейнера sh -c "netstat -tln || ss -tln"

Если видите 127.0.0.1:80 вместо 0.0.0.0:80 — вот проблема. Решение на стороне приложения: настройте его слушать 0.0.0.0, а не localhost. У веб-серверов и фреймворков это обычно параметр хоста в конфиге или переменной окружения (например, --host 0.0.0.0). Это очень распространённая ошибка при контейнеризации приложений, которые по умолчанию слушают localhost «для безопасности». Внутри контейнера слушать 0.0.0.0 нормально — изоляцию даёт сам Docker и проброс конкретного порта.

Причина 3: порт закрыт файрволом хоста

Если порт опубликован и приложение слушает 0.0.0.0, но снаружи сервер недоступен — порт закрыт файрволом хоста. Проверьте, слушает ли хост на нужном порту и открыт ли он:

ss -tlnp | grep 8080
ufw status

Если Docker слушает порт на хосте, но извне не достучаться, откройте порт в файрволе. Здесь есть важный нюанс: Docker управляет своими правилами iptables в обход UFW, из-за чего опубликованный порт может быть доступен даже при закрытом UFW — или, наоборот, возникают неожиданные конфликты. На боевом сервере это вопрос безопасности: сервис, который вы считали закрытым, может оказаться открыт всему интернету. Настраивайте доступ осознанно: публикуйте наружу только то, что действительно должно быть доступно, а внутренние сервисы привязывайте к localhost хоста через -p 127.0.0.1:8080:80, чтобы они не торчали в интернет. Учтите и внешний файрвол провайдера, если он есть, — его настраивают отдельно.

Причина 4: порт уже занят на хосте

Если при запуске контейнера возникает ошибка вроде port is already allocated или bind: address already in use, значит порт хоста уже занят другим процессом или контейнером. Посмотрите, кто держит порт:

ss -tlnp | grep 8080

Это может быть другой контейнер, проброшенный на тот же порт, или обычный сервис на хосте (например, Nginx или другой веб-сервер уже на 80). Решение — либо освободить порт, остановив конфликтующий процесс, либо пробросить контейнер на другой свободный порт хоста (-p 8081:80). Нельзя пробросить два контейнера на один и тот же порт хоста одновременно — каждый хостовый порт занимает только один слушатель. Если перед контейнерами стоит reverse proxy, обычно наружу публикуют только его порты (80 и 443), а сами контейнеры не пробрасывают напрямую — это и чище, и безопаснее, и снимает конфликты портов.

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

После правок проверьте доступность и локально с хоста, и снаружи. Сначала с самого сервера, потом с внешней машины:

curl -I http://localhost:8080

Если curl с хоста отвечает, а снаружи нет — дело в файрволе (хоста или провайдера). Если и с хоста connection refused при опубликованном порте — приложение слушает localhost внутри контейнера (вернитесь к причине 2). Такое разделение «работает локально, но не снаружи» против «не работает даже локально» мгновенно указывает, где барьер. Проверяйте именно ту точку, откуда должен идти доступ пользователей: часто оказывается, что локально всё хорошо, а наружу закрыто файрволом, и наоборот. Последовательная проверка снимает неопределённость.

Профилактика: чтобы порты пробрасывались предсказуемо

Чтобы не возвращаться к этой проблеме, держите в голове несколько принципов. Всегда публикуйте порты явно через -p или ports в compose и помните, что EXPOSE сам по себе ничего не открывает. Настраивайте приложения слушать 0.0.0.0 внутри контейнера — это обязательное условие для проброса. Обращайтесь снаружи к порту хоста, а не к внутреннему порту контейнера, и не путайте их порядок в хост:контейнер.

С точки зрения безопасности не публикуйте наружу лишнего: внутренние сервисы (базы, кэши) привязывайте к 127.0.0.1 на хосте или держите только во внутренней Docker-сети без публикации, а наружу выставляйте один reverse proxy на 80 и 443. Это и безопаснее, и избавляет от конфликтов портов между контейнерами. Помните про особенность взаимодействия Docker и UFW и проверяйте, что действительно открыто в интернет. Продуманная схема портов делает проброс предсказуемым, а сервисы — доступными ровно там, где нужно, и закрытыми там, где нельзя.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США и РФ. Оплата картой РФ и по СБП.

Арендовать VPS под Docker

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Частые вопросы

Почему сервис в контейнере недоступен снаружи?

Чаще всего порт не опубликован (нет -p), либо приложение внутри слушает 127.0.0.1 вместо 0.0.0.0, либо порт закрыт файрволом. Проверьте docker ps — в колонке PORTS должна быть публикация вида 0.0.0.0:8080->80.

Достаточно ли EXPOSE в Dockerfile, чтобы открыть порт?

Нет: EXPOSE — это только документация. Реальную публикацию делает флаг -p при запуске или ports в compose. Без них порт снаружи закрыт.

Приложение работает в контейнере, но curl с хоста отвергается. Почему?

Скорее всего приложение слушает localhost внутри контейнера. Проверьте ss -tln изнутри и настройте приложение слушать 0.0.0.0, иначе проброс порта не поможет.

Как оплатить сервер под Docker из России?

В MAATRIX — картой российского банка, через СБП, криптовалютой или токеном MAAT. Иностранная карта не нужна.

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.