Проблема: Docker-контейнеры изолированы от внешнего мира
Контейнер запущен, на localhost:8080 всё работает, и тут кто-то просит ссылку. Docker-контейнеры живут в изолированной сети — даже с -p 8080:80 сервис доступен только на вашей машине. Чтобы показать проект коллеге, принять вебхук от Stripe или протестировать мобильное приложение с реального устройства, нужен публичный URL. Туннель решает эту задачу за 30 секунд — без проброса портов на роутере, без статического IP, без деплоя.
Стандартный docker-маппинг портов выглядит так:
docker run -p 8080:80 my-web-app
Контейнер слушает порт 80, Docker пробрасывает его на порт 8080 хоста. Вы открываете http://localhost:8080 — всё работает. Но этот адрес доступен только вам. Ни коллега, ни внешний сервис не смогут до него достучаться.
Почему обычный проброс портов не помогает
Проброс портов на роутере звучит как решение, но на практике это сложно, небезопасно и зачастую невозможно. Провайдер может выдать серый IP (CGNAT), в корпоративной сети к роутеру вас не пустят, а открытый порт — это постоянная дыра. Подробнее о разнице подходов — в статье «Как открыть localhost из интернета».
Типичные препятствия:
- CGNAT / серый IP — провайдер разделяет один публичный IP между десятками клиентов. Пробросить порт невозможно.
- Корпоративная сеть — доступ к роутеру закрыт политиками безопасности.
- Динамический IP — адрес меняется при каждом переподключении. Нужен DDNS, а это ещё одна точка отказа.
- Безопасность — открытый порт на роутере виден всему интернету. Без TLS и аутентификации это приглашение для атак.
Туннель обходит все эти ограничения: клиент устанавливает исходящее соединение с публичным сервером, и тот выдаёт HTTPS-адрес, ведущий на ваш контейнер. NAT, файрвол и динамический IP перестают быть проблемой.
Решение 1: fxTunnel на хосте (самый простой способ)
Самый быстрый путь: fxTunnel прямо на хосте. Контейнер пробрасывает порт через -p, fxTunnel делает к нему туннель. Две команды — готово.
Шаг 1. Установка fxTunnel
# Быстрая установка (Linux/macOS)
curl -fsSL https://fxtun.dev/install.sh | bash
# Проверяем
fxtunnel --version
Шаг 2. Запуск контейнера с пробросом порта
# Запускаем контейнер с маппингом порта на хост
docker run -d -p 8080:80 --name my-app nginx
Теперь nginx доступен на localhost:8080.
Шаг 3. Создание туннеля
# Открываем туннель к порту 8080 на хосте
fxtunnel http 8080
Вы получите публичный URL:
fxTunnel v1.x — tunnel is active
Public URL: https://docker-demo.fxtun.dev
Forwarding: https://docker-demo.fxtun.dev → http://localhost:8080
Готово. https://docker-demo.fxtun.dev теперь ведёт на nginx внутри контейнера. Любой человек с этим URL может открыть ваше приложение в браузере.
Когда использовать этот подход
- Быстрое тестирование и демонстрация — одна команда, минимум конфигурации.
- Локальная разработка — fxTunnel уже установлен на машине, не нужно менять
Dockerfileилиdocker-compose.yml. - Разовые задачи — показать проект коллеге, принять вебхук, протестировать интеграцию.
Решение 2: fxTunnel внутри Docker-контейнера
Для воспроизводимых окружений, CI/CD пайплайнов и командной работы лучше добавить fxTunnel как отдельный сервис в docker-compose. Туннель поднимается и останавливается вместе с остальными контейнерами, конфигурация хранится в коде, и каждый разработчик получает одинаковое окружение.
docker-compose.yml с fxTunnel
version: "3.8"
services:
web:
image: nginx
ports:
- "8080:80"
tunnel:
image: ghcr.io/mephistofox/fxtunnel:latest
command: ["http", "web:80"]
depends_on:
- web
Запуск:
docker compose up
fxTunnel внутри контейнера обращается к сервису web по имени (Docker DNS), минуя необходимость проброса на хост. Туннель выведет публичный URL в логах.
Пояснение: как контейнеры видят друг друга
Docker Compose создаёт общую сеть для всех сервисов в файле. Контейнер tunnel может обращаться к контейнеру web по имени сервиса: web:80. Это внутренняя DNS-резолюция Docker, порт 80 — это порт внутри контейнера, а не на хосте.
Практические сценарии
Сценарий 1: HTTP-туннель для веб-приложения
Допустим, вы собираете React/Vue/Next.js приложение в Docker и хотите показать результат коллеге или заказчику.
version: "3.8"
services:
frontend:
build: .
ports:
- "3000:3000"
volumes:
- .:/app
environment:
- NODE_ENV=development
tunnel:
image: ghcr.io/mephistofox/fxtunnel:latest
command: ["http", "frontend:3000"]
depends_on:
- frontend
docker compose up
# tunnel_1 | Public URL: https://xyz.fxtun.dev
# tunnel_1 | Forwarding: https://xyz.fxtun.dev → http://frontend:3000
Отправьте ссылку https://xyz.fxtun.dev заказчику — он увидит ваше приложение без деплоя.
Сценарий 2: TCP-туннель для базы данных
Хотите дать коллеге доступ к dev-базе PostgreSQL из Docker? TCP-туннель откроет порт базы через публичный адрес.
На хосте (если порт проброшен):
# PostgreSQL проброшен на хост: docker run -p 5432:5432 postgres
fxtunnel tcp 5432
В docker-compose:
version: "3.8"
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: devpass
POSTGRES_DB: myapp
ports:
- "5432:5432"
db-tunnel:
image: ghcr.io/mephistofox/fxtunnel:latest
command: ["tcp", "db:5432"]
depends_on:
- db
docker compose up
# db-tunnel_1 | TCP tunnel active
# db-tunnel_1 | tcp://db-demo.fxtun.dev:18432 → tcp://db:5432
Коллега подключается к базе данных:
psql -h db-demo.fxtun.dev -p 18432 -U postgres -d myapp
Сценарий 3: несколько туннелей для микросервисов
Работаете с микросервисным стеком — фронтенд, API, база данных? Каждому сервису нужен свой туннель. В docker-compose просто добавьте отдельный сервис-туннель для каждого.
version: "3.8"
services:
frontend:
build: ./frontend
ports:
- "3000:3000"
api:
build: ./api
ports:
- "4000:4000"
environment:
- DATABASE_URL=postgres://postgres:devpass@db:5432/myapp
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: devpass
POSTGRES_DB: myapp
tunnel-frontend:
image: ghcr.io/mephistofox/fxtunnel:latest
command: ["http", "frontend:3000"]
depends_on:
- frontend
tunnel-api:
image: ghcr.io/mephistofox/fxtunnel:latest
command: ["http", "api:4000"]
depends_on:
- api
tunnel-db:
image: ghcr.io/mephistofox/fxtunnel:latest
command: ["tcp", "db:5432"]
depends_on:
- db
docker compose up
# tunnel-frontend_1 | https://front-xyz.fxtun.dev → http://frontend:3000
# tunnel-api_1 | https://api-xyz.fxtun.dev → http://api:4000
# tunnel-db_1 | tcp://db-xyz.fxtun.dev:19432 → tcp://db:5432
Три публичных адреса, три сервиса — всё поднимается одним docker compose up.
Docker Compose + fxTunnel: готовый рецепт
Ниже — полный шаблон docker-compose для типичного веб-приложения с API, базой данных и туннелями. Скопируйте, измените имена сервисов и порты под свой проект.
version: "3.8"
services:
# === Ваше приложение ===
app:
build: .
ports:
- "8080:8080"
environment:
- DATABASE_URL=postgres://postgres:devpass@db:5432/myapp
depends_on:
- db
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: devpass
POSTGRES_DB: myapp
volumes:
- pgdata:/var/lib/postgresql/data
# === Туннели ===
tunnel-app:
image: ghcr.io/mephistofox/fxtunnel:latest
command: ["http", "app:8080"]
depends_on:
- app
restart: unless-stopped
tunnel-db:
image: ghcr.io/mephistofox/fxtunnel:latest
command: ["tcp", "db:5432"]
depends_on:
- db
restart: unless-stopped
volumes:
pgdata:
Команды для работы:
# Запуск всего стека
docker compose up -d
# Просмотр логов туннелей (там будут публичные URL)
docker compose logs tunnel-app tunnel-db
# Остановка
docker compose down
Советы по работе с Docker-сетями и туннелями
host.docker.internal
Если fxTunnel запущен внутри контейнера и вам нужно добраться до сервиса на хост-машине (не в Docker), используйте специальный адрес host.docker.internal:
tunnel:
image: ghcr.io/mephistofox/fxtunnel:latest
command: ["http", "host.docker.internal:3000"]
extra_hosts:
- "host.docker.internal:host-gateway"
Директива extra_hosts нужна на Linux — на macOS и Windows host.docker.internal работает из коробки.
Bridge-сеть по умолчанию
Docker Compose автоматически создаёт bridge-сеть для всех сервисов в файле. Контейнеры видят друг друга по имени сервиса. Если вы используете docker run без Compose, контейнеры находятся в сети bridge по умолчанию и обращаются друг к другу по IP-адресу, а не по имени. В этом случае проще запустить fxTunnel на хосте (решение 1).
Несколько сетей
Если ваши контейнеры находятся в разных Docker-сетях, убедитесь, что контейнер с fxTunnel подключён к той же сети, что и целевой сервис:
services:
web:
image: nginx
networks:
- frontend
tunnel:
image: ghcr.io/mephistofox/fxtunnel:latest
command: ["http", "web:80"]
networks:
- frontend
networks:
frontend:
Проверка доступности изнутри контейнера
Если туннель не подключается к целевому сервису, проверьте сетевую связность:
# Заходим в контейнер туннеля
docker compose exec tunnel sh
# Проверяем, доступен ли целевой сервис
wget -qO- http://web:80
FAQ
Можно ли открыть Docker-контейнер в интернет без проброса портов на роутере?
Да. fxTunnel открывает исходящее соединение с relay-сервером и получает HTTPS-адрес, ведущий прямо на порт контейнера. Соединение инициируется изнутри вашей сети, поэтому NAT и файрвол не мешают. Подробнее о том, как устроены туннели.
Лучше запускать fxTunnel на хосте или внутри контейнера?
Зависит от задачи. Для быстрой демонстрации проще на хосте — одна команда, ничего настраивать не надо. Если нужна воспроизводимость или CI/CD — добавьте как docker-compose сервис, тогда жизненный цикл туннеля совпадает с контейнерами.
Как открыть несколько контейнеров одновременно?
По одному туннелю на сервис. На хосте — несколько команд fxtunnel с разными портами. В docker-compose — отдельный сервис-туннель для каждого. Несколько одновременных туннелей работают и на бесплатном тарифе.
Работает ли TCP-туннель для доступа к базе данных в Docker?
Да. fxtunnel tcp 5432 создаёт TCP-туннель к порту PostgreSQL и выдаёт адрес вида tcp://xxx.fxtun.dev:12345. Любой стандартный клиент БД может к нему подключиться — удобно для совместной отладки или подключения внешних инструментов к dev-базе.
Безопасно ли открывать Docker-контейнеры через туннель?
Для разработки и тестирования — да, весь трафик шифруется TLS. Только не направляйте туннель на продакшен-контейнеры с реальными данными. Закончили работу — остановите туннель. Сервисы с чувствительными данными не стоит открывать без дополнительной защиты. Подробнее о безопасности — в статье «Как открыть localhost из интернета».