Публикация портов в Docker кажется простой ровно до первого сбоя. Обычно всё начинается с команды вроде docker run -p 8080:80 nginx:stable, а заканчивается сообщениями too many colons in address или bind address already in use. На Debian и Ubuntu эти ошибки встречаются регулярно: где-то ломается синтаксис -p, где-то контейнер пытаются привязать к уже занятому порту, а где-то мешает путаница между IPv4 и IPv6.
Проблема в том, что снаружи последствия похожи: контейнер не стартует как ожидалось, сервис недоступен, а ощущение такое, будто Docker «просто не открыл порт». Но причины у этих ошибок разные, и лечатся они тоже по-разному. Если действовать наугад, легко потерять время на перезапуск демона, пересоздание контейнеров и правку compose-файлов без реального результата.
Ниже разберём, как Docker читает published ports, почему он ругается на лишние двоеточия, откуда берётся конфликт уже занятого адреса и чем это всё быстро проверять на Debian и Ubuntu.
Как Docker понимает published ports
Базовая форма публикации порта выглядит как HOST_PORT:CONTAINER_PORT. Например, запись 8080:80 означает, что порт 80 внутри контейнера будет доступен на порту 8080 хоста.
Более полная форма — HOST_IP:HOST_PORT:CONTAINER_PORT. Она нужна, если вы хотите ограничить публикацию конкретным адресом, например только loopback-интерфейсом:
docker run -d --name web -p 127.0.0.1:8080:80 nginx:stable
Такой вариант удобен, если сервис должен быть доступен только локально, например за reverse proxy. Но именно здесь часто начинается путаница с IPv6: адрес IPv6 сам содержит двоеточия, а Docker тоже использует двоеточие как разделитель полей.
Если в параметре
-pфигурирует IPv6, адрес почти всегда нужно заключать в квадратные скобки. Иначе Docker не поймёт, где IP, а где порт.
Почему появляется too many colons in address
Сообщение too many colons in address означает, что Docker не смог корректно распарсить значение в -p или в секции ports compose-файла. На практике самая частая причина — IPv6-адрес без квадратных скобок.
Типичная ошибка с IPv6
Неправильный пример:
docker run -d --name web -p 2001:db8::10:8080:80 nginx:stable
Для человека структура ещё угадывается, а для парсера Docker уже нет: двоеточий слишком много, и он не понимает, где заканчивается адрес.
Правильный вариант:
docker run -d --name web -p [2001:db8::10]:8080:80 nginx:stable
Скобки однозначно отделяют IPv6-адрес от номера порта. Тот же принцип действует и в compose.
Когда ошибка не в IPv6, а в смешении форматов
Иногда проблема появляется из-за попытки скрестить несколько форм записи сразу. Например, пользователь добавляет лишнее поле через двоеточие или случайно оставляет пустую часть между разделителями.
docker run -p 127.0.0.1:80:80:tcp nginx:stable
docker run -p :::8080:80 nginx:stable
docker run -p 0.0.0.0::80 nginx:stable
Во всех этих случаях Docker либо сообщит о лишних двоеточиях, либо вернёт ошибку парсинга published port. Если нужен протокол, он указывается как суффикс, например 8080:80/tcp.
Docker Compose и YAML
В compose ошибка может выглядеть менее очевидно, потому что запись часто хранится в строке. Надёжный вариант для IPv4 и IPv6 такой:
services:
web:
image: nginx:stable
ports:
- "127.0.0.1:8080:80"
- "[2001:db8::10]:8443:443"
Для IPv6 в compose лучше всегда оставлять значение в кавычках и со скобками. Это избавляет от двусмысленности на уровне YAML и самого Docker.
Что означает bind address already in use
Ошибка bind address already in use означает уже не проблему синтаксиса, а конфликт на хосте: Docker пытается занять конкретный адрес и порт, но они уже используются другим процессом или контейнером.
На Debian и Ubuntu это чаще всего происходит в таких сценариях:
- порт
80или443уже занят Nginx, Apache или другим reverse proxy; - тот же host port уже опубликован другим контейнером;
- после рестарта или сбоя осталась старая привязка;
- есть конфликт между IPv4 и IPv6, когда процесс слушает слишком широко;
- systemd-служба стартовала раньше Docker и заняла нужный порт.
Важно помнить: Docker не делает порт доступным «сам по себе». Он создаёт привязку на стороне хоста. Если привязка уже существует, контейнер не сможет стартовать с тем же published port.

