Проблема: микросервисам нужно несколько точек входа
Современные приложения редко укладываются в один процесс. Типичный стек – это фронтенд, API-шлюз, несколько бэкенд-сервисов, база данных и, может быть, очередь сообщений. При локальной разработке каждый сервис живёт на своём порту: фронтенд на localhost:3000, API на localhost:4000, платёжный сервис на localhost:4100 и так далее.
На вашей машине всё работает отлично. Но как только нужен внешний доступ – коллега тестирует вашу ветку, вебхук от Stripe приходит на платёжный сервис, или мобильное приложение подключается к API – нужны публичные URL. Не один, а несколько – по одному для каждого сервиса, который принимает внешний трафик.
Одним туннелем тут не обойтись. Нужно несколько туннелей одновременно, каждый направленный на свой сервис. В этом гайде разбираем, как настроить такую конфигурацию с fxTunnel – и на хосте, и с Docker Compose – и как связать микросервисы через URL туннелей.
Архитектура: один туннель на сервис
Идея проста: каждый микросервис, которому нужен внешний доступ, получает свой туннель. Внутренние сервисы, которые общаются только с другими локальными сервисами, не нуждаются в туннеле — они используют Docker-сеть или localhost напрямую.
Интернет
│
┌────────────┼────────────┐
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌───────────┐ ┌──────────────┐
│ fxTunnel │ │ fxTunnel │ │ fxTunnel │
│ Сервер │ │ Сервер │ │ Сервер │
│ (HTTPS) │ │ (HTTPS) │ │ (TCP) │
└──────┬──────┘ └─────┬─────┘ └──────┬───────┘
│ │ │
┌──────▼──────┐ ┌─────▼─────┐ ┌──────▼───────┐
│ Туннель 1 │ │ Туннель 2 │ │ Туннель 3 │
│ :3000 │ │ :4000 │ │ :5432 │
└──────┬──────┘ └─────┬─────┘ └──────┬───────┘
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌───────────┐ ┌──────────────┐
│ Фронтенд │ │ API │ │ PostgreSQL │
│ (React) │ │ (Node) │ │ │
│ порт 3000 │ │ порт 4000│ │ порт 5432 │
└─────────────┘ └───────────┘ └──────────────┘
Машина разработчика
Каждый туннель создаёт свой публичный URL. Фронтенд доступен по адресу https://front-abc.fxtun.dev, API по адресу https://api-xyz.fxtun.dev, а база данных через TCP-туннель по адресу tcp://db-qrs.fxtun.dev:18432.
Что нуждается в туннеле, а что нет
Не каждый сервис в вашем стеке нуждается в туннеле. Простое правило:
| Сервис | Нужен туннель? | Почему |
|---|---|---|
| Фронтенд (React, Vue) | Да | Доступ из браузеров, мобильных приложений, от коллег |
| API-шлюз | Да | Вызывается фронтендом, вебхуками, внешними клиентами |
| Платёжный сервис | Да | Принимает вебхуки от Stripe, PayPal |
| Сервис авторизации | Возможно | Только если OAuth-коллбэки приходят от внешних провайдеров |
| База данных | Редко | Только если коллеге нужен прямой доступ |
| Redis / Очередь сообщений | Нет | Только внутренняя коммуникация |
| Воркер / Фоновые задачи | Нет | Обрабатывает сообщения из очереди, входящего трафика нет |
Быстрый старт: несколько туннелей из CLI
Самый быстрый способ запустить несколько туннелей — открыть несколько терминалов и запустить fxtunnel в каждом.
Шаг 1. Установка fxTunnel
# Быстрая установка (Linux/macOS)
curl -fsSL https://fxtun.dev/install.sh | bash
# Проверяем
fxtunnel --version
Шаг 2. Запуск сервисов
# Терминал 1: Фронтенд
cd frontend && npm run dev
# → listening on localhost:3000
# Терминал 2: API
cd api && npm run dev
# → listening on localhost:4000
# Терминал 3: Платёжный сервис
cd payment-service && go run main.go
# → listening on localhost:4100
Шаг 3. Открытие туннеля для каждого сервиса
# Терминал 4: Туннель для фронтенда
fxtunnel http 3000
# Терминал 5: Туннель для API
fxtunnel http 4000
# Терминал 6: Туннель для платёжного сервиса
fxtunnel http 4100
Каждая команда выводит публичный URL:
fxTunnel v1.x — tunnel is active
Public URL: https://front-abc.fxtun.dev
Forwarding: https://front-abc.fxtun.dev → http://localhost:3000
Три сервиса, три туннеля, три публичных URL — всё работает одновременно на бесплатном плане.
Автоматизация: bash-скрипт для нескольких туннелей
Открывать шесть окон терминала неудобно для ежедневной работы. Простой bash-скрипт запускает все сервисы и туннели в фоне и собирает URL.
#!/bin/bash
# start-tunnels.sh — Запуск нескольких туннелей для микросервисов
set -e
SERVICES=(
"frontend:3000:http"
"api:4000:http"
"payment:4100:http"
"db:5432:tcp"
)
LOG_DIR="/tmp/tunnels"
mkdir -p "$LOG_DIR"
echo "Запуск туннелей..."
for entry in "${SERVICES[@]}"; do
IFS=':' read -r name port proto <<< "$entry"
log_file="$LOG_DIR/${name}.log"
fxtunnel "$proto" "$port" --log-file "$log_file" &
echo " $name ($proto:$port) — PID $!"
done
# Ждём появления URL
sleep 3
echo ""
echo "=== URL туннелей ==="
for entry in "${SERVICES[@]}"; do
IFS=':' read -r name port proto <<< "$entry"
log_file="$LOG_DIR/${name}.log"
url=$(grep -oP 'https?://[a-z0-9-]+\.fxtun\.dev(:[0-9]+)?' "$log_file" 2>/dev/null || echo "ожидание...")
echo " $name → $url"
done
echo ""
echo "Нажмите Ctrl+C для остановки всех туннелей"
wait
Использование:
chmod +x start-tunnels.sh
./start-tunnels.sh
Вывод:
Запуск туннелей...
frontend (http:3000) — PID 12345
api (http:4000) — PID 12346
payment (http:4100) — PID 12347
db (tcp:5432) — PID 12348
=== URL туннелей ===
frontend → https://front-abc.fxtun.dev
api → https://api-xyz.fxtun.dev
payment → https://pay-def.fxtun.dev
db → tcp://db-qrs.fxtun.dev:18432
Нажмите Ctrl+C для остановки всех туннелей
При нажатии Ctrl+C все фоновые процессы завершаются, и туннели закрываются.
Docker Compose: подход для команды
Для командной работы и воспроизводимых окружений Docker Compose — правильный инструмент. Каждый микросервис и его туннель определяются как отдельные сервисы. Весь стек поднимается одной командой.
Полный микросервисный стек с туннелями
# docker-compose.yml — Микросервисы с туннелями
version: "3.8"
services:
# === Сервисы приложения ===
frontend:
build: ./frontend
ports:
- "3000:3000"
environment:
- REACT_APP_API_URL=${API_TUNNEL_URL:-http://localhost:4000}
depends_on:
- api
api:
build: ./api
ports:
- "4000:4000"
environment:
- DATABASE_URL=postgres://postgres:devpass@db:5432/myapp
- REDIS_URL=redis://redis:6379
- PAYMENT_SERVICE_URL=http://payment:4100
depends_on:
- db
- redis
payment:
build: ./payment-service
ports:
- "4100:4100"
environment:
- DATABASE_URL=postgres://postgres:devpass@db:5432/myapp
- STRIPE_WEBHOOK_SECRET=${STRIPE_WEBHOOK_SECRET}
depends_on:
- db
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: devpass
POSTGRES_DB: myapp
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7-alpine
# === Туннели ===
tunnel-frontend:
image: ghcr.io/mephistofox/fxtunnel:latest
command: ["http", "frontend:3000"]
depends_on:
- frontend
restart: unless-stopped
tunnel-api:
image: ghcr.io/mephistofox/fxtunnel:latest
command: ["http", "api:4000"]
depends_on:
- api
restart: unless-stopped
tunnel-payment:
image: ghcr.io/mephistofox/fxtunnel:latest
command: ["http", "payment:4100"]
depends_on:
- payment
restart: unless-stopped
volumes:
pgdata:
Запуск:
docker compose up -d
# Просмотр URL туннелей
docker compose logs tunnel-frontend tunnel-api tunnel-payment
Вывод:
tunnel-frontend_1 | Public URL: https://front-abc.fxtun.dev → http://frontend:3000
tunnel-api_1 | Public URL: https://api-xyz.fxtun.dev → http://api:4000
tunnel-payment_1 | Public URL: https://pay-def.fxtun.dev → http://payment:4100
Три туннеля, три публичных URL — всё управляется через Docker Compose.
Как работает внутренняя коммуникация
Сервисы внутри Docker Compose общаются через внутреннюю DNS Docker — туннели для межсервисных вызовов не нужны. API обращается к базе данных по адресу db:5432, API обращается к Redis по адресу redis:6379, а API вызывает платёжный сервис по адресу payment:4100. Всё это происходит внутри Docker-сети без дополнительных затрат.
Туннели нужны только для трафика, приходящего извне Docker-сети: из браузеров, мобильных приложений, от вебхуков и коллег.
Внешний трафик (через туннели) Внутренний трафик (Docker DNS) ────────────────────────────── ───────────────────────────── Браузер → туннель → frontend frontend → api (http://api:4000) Stripe → туннель → payment api → db (postgres://db:5432) Моб. → туннель → api api → redis (redis://redis:6379) Коллега → туннель → api api → payment (http://payment:4100)
Обнаружение сервисов: связь микросервисов через URL туннелей
Самая непростая часть работы с несколькими туннелями — связать сервисы друг с другом. Фронтенд должен знать публичный URL API для запросов из браузера. Платёжный сервис должен сообщить Stripe, куда отправлять вебхуки. Есть несколько подходов.
Подход 1: переменные окружения (самый простой)
После запуска туннелей считайте URL из логов и передайте как переменные окружения:
#!/bin/bash
# start-with-discovery.sh
# Запускаем стек
docker compose up -d
# Ждём инициализации туннелей
sleep 5
# Извлекаем URL туннелей из логов
API_URL=$(docker compose logs tunnel-api 2>&1 | grep -oP 'https://[a-z0-9-]+\.fxtun\.dev' | head -1)
FRONTEND_URL=$(docker compose logs tunnel-frontend 2>&1 | grep -oP 'https://[a-z0-9-]+\.fxtun\.dev' | head -1)
PAYMENT_URL=$(docker compose logs tunnel-payment 2>&1 | grep -oP 'https://[a-z0-9-]+\.fxtun\.dev' | head -1)
echo "API: $API_URL"
echo "Frontend: $FRONTEND_URL"
echo "Payment: $PAYMENT_URL"
# Перезапускаем фронтенд с URL API
API_TUNNEL_URL=$API_URL docker compose up -d frontend
# Настраиваем Stripe-вебхук на URL платёжного туннеля
echo "Укажите URL вебхука Stripe: $PAYMENT_URL/webhooks/stripe"
Подход 2: общий .env-файл
Сгенерируйте .env-файл, который Docker Compose подхватит автоматически:
#!/bin/bash
# generate-env.sh — Извлечение URL туннелей в .env
docker compose up -d tunnel-api tunnel-frontend tunnel-payment
sleep 5
API_URL=$(docker compose logs tunnel-api 2>&1 | grep -oP 'https://[a-z0-9-]+\.fxtun\.dev' | head -1)
FRONTEND_URL=$(docker compose logs tunnel-frontend 2>&1 | grep -oP 'https://[a-z0-9-]+\.fxtun\.dev' | head -1)
PAYMENT_URL=$(docker compose logs tunnel-payment 2>&1 | grep -oP 'https://[a-z0-9-]+\.fxtun\.dev' | head -1)
cat > .env.tunnels <<EOF
API_TUNNEL_URL=$API_URL
FRONTEND_TUNNEL_URL=$FRONTEND_URL
PAYMENT_TUNNEL_URL=$PAYMENT_URL
EOF
echo "URL туннелей записаны в .env.tunnels"
echo "Перезапуск сервисов с новыми URL..."
# Объединяем с существующим .env
cat .env .env.tunnels > .env.combined && mv .env.combined .env
docker compose up -d
Подход 3: кастомные домены (платный план)
С планом fxTunnel Pro (от $5/мес) вы можете назначить кастомные поддомены для туннелей. Это значит, что URL предсказуемы и их можно захардкодить в конфигурации:
tunnel-api:
image: ghcr.io/mephistofox/fxtunnel:latest
command: ["http", "api:4000", "--domain", "api.myteam.fxtun.dev"]
depends_on:
- api
tunnel-frontend:
image: ghcr.io/mephistofox/fxtunnel:latest
command: ["http", "frontend:3000", "--domain", "app.myteam.fxtun.dev"]
depends_on:
- frontend
Теперь api.myteam.fxtun.dev и app.myteam.fxtun.dev всегда ведут на нужные сервисы. Скрипты для извлечения URL не нужны.
Командная работа: общий доступ к микросервисному стеку
Когда несколько разработчиков работают над одной микросервисной архитектурой, каждому нужен свой набор туннелей. Вот практические паттерны для командной работы.
Паттерн 1: каждый разработчик запускает свой стек
Самый простой подход: каждый разработчик запускает docker compose up на своей машине. Каждый получает уникальные URL туннелей. URL передаются через командный чат, когда нужен фидбэк.
Разработчик A Разработчик B
────────────── ──────────────
frontend → https://a-front.fxtun.dev frontend → https://b-front.fxtun.dev
api → https://a-api.fxtun.dev api → https://b-api.fxtun.dev
payment → https://a-pay.fxtun.dev payment → https://b-pay.fxtun.dev
Это хорошо работает для небольших команд (2-5 разработчиков). Стек каждого разработчика полностью изолирован. Конфликтов нет.
Паттерн 2: общий бэкенд, локальный фронтенд
Во многих проектах фронтенд меняется чаще бэкенда. Один разработчик запускает бэкенд с туннелями, а остальные направляют свои локальные фронтенды на общий API.
# Разработчик A (запускает полный стек)
docker compose up -d
# API: https://shared-api.fxtun.dev
# Разработчик B (запускает только фронтенд)
REACT_APP_API_URL=https://shared-api.fxtun.dev npm run dev
Это снижает потребление ресурсов и позволяет не запускать весь стек на каждой машине.
Паттерн 3: preview-окружения в CI/CD
Для ревью пул-реквестов комбинируйте туннели с CI/CD. Каждый PR получает свой набор туннелей через GitHub Actions, и ревьюеры переходят по ссылке вместо того, чтобы выкачивать ветку локально.
Отладка нескольких туннелей
Когда туннелей несколько, разбираться, какой сервис получил какой запрос, становится важнее. Инспектор трафика fxTunnel (доступен на плане Pro от $5/мес) записывает каждый HTTP-запрос через каждый туннель – с заголовками, телом и таймингами. Replay позволяет повторить любой запрос одним кликом.
Просмотр логов по сервисам
В Docker Compose вы можете просматривать логи конкретного туннеля:
# Логи только туннеля API
docker compose logs -f tunnel-api
# Логи всех туннелей
docker compose logs -f tunnel-frontend tunnel-api tunnel-payment
Проверка статуса туннелей
Убедитесь, что все туннели работают:
# Список запущенных контейнеров-туннелей
docker compose ps | grep tunnel
# Ожидаемый вывод:
# tunnel-frontend_1 ghcr.io/mephistofox/fxtunnel:latest Up 2 minutes
# tunnel-api_1 ghcr.io/mephistofox/fxtunnel:latest Up 2 minutes
# tunnel-payment_1 ghcr.io/mephistofox/fxtunnel:latest Up 2 minutes
Проверка связности
Если туннель не пробрасывает трафик, проверьте доступность сервиса изнутри контейнера туннеля:
# Заходим в контейнер туннеля
docker compose exec tunnel-api sh
# Проверяем целевой сервис
wget -qO- http://api:4000/health
Если wget не работает, проблема в Docker-сети, а не в туннеле. Убедитесь, что оба контейнера находятся в одной Docker-сети.
Продвинутый уровень: выборочные туннели с профилями Docker Compose
Вам не всегда нужны все туннели. Профили Docker Compose позволяют запускать только нужные:
version: "3.8"
services:
frontend:
build: ./frontend
ports:
- "3000:3000"
api:
build: ./api
ports:
- "4000:4000"
payment:
build: ./payment-service
ports:
- "4100:4100"
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: devpass
tunnel-frontend:
image: ghcr.io/mephistofox/fxtunnel:latest
command: ["http", "frontend:3000"]
depends_on:
- frontend
profiles:
- tunnels
- frontend-tunnel
tunnel-api:
image: ghcr.io/mephistofox/fxtunnel:latest
command: ["http", "api:4000"]
depends_on:
- api
profiles:
- tunnels
- api-tunnel
tunnel-payment:
image: ghcr.io/mephistofox/fxtunnel:latest
command: ["http", "payment:4100"]
depends_on:
- payment
profiles:
- tunnels
- webhook-tunnel
Использование:
# Запуск без туннелей (локальная разработка)
docker compose up -d
# Запуск со всеми туннелями
docker compose --profile tunnels up -d
# Запуск только с туннелем API (для тестирования вебхуков)
docker compose --profile api-tunnel up -d
# Запуск только с туннелем платёжного сервиса (для тестирования Stripe)
docker compose --profile webhook-tunnel up -d
Профили делают ваше окружение разработки гибким — туннели доступны, когда нужны, и не мешают, когда не нужны.
Тарифы: сколько туннелей можно запустить?
| План | Цена | Одновременных туннелей | Возможности |
|---|---|---|---|
| Free | $0 | Несколько (без жёсткого лимита) | HTTP/TCP/UDP, без ограничений на соединения |
| Pro | от $5/мес | Несколько | Кастомные домены, инспектор запросов, replay |
| Team | от $10/мес | 10+ | Приоритетная поддержка, командное управление |
Бесплатного плана хватает большинству разработчиков с 3-5 туннелями для микросервисов. Для команд, которым нужны предсказуемые кастомные домены и инспектор трафика, план Pro за $5/мес покрывает основные потребности. План Team за $10/мес рассчитан на крупные сетапы с 10+ одновременными туннелями.
В отличие от ngrok, который ограничивает бесплатных пользователей одним туннелем, fxTunnel позволяет запустить весь стек с публичными URL бесплатно. Полное сравнение – в статье ngrok vs Cloudflare vs fxTunnel.
Лучшие практики
1. Создавайте туннели только для того, что нуждается во внешнем доступе
Не создавайте туннели для баз данных, Redis или очередей сообщений, если только коллеге не нужен прямой доступ к ним. Внутренние сервисы общаются через Docker-сеть без дополнительных затрат.
2. Используйте скрипт или Makefile
Оберните запуск туннелей в скрипт или Makefile, чтобы каждый разработчик в команде использовал одинаковую настройку:
# Makefile
.PHONY: dev dev-tunnels
dev:
docker compose up -d
dev-tunnels:
docker compose --profile tunnels up -d
@sleep 3
@echo "=== URL туннелей ==="
@docker compose logs tunnel-frontend tunnel-api 2>&1 | grep -oP 'https://[a-z0-9-]+\.fxtun\.dev'
3. Закрывайте туннели после работы
Открытые туннели — это публичные URL, доступные любому, кто знает адрес. Для безопасности закрывайте туннели после завершения сессии:
# Остановить все туннели, но оставить сервисы работающими
docker compose stop tunnel-frontend tunnel-api tunnel-payment
# Или остановить всё
docker compose down
4. Используйте .env-файлы для командной конфигурации
Храните конфигурацию, связанную с туннелями, в .env-файлах, которые исключены из системы контроля версий:
# .env (не коммитится в git)
STRIPE_WEBHOOK_SECRET=whsec_test_xxx
API_TUNNEL_URL=https://api-xyz.fxtun.dev
5. Документируйте настройку
Добавьте раздел в README проекта с описанием запуска туннелей. Будущие участники команды скажут вам спасибо.
FAQ
Можно ли запустить несколько туннелей одновременно бесплатно?
Да – жёсткого лимита на количество одновременных туннелей на бесплатном плане нет, так что можно открыть по одному на каждый микросервис. Кастомные домены и инспектор трафика доступны с планом за $5/мес, а план за $10/мес добавляет 10+ одновременных туннелей и приоритетную поддержку.
Как микросервисы обнаруживают друг друга через туннели?
Самый простой способ – переменные окружения: передайте URL каждого туннеля сервисам, которым он нужен. Например, фронтенд получает REACT_APP_API_URL с адресом туннеля API, а API – PAYMENT_SERVICE_URL с адресом туннеля платёжного сервиса. В docker-compose стартовый скрипт может считать URL из логов и пробросить их до запуска зависимых сервисов.
Нужен ли отдельный туннель для каждого микросервиса?
Только для тех, к которым нужен доступ снаружи. Всё, что общается исключительно с другими локальными сервисами – базы данных, Redis, фоновые воркеры – может использовать Docker-сеть или localhost напрямую. Туннели нужны только сервисам, к которым обращаются внешние клиенты, вебхуки или коллеги.
Замедлят ли несколько туннелей мою машину?
Заметного влияния не будет. Каждый процесс fxTunnel потребляет порядка 10-15 МБ RAM и минимум CPU. Запуск 5-10 туннелей одновременно практически не сказывается на производительности. Клиент – один бинарник на Go, оптимизированный для низкого потребления ресурсов.
Может ли команда совместно использовать один набор туннелей?
Да – когда туннели запущены, достаточно скинуть публичные URL в Slack или командный чат. Для более постоянной настройки план за $10/мес даёт 10+ одновременных туннелей с кастомными доменами, чтобы у каждого сервиса был стабильный и запоминающийся адрес.