MAATRIX / Блог / Portainer не создаёт порты: причины и решение

Portainer не создаёт порты: причины и решение

Portainer не создаёт порты: причины и решение

MAATRIX

Вы вписали порт в форму, нажали Deploy, контейнер поднялся зелёным — а в колонке Published Ports пусто. Или там оказался никем не заказанный 32771. Portainer не создаёт порты не потому, что сломан: он честно передал Docker то, что вы ввели, а Docker так же честно это проигнорировал или подменил. Ниже — пять мест, где публикация теряется, с точным текстом ошибок и командами проверки.

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

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

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

Три разных симптома под одной жалобой

Сначала определите, какой из трёх сценариев у вас — они лечатся по-разному. Первый: порт не создан вообще. Второй: порт создан, но не тот — случайный номер вместо вашего или TCP вместо UDP. Третий: порт создан верно, но панель не рисует по нему ссылку. Различить их можно двумя командами по SSH, не в панели: она показывает уже интерпретированные данные.

docker port myapp
80/tcp -> 0.0.0.0:8080

docker inspect -f '{{json .HostConfig.PortBindings}}' myapp
{"80/tcp":[{"HostIp":"","HostPort":"8080"}]}

Так выглядит здоровая публикация. Если docker port промолчал, а inspect отдал пустой объект {} — портов действительно нет, читайте секции про пересоздание и про форму в UI. Если же всё верно, а в панели порт показан серым текстом без ссылки, это косметика: в настройках окружения (Environments → ваш Docker → поле Public IP) не задан внешний адрес, и Portainer не рискует строить ссылку. Впишите публичный IP — ссылки в колонке Published Ports станут кликабельными. На саму публикацию поле не влияет.

Причина 1: порты нельзя дописать в существующий контейнер

Причина номер один по частоте, и она не про Portainer, а про архитектуру Docker: список привязок HostConfig.PortBindings фиксируется при создании контейнера и дальше неизменяем. У docker container update нет ни одного флага для портов — она меняет только лимиты ресурсов и политику рестарта.

На практике человек дописывает в панели 8080:80, жмёт Restart — и ничего не происходит: Restart перезапускает процесс внутри уже созданного контейнера, сетевую конфигурацию не пересобирает. Чтобы порт появился, контейнер надо удалить и создать заново.

Для этого на странице контейнера есть кнопка Duplicate/Edit: она открывает форму создания с текущими параметрами. Меняете порты, жмёте Deploy the container, панель просит подтверждение — «A container with the same name already exists. Portainer can automatically remove it and re-create one» — и пересоздаёт контейнер.

Честно о риске: пересоздание уничтожает всё, что лежало в записываемом слое и в анонимных томах, — именованные тома и bind-монтирования переживут. Проверьте заранее, где лежат данные:

docker inspect -f '{{range .Mounts}}{{.Type}} {{.Name}} -> {{.Destination}}{{"\n"}}{{end}}' myapp
volume 8f3c1a24e91b... -> /var/lib/mysql

Длинный хеш вместо имени — это анонимный том: после Duplicate/Edit база начнётся с нуля, сначала снимите дамп. Вывод вида volume pgdata -> /var/lib/postgresql/data безопасен.

Вывод на будущее: контейнеры, которые придётся править, лучше описывать стеком — тогда смена порта это правка двух символов и кнопка Update the stack.

Развернуть за пару минут

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

Развернуть Portainer

Причина 2: в форме публикации порт потерялся или подменился

Публикация портов спрятана в форме «Add container», в блоке Network ports configuration, и там три ловушки.

Пустое поле host. Кнопка «publish a new network port» добавляет пару полей — host и container. Заполнили только container (80) — Portainer отправит запрос без фиксированного хостового порта, и Docker выдаст случайный из эфемерного диапазона ядра (cat /proc/sys/net/ipv4/ip_local_port_range обычно возвращает 32768 60999). Отсюда загадочные номера: docker ps покажет 0.0.0.0:32771->80/tcp вместо 8080. Лечится заполнением обоих полей.

Тумблер «Publish all exposed network ports to random host ports». Это эквивалент docker run -P: публикует не всё подряд, а только объявленное EXPOSE в образе, снова на случайные порты. Нет EXPOSE — не опубликует ничего. Проверьте образ до запуска:

docker image inspect -f '{{json .Config.ExposedPorts}}' nginx:1.29
{"80/tcp":{}}

Вывод null — автопубликация для образа бесполезна, задавайте порты вручную.

