GrowthBook на сервере: частые ошибки и решения
GrowthBook — open-source платформа feature flags и A/B-тестирования: включаете функции по сегментам, гоняете эксперименты, считаете статистическую значимость на своих данных, а не в чужом облаке. Self-hosted развёртывание освобождает от лимитов SaaS-тарифов, но добавляет свой набор проблем — от отказа подключения к MongoDB до флагов, которые SDK почему-то не видит. Ниже — разбор самых частых ошибок при установке и эксплуатации GrowthBook на сервере и рабочие способы их починить.
Содержание
- Архитектура GrowthBook и откуда берутся типовые проблемы
- MongoDB: отказ в подключении и ошибки авторизации
- APP_ORIGIN и API_HOST: CORS и SDK, который не видит флаги
- Reverse-прокси, SSL и обрывы realtime-обновлений
- ENCRYPTION_KEY и JWT_SECRET: флаги «пропадают» после переноса
- Подключение источника данных: ошибки доступа к Postgres, ClickHouse, BigQuery
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Архитектура GrowthBook и откуда берутся типовые проблемы
GrowthBook на сервере — это, как правило, три части: сам сервис (образ growthbook/growthbook, объединяющий API и веб-интерфейс на порту 3000), MongoDB как хранилище конфигурации (флаги, эксперименты, пользователи, API-ключи) и опционально внешний источник данных (Postgres, ClickHouse, BigQuery, Mixpanel и т.д.), куда GrowthBook ходит за сырыми событиями для подсчёта результатов экспериментов. Большинство проблем возникает на стыках этих частей, а не в самом приложении.
Минимальный рабочий docker-compose.yml:
services:
mongo:
image: mongo:7
restart: unless-stopped
volumes:
- mongo_data:/data/db
environment:
MONGO_INITDB_ROOT_USERNAME: gbadmin
MONGO_INITDB_ROOT_PASSWORD: смените_меня
growthbook:
image: growthbook/growthbook:latest
restart: unless-stopped
depends_on:
- mongo
ports:
- "3000:3000"
- "3100:3100"
environment:
MONGODB_URI: "mongodb://gbadmin:смените_меня@mongo:27017/growthbook?authSource=admin"
APP_ORIGIN: "https://gb.example.com"
API_HOST: "https://gb.example.com/api"
JWT_SECRET: "длинная_случайная_строка"
ENCRYPTION_KEY: "ещё_одна_длинная_случайная_строка"
volumes:
- gb_uploads:/usr/local/src/app/packages/back-end/uploads
volumes:
mongo_data:
gb_uploads:
Порт 3100 — внутренний SSE-сервис (Server-Sent Events), через который GrowthBook рассылает realtime-обновления флагов, если включён streaming. Про него часто забывают при настройке прокси — и получают флаги, которые обновляются только после перезагрузки страницы.
MongoDB: отказ в подключении и ошибки авторизации
Самая частая причина, по которой контейнер GrowthBook падает на старте или крутится в рестарт-луп — недоступная или неправильно указанная MongoDB. В логах это выглядит как:
MongoServerError: bad auth : Authentication failed.
или
MongooseServerSelectionError: connect ECONNREFUSED 127.0.0.1:27017
ECONNREFUSED 127.0.0.1 внутри Docker почти всегда означает, что в MONGODB_URI указан localhost вместо имени сервиса из docker-compose.yml (в примере выше — mongo). Внутри контейнерной сети localhost указывает на сам контейнер GrowthBook, где никакой MongoDB нет.
bad auth — это либо неверный пароль, либо неверный authSource. Если пользователь создан в базе admin (как в примере с MONGO_INITDB_ROOT_USERNAME), а в строке подключения authSource не указан или указан неверно, аутентификация не пройдёт даже с правильным паролем:
# неверно — authSource по умолчанию = имя базы после /
mongodb://gbadmin:pass@mongo:27017/growthbook
# верно, если пользователь создан в admin
mongodb://gbadmin:pass@mongo:27017/growthbook?authSource=admin
Проверить подключение изолированно от GrowthBook, зайдя в контейнер mongo:
docker exec -it $(docker ps -qf name=mongo) mongosh \
"mongodb://gbadmin:pass@localhost:27017/growthbook?authSource=admin"
Если это отработало, а GrowthBook всё равно не подключается — проверьте, что специальные символы в пароле URL-энкодены (@, #, % ломают строку без кодирования через %40, %23 и т.д.), и что версия MongoDB не совсем старая — GrowthBook рассчитан на актуальные релизы и может отказаться стартовать на устаревшем образе.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверAPP_ORIGIN и API_HOST: CORS и SDK, который не видит флаги
Вторая по частоте категория ошибок — неправильно выставленные APP_ORIGIN и API_HOST. Это не декоративные переменные: GrowthBook использует их и для формирования ссылок в интерфейсе, и для проверки CORS-заголовков на API-запросах от SDK. Симптом в браузере при обращении фронтенд-SDK к серверу:
Access to fetch at 'https://gb.example.com/api/features/sdk-xxxxx'
from origin 'https://app.example.com' has been blocked by CORS policy
Частая ошибка — оставить APP_ORIGIN со значением по умолчанию (http://localhost:3000) в проде после переноса на реальный домен. GrowthBook сверяет заголовок Origin входящих запросов именно с этой переменной, и если она не совпадает с реальным адресом фронтенда, который дёргает SDK-эндпоинт, запрос блокируется.
Второй вариант той же беды — SDK получает пустой ответ или 404 не из-за CORS, а из-за неверного API_HOST в самом SDK-коде. Значение из настроек GrowthBook (API_HOST на сервере) и apiHost, который вы передаёте в конструктор SDK на клиенте, — это разные вещи, но должны указывать на один и тот же реально достижимый адрес:
const gb = new GrowthBook({
apiHost: "https://gb.example.com/api",
clientKey: "sdk-xxxxxxxxxxxx",
});
Если сервер стоит за reverse-прокси и API проксируется по отдельному пути (например /api на том же домене, что и UI), убедитесь, что прокси действительно пробрасывает этот путь к GrowthBook на порт 3000, а не 404-ит его как несуществующий маршрут фронтенда.
Ещё одна деталь: clientKey бывает двух типов — публичный (client-side, безопасно светить в браузере) и серверный (server-side, содержит больше данных). Ошибка 403 Forbidden или пустой список фич при использовании клиентского ключа обычно означает, что вы перепутали ключи местами.
Reverse-прокси, SSL и обрывы realtime-обновлений
GrowthBook умеет пушить обновления флагов клиентам без опроса через Server-Sent Events — это удобно, но SSE-соединения болезненно реагируют на буферизацию и таймауты прокси. Симптом: флаги на сайте обновляются с задержкой в минуты или только после перезагрузки страницы, хотя в интерфейсе GrowthBook изменение сохранено мгновенно.
Для nginx перед GrowthBook нужен отдельный location под SSE-эндпоинт с отключённой буферизацией и увеличенным таймаутом:
location /sub/ {
proxy_pass http://127.0.0.1:3100;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 3600s;
}
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
Без proxy_buffering off nginx копит данные перед отправкой клиенту, и SSE-поток приходит пачками вместо потока событий. Дефолтный proxy_read_timeout (обычно 60 секунд) рвёт долгоживущее SSE-соединение — клиент переподключается с задержкой, и в консоли браузера видны повторяющиеся net::ERR_INCOMPLETE_CHUNKED_ENCODING.
С SSL проблем обычно меньше: если сертификат выпущен через certbot или встроенный ACME в Traefik и домен в APP_ORIGIN совпадает с сертификатом — всё работает штатно. Базовую настройку разбирали в статье про Let's Encrypt на VPS; если Traefik уже стоит перед другими сервисами на том же сервере, приёмы из статьи про частые ошибки Traefik применимы и к GrowthBook — принцип с лейблами и middleware тот же.
ENCRYPTION_KEY и JWT_SECRET: флаги «пропадают» после переноса
Одна из самых неприятных ошибок — та, что не проявляется сразу. GrowthBook шифрует часть чувствительных данных (payload фич, отдаваемый SDK) значением ENCRYPTION_KEY. Если при переносе на новый сервер или пересборке docker-compose.yml эта переменная не сохранилась и сгенерировалась заново — старые зашифрованные данные в MongoDB остаются, а расшифровать их нечем. Итог: интерфейс открывается, но SDK получает пустой или битый payload, а в логах — что-то вроде:
Error: Unable to decrypt payload — invalid key or corrupted data
Правило простое: ENCRYPTION_KEY и JWT_SECRET генерируются один раз при первом запуске и хранятся отдельно от docker-compose файла — в секрет-менеджере или зашифрованном .env вне репозитория. Восстанавливая GrowthBook из бэкапа MongoDB на новом сервере, обязательно переносите и эти две переменные вместе с дампом — иначе бэкап базы сам по себе бесполезен.
JWT_SECRET ломает другую вещь — сессии пользователей. Смена переменной разлогинивает всех: ошибка выглядит как JsonWebTokenError: invalid signature и бесконечный редирект на страницу логина. Ротируете секрет намеренно — предупредите команду, что придётся перелогиниться, это ожидаемо.
Дамп и восстановление MongoDB для GrowthBook — обычные mongodump/mongorestore:
docker exec mongo mongodump --uri="mongodb://gbadmin:pass@localhost:27017/growthbook?authSource=admin" \
--archive=/data/db/gb-backup.gz --gzip
docker exec mongo mongorestore --uri="mongodb://gbadmin:pass@localhost:27017/growthbook?authSource=admin" \
--archive=/data/db/gb-backup.gz --gzip --drop
Общие принципы бэкапа для self-hosted сервисов в Docker разобраны в статье про резервное копирование и восстановление BorgBackup — тот же подход с шифрованными снапшотами тома mongo_data подходит и для GrowthBook, если вы бэкапите весь volume целиком, а не только логический дамп.
Подключение источника данных: ошибки доступа к Postgres, ClickHouse, BigQuery
Сам GrowthBook хранит только конфигурацию флагов и экспериментов, а сырые события (какой пользователь в какой группе, какие метрики сработали) обычно лежат в аналитическом хранилище — Postgres, ClickHouse, BigQuery или событийной системе вроде Mixpanel. При подключении такого источника чаще всего встречаются три проблемы.
Сетевая недоступность. Если аналитическая база — отдельный сервер или управляемый сервис в другой сети, GrowthBook должен физически до неё дотянуться. Ошибка в интерфейсе обычно generic — Failed to connect to data source; реальную причину смотрите в логах контейнера:
docker logs -f growthbook 2>&1 | grep -i "datasource\|connect"
Частый случай — Postgres или ClickHouse слушает только 127.0.0.1 на хосте, а GrowthBook стучится снаружи контейнера; нужно либо слушать на адресе docker-моста, либо пробросить общую сеть в docker-compose, либо открыть порт в firewall между серверами.
Недостаточные права read-only пользователя. GrowthBook рекомендует создавать под аналитику отдельного пользователя с правами только на чтение — это правильно с точки зрения безопасности, но именно урезанные права чаще всего и ломают первый тестовый запрос. Тест соединения проходит (логин успешен), а первый реальный запрос метрики падает с ошибкой вида permission denied for schema analytics, если не выдан SELECT на нужную схему.
SSL-режим не совпадает. Управляемые облачные Postgres/ClickHouse обычно требуют sslmode=require или verify-full, а строка подключения по умолчанию собирается без SSL. Если провайдер форсирует TLS, а в настройках источника SSL не включён — соединение либо рвётся сразу, либо молча зависает до таймаута. Проверяйте параметр SSL явно, а не полагайтесь на автоопределение.
Если аналитическое хранилище тоже на вашем сервере, статья MongoDB или PostgreSQL: что выбрать для сервера поможет определиться, на чём считать эксперименты.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Сколько RAM нужно для GrowthBook под нагрузкой?
Сам сервис лёгкий и на небольших командах работает в 512 МБ–1 ГБ. Основной аппетит к памяти — у MongoDB и особенно у аналитического хранилища (ClickHouse под большой объём событий требует заметно больше), закладывайте ресурсы отдельно под каждый компонент.
Можно ли использовать GrowthBook только для feature flags, без анализа экспериментов?
Да, для управления флагами без статистического анализа A/B-тестов внешнее аналитическое хранилище не обязательно — MongoDB для конфигурации достаточно.
Флаги не обновляются, хотя в интерфейсе изменение сохранено — с чего начать?
Проверьте, правильно ли проксируется порт 3100 для SSE (см. раздел про reverse-прокси); если streaming не используется, SDK кэширует ответ на заданный TTL — уменьшите его или дождитесь истечения кэша.
После обновления образа сервис не стартует — что проверить в первую очередь?
Логи на предмет ошибок миграции MongoDB (docker logs growthbook) — мажорные обновления иногда прогоняют миграцию схемы при старте, дайте ей завершиться и тестируйте апдейт сначала на копии базы.
Нужен ли отдельный SMTP для приглашений пользователей?
Да, без SMTP_HOST/SMTP_PORT/SMTP_USER/SMTP_PASS/SMTP_FROM в переменных окружения приглашение просто не отправится, а ошибка в интерфейсе может быть не явной — смотрите логи контейнера.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →