Самостоятельный хостинг

Flow поставляется контейнерами и поднимается через docker compose: в репозитории лежит готовый файл infra/prod/docker-compose.yml и пример переменных окружения к нему (infra/prod/.env.example). Это одна машина, на которой работает всё: приложение, база, очередь, векторный поиск, изолированная песочница для кода и входной прокси.

Тарифные ограничения, о которых говорят остальные страницы документации, на своей установке ваши. При первом старте в базу засеваются два плана — free и paid — но их числа объявлены в коде как заготовки, и администратор инстанса правит их прямо в админке. Так что «на бесплатном нельзя Telegram» — это решение того, кто держит установку, а не свойство платформы.

Из чего состоит стек

Девять контейнеров. Их роли не взаимозаменяемы: три из них хранят состояние, два исполняют воркфлоу, два раздают интерфейс, один изолирует чужой код, один принимает трафик.

КонтейнерЧто этоСостояние
postgresосновная база: воркфлоу, запуски, история чатов, учёт кредитовтом postgres_data
redisочередь запусков, кросс-процессная шина событий (SSE), рантайм-состояниетом redis_data
qdrantвекторное хранилище баз знанийтом qdrant_data
apiHTTP API и SSE; он же применяет миграции при стартетом app_data (файлы)
workerисполнение воркфлоу из очереди — тот же образ, другая командатом app_data (тот же)
js-executorизолированная песочница для нод с кодомнет
frontendсобранный SPA — сам редакторнет
webсайт: документация, примеры, страница публичного чатанет
edgenginx: единственный публичный вход, маршрутизация по доменунет

Три вещи в этой раскладке неочевидны, и все три ломаются молча.

api и worker — один и тот же образ. Отличаются командой запуска и тем, что миграции применяет только api (RUN_MIGRATIONS=1): двух мигрирующих процессов быть не должно. Движок при этом живёт в обоих — часть запусков (синхронный вызов, ход чата, агент как инструмент, суб-воркфлоу) выполняется прямо в api.

Том с файлами обязан быть примонтирован в оба. app_data держит вложения сообщений, сгенерированные ноды файлы и исходники документов баз знаний. Пишет их движок из worker, а раздаёт api — при раздельных файловых системах скачивание отдаёт 404, а без тома вообще всё пользовательское содержимое уничтожается первым же up --force-recreate.

У песочницы кода отдельная сеть и ни одного секрета. js-executor подключён к сети, где нет ни базы, ни Redis, ни Qdrant, запущен с read-only корнем, без capabilities, с ограничением по памяти и числу процессов, и env_file ему намеренно не выдаётся. Смысл в том, что побег из песочницы попадает в контейнер, в котором нечего красть. Подробнее про сами ноды — в «Свой код».

Что обязательно настроить до первого запуска

Порядок такой: скопировать infra/prod/.env.example в infra/prod/.env и заполнить. С ENVIRONMENT=production приложение проверяет конфигурацию на старте и отказывается запускаться, если опасное значение оставлено дефолтным, — это сделано специально, чтобы установка не поехала с ключом из примера.

ПеременнаяЧто этоЕсли не задать
SECRET_KEYмастер-секрет: из него выводятся подпись JWT и подписи ссылок на файлыстарт прерывается
CONNECTIONS_FERNET_KEYключ шифрования секретов подключений в базестарт прерывается
CORS_ORIGINSс каких origin браузеру разрешено звать API — строго JSON-массив«*» в проде запрещён; не-JSON рвёт старт
POSTGRES_PASSWORD + DATABASE_URLпароль базы; строка подключения должна совпадать с нимприложение не подключится
PUBLIC_BASE_URLвнешний адрес приложения — по нему регистрируются вебхукипубликация в Telegram невозможна
OPENROUTER_API_KEYключ провайдера моделей платформыкаталог виден, но любой вызов модели падает

Ещё два адреса задаются на сборке образа, а не в рантайме: PUBLIC_SITE_URL (куда приложение ведёт ссылками на документацию) и PUBLIC_APP_URL. Vite и Astro подставляют их в бандл в момент сборки, поэтому смена требует пересборки образа, а не перезапуска. Если PUBLIC_SITE_URL не задан — приложение просто не рисует ссылок на документацию, и это правильное поведение для установки, которая сайт не публикует.

TLS входной прокси не терминирует: в поставляемой конфигурации edge слушает обычный HTTP и маршрутизирует по домену (/api/…api, корень основного домена → сайт, домен приложения → SPA). Сертификаты — на внешнем балансировщике или расширением этой конфигурации; это задача администратора инстанса.