Селектор TCP/UDP. Рядом с полями стоит переключатель протокола, по умолчанию TCP. Для WireGuard (51820), DNS (53), Syslog (514) и всего на QUIC это неверно: порт создастся, но не тот, и сервис останется недоступен при формально «зелёном» контейнере — docker port wg покажет 51820/tcp -> 0.0.0.0:51820 там, где нужен UDP. Пересоздайте контейнер с нужным протоколом, а если нужны оба — добавьте две записи.

Причина 3: стек деплоится, а ports из него выпадают

Стеки — самый частый способ работы с Portainer, и здесь публикация теряется в YAML.

expose вместо ports. Ключ expose наружу не публикует ничего, он лишь объявляет порт для соседей по сети. Стек задеплоится без ошибок, контейнер будет здоров, портов не появится.

Незаданная переменная. Стек в Portainer не читает .env из каталога: переменные задаются в отдельном блоке формы под редактором. Написали - "${WEB_PORT}:80", а переменную не завели — compose подставит пустую строку и предупредит в логе деплоя:

WARN[0000] The "WEB_PORT" variable is not set. Defaulting to a blank string.

Дальше — либо отказ invalid published port, либо публикация не туда. Берите значение в кавычки ("8080:80") и проверяйте блок Environment variables формы стека.

Неверный уровень вложенности. ports относится к сервису, а не к deploy. Ошибка отступа даёт отказ валидации ещё до запуска, красной плашкой над редактором: services.web.deploy Additional property ports is not allowed.

network_mode, который отменяет публикацию. При network_mode: host контейнер использует сетевой стек хоста, проброс лишён смысла и выбрасывается с предупреждением «Published ports are discarded when using host network mode»: портов в панели не будет, но сервис доступен напрямую на порту хоста.

А схема «контейнер за VPN-сайдкаром» — когда у сервиса стоит network_mode: "service:gluetun" и при этом свой блок ports — ломается жёстко, отказом демона:

Error response from daemon: conflicting options: port publishing and the container type network mode

Контейнер живёт в чужом сетевом namespace, и публиковать порт должен его владелец: уберите ports у клиентского сервиса и перенесите - "8080:8080" в gluetun. То же правило для любого network_mode: "container:имя".

Причина 4: Swarm-окружение — порты публикует не контейнер

Если окружение помечено как Swarm, вы смотрите не туда. Стеки деплоятся через docker stack deploy, порты принадлежат сервису и публикуются через routing mesh. В списке Containers у задачи (task) колонка портов пустая — это нормально. Смотреть надо на сервис:

docker service inspect --format '{{json .Endpoint.Ports}}' web_app
[{"Protocol":"tcp","TargetPort":80,"PublishedPort":8080,"PublishMode":"ingress"}]

Заодно прочитайте лог деплоя целиком: docker stack deploy выбрасывает неподдерживаемые ключи строкой Ignoring unsupported options: restart, её принимают за безобидную и пропускают соседние предупреждения о сети.

Второй сюжет — routing mesh не работает, и порт отвечает только на ноде, где физически крутится задача. Причина почти всегда в файрволе между нодами: Swarm требует открытыми три вещи, и про третью забывают чаще всего.

ПортПротоколЗачем
2377TCPуправление кластером, только между менеджерами
7946TCP + UDPобнаружение нод
4789UDPVXLAN, трафик overlay-сетей и ingress

Закрытый 4789/udp — самый частый виновник: docker node ls зелёный, а порт публикуется «наполовину». Открывать их надо только между нодами, не в интернет: ufw allow from 10.0.0.0/24 to any port 4789 proto udp.

Если routing mesh не нужен и хочется привязку к порту конкретной ноды, используйте длинный синтаксис — - target: 80, published: 8080, protocol: tcp, mode: host. Тогда порт займёт хост ноды с задачей и появится в обычном docker ps, но на одной ноде не запустить две реплики такого сервиса.

Причина 5: порт создан, но занят, заблокирован или недостижим

Финальная группа: Portainer отправил верный запрос, а он не прошёл.

Порт занят. Панель показывает красное уведомление, внутри которого спрятан ответ демона:

Error response from daemon: driver failed programming external connectivity on endpoint myapp (a3f1...): Bind for 0.0.0.0:8080 failed: port is already allocated

Коварная деталь: контейнер уже создан, он просто не смог стартовать. При повторной попытке вы получите другой отказ — Conflict. The container name "/myapp" is already in use by container ... — и решите, что панель сошла с ума. Уберите мёртвую заготовку через docker rm myapp и найдите, кто держит порт:

ss -tlnp | grep :8080
LISTEN 0 4096 0.0.0.0:8080 0.0.0.0:* users:(("docker-proxy",pid=1841,fd=4))