Как быстро найти, кто занял порт
Первый инструмент на Debian и Ubuntu — ss:
ss -ltnp
ss -ltnp '( sport = :80 )'
ss -ltnp '( sport = :443 )'
ss -ltnp '( sport = :8080 )'
Если нужен вывод с PID и именем процесса, используйте lsof:
lsof -iTCP:80 -sTCP:LISTEN -n -P
lsof -iTCP:443 -sTCP:LISTEN -n -P
lsof -iTCP:8080 -sTCP:LISTEN -n -P
Если подозрение на другой контейнер, проверьте Docker:
docker ps --format 'table {{.Names}} {{.Ports}}'
docker port CONTAINER_NAME
Если у вас уже были сложности с сетевой фильтрацией хоста, дополнительно полезно посмотреть, как Docker взаимодействует с firewall в материале про Docker, iptables и nftables.
Разница между конфликтом на IPv4 и IPv6
Один из неприятных случаев — когда кажется, что порт свободен, но Docker всё равно пишет bind address already in use. Часто это связано с тем, как процесс слушает сокет IPv6. На Linux приложение может слушать :: и при определённых настройках принимать также IPv4-подключения.
Поэтому проверять надо оба семейства адресов. Команда ss -ltnp обычно сразу показывает, на чём висит слушатель: 0.0.0.0:80, 127.0.0.1:8080 или [::]:80. Если сервис слушает [::]:80, это не значит, что IPv4 гарантированно свободен.
Практический вывод простой: публикация на конкретный адрес, например 127.0.0.1:8080:80, обычно предсказуемее, чем публикация на все интерфейсы через 8080:80.
Примеры безопасной публикации
Если сервис нужен только локально:
docker run -d --name app -p 127.0.0.1:8080:80 nginx:stable
Если нужен только IPv6 loopback:
docker run -d --name app -p [::1]:8080:80 nginx:stable
Если нужно слушать все интерфейсы:
docker run -d --name app -p 8080:80 nginx:stable
Последний вариант самый удобный для быстрых тестов, но именно он чаще всего упирается в конфликты.
Пошаговая диагностика на Debian и Ubuntu
Когда контейнер не стартует из-за публикации порта, лучше идти по короткому чек-листу, а не менять всё подряд.
- Проверьте синтаксис
-pили секцииports. Если есть IPv6, убедитесь, что адрес в квадратных скобках. - Проверьте, свободен ли порт через
ssиlsof. - Посмотрите, не занят ли тот же порт другим контейнером через
docker ps. - Убедитесь, что старый контейнер не завис в цикле рестартов.
- Проверьте, какие адреса реально слушает системный сервис: только IPv4, только IPv6 или оба варианта.
- Если проблема в compose-проекте, посмотрите
docker inspectи параметры сети.
Набор команд для полной картины:
docker ps -a
docker inspect CONTAINER_NAME
docker network ls
docker network inspect bridge
journalctl -u docker --no-pager -n 100
Особенно полезен journalctl: демон Docker часто пишет туда точную причину сбоя, включая адрес и порт bind-привязки.
Если контейнеры запускаются на отдельном сервере, удобнее и безопаснее делать такую диагностику на VDS, где проще контролировать сетевой стек, firewall и список сервисов на хосте.
Частые сценарии и как их исправить
Nginx или Apache уже занял 80/443
Очень частый сценарий: на хосте уже работает веб-сервер, а вы запускаете контейнер с -p 80:80 или -p 443:443. Docker получает конфликт и завершает запуск.
Обычно помогает один из трёх вариантов:
- публиковать контейнер на другой порт, например
127.0.0.1:8080:80; - освободить
80и443, если трафик должен принимать именно контейнер; - оставить фронтовой веб-сервер на хосте и проксировать запросы в контейнер по loopback.
Другой контейнер уже опубликовал тот же порт
В нескольких compose-проектах это происходит постоянно: один стек уже слушает 8080 или 3000, а второй пытается занять тот же порт.
docker ps --format 'table {{.Names}} {{.Ports}}'
Исправление простое: меняете host port или убираете лишнюю публикацию у контейнера, которому внешний доступ не нужен.
Ошибка из-за IPv6 в compose
Если в compose-файле есть строка без скобок вроде 2001:db8::1:8080:80, получите too many colons in address ещё до нормального старта контейнера.
services:
app:
image: nginx:stable
ports:
- "[2001:db8::1]:8080:80"
Порт как будто свободен, но контейнер не стартует
Тут часто виноваты dual stack, systemd socket units или остаточные процессы после перезапуска служб.
systemctl list-units --type=socket --all
systemctl status nginx apache2 docker
ss -ltnp | grep ':8080 '
ss -ltnp | grep ':80 '
ss -ltnp | grep ':443 '
Если случай редкий и плавающий, смотрите также логи Docker и журнал системы. В спорных ситуациях помогает поочерёдно выключить кандидатов и повторить запуск контейнера.
Как правильно работать с IPv6 в Docker
Здесь часто смешивают две разные темы: публикацию порта контейнера на IPv6-адресе хоста и полноценную работу IPv6 внутри Docker-сетей. Для ошибки too many colons in address обычно важна именно первая часть — корректная запись bind-адреса.
Если контейнер должен быть доступен по IPv6, проверьте четыре вещи:
- IPv6 реально настроен на интерфейсе хоста;
- firewall пропускает нужный порт;
- Docker получил адрес в правильном формате со скобками;
- само приложение внутри контейнера слушает нужный контейнерный порт.
Проверочные команды:
ip -6 addr show
ss -ltnp
docker inspect --format '{{json .NetworkSettings.Ports}}' CONTAINER_NAME
Если порт опубликован, но извне доступа нет, проблема уже не в синтаксисе публикации, а в маршрутизации, firewall или конфигурации приложения.
Для более жёстких требований к изоляции контейнеров может пригодиться разбор изоляции контейнеров через gVisor и Firecracker, особенно если на одном хосте работает несколько независимых сервисов.

