Развёртывание Simmetric Chat в Docker: полное руководство
Полная карта развёртывания Simmetric Chat - compose-стек из шести сервисов, единый all-in-one контейнер для air-gap-хостов и путь Coolify в один клик.
Один продукт, три формы развёртывания
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, а фронтенд ждёт, пока сервер объявит себя здоровым, прежде чем стартовать.
Поднять стек - одна команда:
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.
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-oneAll-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 в корне - обязательны в продакшене
JWT_SECRET=... # openssl rand -hex 32
COLLECTOR_SECRET=... # openssl rand -hex 32ENCRYPTION_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 путём.
От клона до работающей платформы
Самый короткий путь, от начала до конца:
- Клонируйте репозиторий и скопируйте
.env.exampleв.env. - Задайте
JWT_SECRETиCOLLECTOR_SECRET(ключи шифрования entrypoint провизионит сам). docker compose -f docker/docker-compose.yml up --build -d.- Следите за
docker compose logs -f serverдо завершения миграций и seeding, затем откройте фронтенд и войдите.
Для air-gap-хостов замените на all-in-one-образ и tarball-перенос. Для Coolify импортируйте репозиторий и заполните обязательные секреты - остальное развёртывание платформа сделает сама.

