Блог
Руководства

Развёртывание Simmetric Chat в Docker: полное руководство

Полная карта развёртывания Simmetric Chat - compose-стек из шести сервисов, единый all-in-one контейнер для air-gap-хостов и путь Coolify в один клик.

Simmetric Team7 мин чтения10/03/2026

Один продукт, три формы развёртывания

Simmetric Chat - многосервисная платформа: API-сервер, collector для обработки документов, встраиваемый widget-сервис, фронтенд и база PostgreSQL. Поэтому история развёртывания - это выбор правильной упаковки под вашу среду, а не ручная проводка сервисов. Есть три продакшен-пути, и все артефакты лежат в каталоге docker/ репозитория:

  • Многоконтейнерный Compose - шесть сервисов, каждый в своём контейнере с healthcheck. Рекомендуемая продакшен-схема с изоляцией по сервисам и независимыми перезапусками.
  • Единый all-in-one контейнер - один образ, выполняющий всё: Postgres, сервер, collector и widget под управлением supervisord. Для air-gap-хостов и демо-машин, где дорого - это протаскивать через периметр несколько образов.
  • Coolify - путь self-hosted PaaS с TLS-терминацией Traefik, где платформа клонируется и собирается за вас из compose-файла, подогнанного под конвенции Coolify.

Все образы - multi-stage сборки на базах Node 24, и каждый сервис открывает healthcheck, от которого зависит порядок запуска стека. В этой статье разбирается каждый путь, модель конфигурации и детали, которые кусаются в продакшене.

Compose-стек из шести сервисов

Продакшен-compose определяет шесть активных сервисов:

  • frontend - собирается как приложение React 19 из Node-билдера, затем отдаётся nginx, который терминирует TLS самоподписанными сертификатами, сгенерированными на этапе сборки образа, отдаёт SPA и проксирует /api и /widget к бэкенд-сервисам.
  • server - Express 5 API: аутентификация, RBAC, оркестрация чатов и ReAct-агент. Сервис, с которым говорят ваши пользователи.
  • collector - конвейер документов: разбор, нарезка и эмбеддинг загрузок. Собран на slim-базе Node, а не Alpine, потому что его нативные зависимости (LanceDB, локальные embedding-трансформеры, canvas-биндинги) требуют glibc.
  • widget - сервис встраиваемого чат-виджета для внешних сайтов.
  • postgres - PostgreSQL 16 с расширением pgvector в комплекте, чтобы провайдеру векторов по умолчанию не требовался отдельный сервис векторной БД.
  • redis - опциональный кэш для масштабирования, включаемый с append-only персистентностью и LRU-вытеснением. Каждый потребитель Redis изящно деградирует в память, поэтому этот сервис может присутствовать или отсутствовать без проблем.

Два сервиса в стандартном файле намеренно отключены, а не отсутствуют: ollama и qdrant. Их блоки идут закомментированными, и включить их - значит раскомментировать строку плюс переменную: OLLAMA_BASE_URL=http://ollama:11434 для хоста локальных моделей или VECTOR_DB_PROVIDER=qdrant, если вы хотите удалённое векторное хранилище вместо файлового LanceDB по умолчанию. Закомментированные значения по умолчанию держат минимальную установку действительно минимальной.

Порядок запуска определяется healthcheck'ами: пробы на основе wget бьют в GET /api/health на сервере и collector, /health на widget, pg_isready на Postgres и redis-cli ping на Redis, а фронтенд ждёт, пока сервер объявит себя здоровым, прежде чем стартовать.

Поднять стек - одна команда:

bash
docker compose -f docker/docker-compose.yml up --build -d

Единый all-in-one контейнер

Второй путь сжимает всю платформу в один образ. Единственный Dockerfile компилирует PostgreSQL 16 с расширением pgvector, собранным из исходников на этапе сборки образа, а supervisord запускает вместе, внутри контейнера, базу данных, сервер, collector и widget. Порты 3000, 3210 и 3211 открыты, а healthcheck опрашивает /api/health сервера на localhost.

bash
docker build -f docker/Dockerfile -t simmetric-chat-all-in-one .
docker run -d -p 3000:3000 -p 3210:3210 -p 3211:3211 simmetric-chat-all-in-one

All-in-one-образ - это история air-gap: единственный артефакт для переноса через периметр, никакого pull из registry в момент установки, и всё - от базы данных до чат-UI - поднимается одной командой docker run. Исходники pgvector компилируются и устанавливаются офлайн во время сборки именно для того, чтобы у итогового образа не было сетевых зависимостей при установке. Его средовые значения по умолчанию - строительные заглушки по замыслу: оператор переопределяет настоящие секреты через .env или -e, и образ никогда не делает вид, что он production-safe прямо из коробки.

Одну границу стоит знать: enterprise-плагин никогда не запекается ни в один образ. Это осознанно - так AGPL-образы сообщества остаются свободными от проприетарного кода, а air-gap-клиенты получают плагин отдельным проверяемым tarball'ом, монтируемым на этапе выполнения.

Путь Coolify