Что лучше: publish на все интерфейсы или только localhost
С точки зрения безопасности и предсказуемости чаще выигрывает публикация на 127.0.0.1 или ::1 с фронтом через reverse proxy. Так меньше конфликтов, проще контролировать доступ и ниже риск случайно выставить служебный интерфейс наружу.
Публикация на все интерфейсы удобна для быстрых тестов, но в production именно она чаще приводит к вопросам вроде «кто уже занял порт» и «почему сервис неожиданно доступен извне».
Если контейнеру не нужен прямой доступ из интернета, публикуйте его на loopback и выводите наружу только через один контролируемый прокси-слой.
Короткий runbook: что делать за 2 минуты
- Увидели
too many colons in address— проверьте формат-pи квадратные скобки у IPv6. - Увидели
bind address already in use— сразу смотритеss -ltnpиdocker ps. - Если порт занят веб-сервером хоста, переводите контейнер на локальный порт вроде
127.0.0.1:8080:80. - Если конфликтует другой контейнер, меняйте host port или убирайте лишнюю публикацию.
- Если проблема в IPv6, проверьте адрес на интерфейсе и правила firewall.
- Если всё ещё непонятно, открывайте
journalctl -u dockerиdocker inspect.
Итог
Ошибки too many colons in address и bind address already in use относятся к одной теме — публикации портов Docker, но означают разное. Первая почти всегда указывает на неправильный синтаксис, особенно при работе с IPv6. Вторая говорит о реальном конфликте bind-привязки на хосте.
На Debian и Ubuntu лучший подход простой: сначала проверить формат записи port mapping, затем занятость порта, потом поведение IPv4 и IPv6. Такой порядок закрывает большинство кейсов без лишней магии.
Если держать в голове две идеи — IPv6 в publish пишется в квадратных скобках и один host port нельзя занять дважды — подобные ошибки обычно решаются за несколько минут.


