Portainer не создаёт порты: причины и решение
Вы вписали порт в форму, нажали Deploy, контейнер поднялся зелёным — а в колонке Published Ports пусто. Или там оказался никем не заказанный 32771. Portainer не создаёт порты не потому, что сломан: он честно передал Docker то, что вы ввели, а Docker так же честно это проигнорировал или подменил. Ниже — пять мест, где публикация теряется, с точным текстом ошибок и командами проверки.
Содержание
- Три разных симптома под одной жалобой
- Причина 1: порты нельзя дописать в существующий контейнер
- Причина 2: в форме публикации порт потерялся или подменился
- Причина 3: стек деплоится, а ports из него выпадают
- Причина 4: Swarm-окружение — порты публикует не контейнер
- Причина 5: порт создан, но занят, заблокирован или недостижим
- Какой сервер нужен под Portainer и как заказать в MAATRIX
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество 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 требует открытыми три вещи, и про третью забывают чаще всего.
| Порт | Протокол | Зачем |
|---|---|---|
| 2377 | TCP | управление кластером, только между менеджерами |
| 7946 | TCP + UDP | обнаружение нод |
| 4789 | UDP | VXLAN, трафик 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 — десятки моделей в одном окне. Оплата картой РФ и по СБП.