Сети Docker и проброс портов
Как контейнеры видят друг друга, чем отличается публикация порта от простого EXPOSE и почему Docker обходит правила ufw.
Обновлено 23 августа 2026 г.
Половина проблем с контейнерами — сетевые: сервис не достучался до базы, порт занят, панель внезапно открыта всему интернету. Разберём, как это устроено.
Три типа сетей
- bridge — по умолчанию. Контейнеры получают внутренние адреса и общаются между собой; наружу выходят через NAT машины.
- host — контейнер использует сеть машины напрямую, публикация портов не нужна и не работает. Быстро, но никакой изоляции: порт 80 контейнера — это порт 80 машины.
- none — сети нет вообще. Для задач, которым она не нужна.
Собственная сеть, созданная вами или Compose, отличается от стандартной bridge одной важной вещью: в ней работает разрешение имён. Контейнер app достучится до контейнера db по имени db.
docker network create backend
docker network ls
docker network inspect backend
В Compose такая сеть создаётся автоматически на каждый проект.
Публикация портов
ports:
- "127.0.0.1:8080:8080" # доступен только с самой машины
- "80:80" # доступен всему интернету
Слева — порт на машине, справа — внутри контейнера. Директива EXPOSE в образе ничего не публикует: это лишь пометка о том, какой порт слушает приложение.
Правило простое: публикуйте наружу только то, что должно быть доступно снаружи. База данных, панель управления, внутреннее API — на 127.0.0.1, а наружу их выпускает веб-сервер с сертификатом.
Docker пишет правила
iptablesсам и делает это раньше цепочек, которыми управляетufw. Порт, опубликованный как-p 8080:8080, будет доступен из интернета, даже еслиufwпоказывает «deny». Не полагайтесь на брандмауэр — привязывайте порт к127.0.0.1.
Связь между контейнерами
services:
app:
networks: [frontend, backend]
db:
networks: [backend]
proxy:
networks: [frontend]
ports:
- "80:80"
- "443:443"
networks:
frontend:
backend:
Здесь база доступна только приложению, а наружу смотрит один лишь веб-сервер. Это правильная схема для любого стека.
Внутри контейнера обращайтесь к соседу по имени службы: postgres://db:5432/app. Адрес localhost внутри контейнера означает сам контейнер, а не машину, — вторая по частоте ошибка после latest.
Достучаться с контейнера до службы, работающей на самой машине, можно по имени host.docker.internal, если добавить:
extra_hosts:
- "host.docker.internal:host-gateway"
Диагностика
docker compose exec app ping -c2 db
docker compose exec app getent hosts db
sudo ss -tulpn | grep LISTEN
docker port <контейнер>
ss покажет, на каком интерфейсе реально слушает порт: 0.0.0.0:8080 — открыт наружу, 127.0.0.1:8080 — нет.
Частые ошибки
- «Порт уже занят». Его держит другой контейнер или служба машины.
sudo ss -tulpn | grep :80найдёт виновника. - Контейнеры в разных проектах Compose не видят друг друга. У каждого проекта своя сеть. Свяжите их явно через
networks: { shared: { external: true } }. - Приложение слушает
127.0.0.1внутри контейнера. Тогда публикация не поможет: снаружи контейнера туда не попасть. Настройте приложение слушать0.0.0.0. - Полоса. До 500 Мбит/с общие на машину. Раздача файлов и VPN делят её со всем остальным — см. VPN на Outline.