Coolify превращает стек в PaaS-развёртывание: клонирует репозиторий, собирает образы, соединяет внутреннюю сеть и ставит Traefik спереди для TLS-терминации. Отдельный compose-файл автоматизирует то, что стандартный Compose оставляет оператору: сборку образов, внутреннюю сеть, тома, healthcheck'и и монтирование enterprise-плагина.

Он же требует обязательные секреты на момент деплоя через синтаксис :?: JWT_SECRET, COLLECTOR_SECRET, WIDGET_API_KEY, POSTGRES_PASSWORD, APP_URL и ALLOWED_ORIGINS. Coolify отказывается деплоить, если чего-то из них не хватает, - и это превращает целый класс наполовину настроенных развёртываний в видимую ошибку до того, как что-либо запустится.

Различия со стандартным стеком следуют из присутствия Traefik: прикладные сервисы не публикуют порты на хосте (назначьте домен сервису frontend, порт 80), конфигурация nginx только HTTP, потому что TLS терминирует Traefik, а Docker-сокет не смонтирован по умолчанию - он нужен только для потока входа Ollama Cloud. Пошаговый runbook покрывает ручные шаги: rsync enterprise-дерева, заполнение секретов и установку лицензионного ключа.

Конфигурация: один .env, смонтированный Compose

Контейнеры получают конфигурацию через env_file в Compose, указывающий на .env в корне репозитория - тот самый единый runtime-файл конфигурации, который платформа использует повсюду. Если файла нет, стек запускается на одних compose-значениях по умолчанию: достаточно для первого smoke-теста, недостаточно для продакшена.

Два секрета строго обязательны, и сервер завершается без них:

env
# .env в корне - обязательны в продакшене
JWT_SECRET=...          # openssl rand -hex 32
COLLECTOR_SECRET=...    # openssl rand -hex 32

ENCRYPTION_KEY и API_KEY_HMAC_SECRET вообще не нужно готовить заранее: при продакшен-запуске entrypoint сервера генерирует их один раз, сохраняет в томе server-storage, восстанавливает при каждом перезапуске и предпочитает переданное оператором значение, когда оно есть. Provisioning никогда не перегенерирует поверх существующего ключевого материала и громко падает до любого шага с базой, так что наполовину записанный том не может молча стать другим ключом.

Есть специфическая ловушка Compose, которую стоит назвать: не добавляйте в продакшен-сервисы passthrough-записи вида LICENSE_KEY=${LICENSE_KEY:-}. Интерполяция шелла превращает не заданную переменную в пустую строку, а явно пустая запись environment перекрывает значение, приходящее через env_file, - так пустой интерполированный LICENSE_KEY превращает Enterprise-развёртывание в Community, а пустой ENCRYPTION_KEY перекрывает авто-provisioning entrypoint. Оставьте эти ключи только env_file.

Детали, которые важны в продакшене

  • Collector требует glibc. Collector работает на slim-базе (Debian), а не Alpine, потому что LanceDB, локальные embedding-трансформеры и canvas требуют glibc. Не «упрощайте» до Alpine - сборка падает.
  • У URL базы данных три формы. Внутри compose-сети сервер общается с postgres:5432 (захардкожено в environment compose); значение по умолчанию в корневом .env указывает на host.docker.internal для серверов на стороне хоста; all-in-one-образ использует localhost, потому что Postgres работает в том же контейнере.
  • Миграции только аддитивны. Entrypoint сервера выполняет prisma migrate deploy при каждом запуске, поэтому прокрутка образов вперёд безопасна по политике; откат означает редеплой образа, известного как хороший, и откат кода не отменяет применённые миграции.
  • Масштабирование ждёт provisioning. Автоматическое provisioning секретов должно один раз завершиться, прежде чем сервер отмасштабируется дальше одной реплики: конкурирующие реплики при первом запуске могут сгенерировать разные ключи. Одна инстанция - документированное значение по умолчанию; у многоинстансного масштабирования есть отдельный гид.
  • Эндпоинты здоровья внешние. /api/health возвращает объект состояния с проверками по зависимостям и подходит для внешних uptime-проб и проверок балансировщиков; структурированные логи идут в stdout для docker compose logs -f <сервис>.

Опубликованные образы выкладываются в GHCR (server, frontend, collector, widget и all-in-one, с тегами latest и версией), так что развёртывание может зафиксировать версию вместо локальной сборки - хотя docker compose up --build из репозитория остаётся полностью self-hosted путём.

От клона до работающей платформы

Самый короткий путь, от начала до конца:

  1. Клонируйте репозиторий и скопируйте .env.example в .env.
  2. Задайте JWT_SECRET и COLLECTOR_SECRET (ключи шифрования entrypoint провизионит сам).
  3. docker compose -f docker/docker-compose.yml up --build -d.
  4. Следите за docker compose logs -f server до завершения миграций и seeding, затем откройте фронтенд и войдите.

Для air-gap-хостов замените на all-in-one-образ и tarball-перенос. Для Coolify импортируйте репозиторий и заполните обязательные секреты - остальное развёртывание платформа сделает сама.

Читайте полное руководство в документации →

Похожие статьи