docker-proxy в выводе означает, что порт занят другим контейнером — ищите его через docker ps --format '{{.Names}}\t{{.Ports}}'.

Rootless-Docker и порты ниже 1024. Если демон запущен без root, публикация 80 или 443 отваливается с узнаваемым текстом:

rootlessport cannot expose privileged port 80, you can add 'net.ipv4.ip_unprivileged_port_start=80' to /etc/sysctl.conf (currently 1024)

Решение в тексте ошибки: sysctl -w net.ipv4.ip_unprivileged_port_start=80 плюс запись в /etc/sysctl.d/99-docker.conf, чтобы пережила перезагрузку. Либо публикуйте на 8080 и ставьте реверс-прокси.

Docker 28 и прямой доступ к IP контейнера. С 28-й ветки демон по умолчанию блокирует маршрутизацию к IP контейнеров с других хостов. Публикация через -p работает как раньше, обращаться надо к IP хоста, — а схемы, где к 172.17.0.x ходили напрямую из соседней сети, отвалились после апгрейда, и выглядит это как «Portainer перестал создавать порты». Проверка одна: iptables -t nat -S DOCKER | grep 8080 должна вернуть строку с --to-destination 172.17.0.4:80. Есть строка — порт создан, разбирайтесь с внешним доступом; детали в материале Docker: не пробрасывается порт, а сбои самой панели — в Portainer на сервере: частые ошибки.

Какой сервер нужен под Portainer и как заказать в MAATRIX

Сам Portainer по ресурсам почти бесплатен: контейнер CE держится в районе 60–100 МБ RAM, агент — около 30 МБ. Ресурсы съедают ваши контейнеры, по ним и считайте.

Минимум, на котором реально работается: 2 vCPU, 2 ГБ RAM, 40 ГБ NVMe — панель, реверс-прокси и три-четыре лёгких сервиса. Оговорка про диск: /var/lib/docker растёт незаметно, держите под рукой docker system df, иначе получите отказы уже не по портам, а по записи.

Комфортный вариант: 4 vCPU, 8 ГБ RAM, 80–160 ГБ NVMe — Traefik или Nginx на 80/443, десяток сервисов, база и мониторинг, с запасом на пересборку образов. Для стеков с PostgreSQL или Elasticsearch 8 ГБ не роскошь, а нижняя планка. И честно про Swarm: на одном сервере он не нужен и добавляет ровно те проблемы с routing mesh, что разобраны выше — кластер имеет смысл от трёх нод, и там важнее приватная сеть между ними.

Наружу выставляйте только 80 и 443 реверс-прокси, а порт панели 9443 держите на localhost и ходите через ssh -L 9443:127.0.0.1:9443 root@IP: Portainer управляет всеми контейнерами хоста, публиковать его в интернет — прямой риск.

Локацию берите по аудитории сервисов под панелью. Для европейских проектов разумный выбор — UK, Лондон: пинг до крупных точек обмена ЕС держится в пределах 5–25 мс, площадка соседствует с GDPR-контуром и удобна для SaaS и корпоративных сервисов. Если под панелью живут сервисы с персональными данными российских пользователей, берите RU — 152-ФЗ и минимальный пинг. Для контейнеров с доступом к зарубежным AI-API и чистым IP есть US.

Заказать VPS под Docker и Portainer можно с оплатой картой российского банка, по СБП, криптой или токеном MAAT — иностранная карта не нужна даже для лондонской площадки. Установка панели разобрана в статье Как установить и настроить Portainer на VPS.

Развернуть за пару минут

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

Развернуть Portainer

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

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

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

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

Почему после добавления порта в Portainer ничего не изменилось?

Привязки портов фиксируются при создании контейнера и на живом не меняются. Restart не поможет — нужна кнопка Duplicate/Edit, которая пересоздаёт контейнер; перед этим проверьте docker inspect -f '{{json .Mounts}}', чтобы не потерять анонимные тома.

Откуда взялся порт 32771, который я не указывал?

Вы оставили пустым поле host в блоке Network ports configuration либо включили тумблер публикации всех портов на случайные, и Docker выдал номер из эфемерного диапазона 32768–60999. Заполните оба поля.

В Swarm колонка портов у контейнера пустая — это поломка?

Нет: порт публикует сервис через routing mesh, а не отдельная задача. Смотрите docker service inspect --format '{{json .Endpoint.Ports}}' имя_сервиса и проверьте, что между нодами открыты 2377/tcp, 7946/tcp+udp и 4789/udp.

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

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