Первый администратор

Глобальная роль admin — это то, что открывает пункт меню Админка (админку) и снимает лимиты с аккаунта. Первый такой пользователь провижнится через BOOTSTRAP_ADMIN_EMAILS, и здесь важен порядок:

  1. Поднять стек и зарегистрироваться обычным способом через интерфейс.
  2. Вписать этот email в BOOTSTRAP_ADMIN_EMAILS в .env (JSON-массив).
  3. Перезапустить api: список применяется на старте и повышает роль уже существующих пользователей. Несуществующий email не создаёт аккаунт — он просто ни на что не влияет.

Тот же результат даёт скрипт scripts/grant_admin.py, запущенный в контейнере api. Роль читается из базы на каждый запрос, поэтому повышение и понижение действуют сразу, без перевхода. Разбор ролей — в «Доступе команды».

Каталоги моделей — файлы, а не код

Список моделей, которые видят ноды, приезжает не из кода, а из YAML-каталогов в backend/config/: llm/, embeddings/, rerank/, media/. Один файл — один провайдер: его имя, класс-адаптер, базовый адрес, ключ (через подстановку переменной окружения) и список моделей с ценами в кредитах. Добавить модель — правка файла и перезапуск, а не изменение кода. Как устроены поля — в «Каталоге моделей».

Отсюда и главная возможность своей установки: указать в params.base_url собственный OpenAI-совместимый эндпоинт (LM Studio, vLLM, TEI) и получить модели, которые никуда наружу не ходят. Такой каталог уже лежит в комплекте как пример — config/llm/local.yaml с переменными LOCAL_BASE_URL / LOCAL_API_KEY.

Тарифы, лимиты и деньги на своей установке

Планы живут строками в базе, а не константами: администратор правит числа во вкладке Тарифы админки и точечно переопределяет лимиты конкретному пользователю. Отсутствующий в плане ключ означает «поведение по умолчанию» — значение из конфигурации приложения. Что именно ограничивается, перечислено в «Лимитах тарифа», а кредиты и их списание — в «Кредитах».

Учёт кредитов работает и на своей установке: каждый запуск и каждый вызов модели пишет строку в журнал использования. Это полезно даже когда деньги никто не собирает — видно, во что обходится конкретный воркфлоу. Полностью выключить тарифные проверки можно переключателем PLAN_ENFORCEMENT_ENABLED=false; тогда квоты, потолки параллельности и гейты по кредитам перестают применяться ко всем.

Самостоятельная покупка тарифа пользователем в поставке выключена (BILLING_SELF_SERVICE_ENABLED=false), и это не оплошность: провайдер по умолчанию — заглушка, чей эндпоинт подтверждения выдаёт кредиты и платный план без реального платежа. Проверка старта отказывается поднимать production с включённой самообслуживаемой оплатой на заглушке. На своей установке роли раздаёт администратор, а не касса.

Чем своя установка отличается от облачной

ВопросСвоя установка
Чьи ключи провайдеровваши, в .env инстанса; можно вообще не выпускать наружу
Какие модели доступныкакие перечислены в ваших YAML — включая локальные
Кто задаёт лимитыадминистратор инстанса в админке; проверки можно и выключить
Где лежат данныеваши тома: postgres, qdrant, файлы приложения
Ноды с кодомвключаются явно и только вместе с контейнером-песочницей
Обновления и бэкапываша ответственность — в поставке демона бэкапов нет

Что платформа не делает за администратора: не выпускает сертификаты, не снимает бэкапы, не поднимает мониторинг (сбор трасс и метрик через OTLP есть, но коллектор в этот compose не входит — его адрес задаётся отдельно) и не обновляется сама. Ретеншен логов запусков при этом автоматический: чистку делает сам воркер по сроку из тарифа владельца, отдельного сервиса для этого нет.

Сколько это просит ресурсов

Суммарные лимиты памяти всех девяти контейнеров в поставляемом файле — 8320 МиБ (воспроизвести: сложить значения memory: в infra/prod/docker-compose.yml). Это лимиты, а не потребление, но планировать машину меньше 8 ГБ смысла нет; в плане мощностей (infra/CAPACITY_PLAN.md) для старта рекомендованы 8 vCPU / 16 ГБ на одну машину.

Дальше упирается в две вещи: число одновременных запусков (MAX_PARALLEL_RUNS на процесс воркера) и пул соединений к Postgres — они связаны, и поднимать первое без второго бессмысленно. Соответствующие переменные и правило их согласования описаны комментариями в backend/.env.example.