Проблема: микросервисам нужно несколько точек входа

Современные приложения редко укладываются в один процесс. Типичный стек – это фронтенд, 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+ одновременных туннелей с кастомными доменами, чтобы у каждого сервиса был стабильный и запоминающийся адрес.