Langfuse на сервере: частые ошибки и решения
Self-hosted Langfuse — это минимум шесть контейнеров: web, worker, Postgres, ClickHouse, Redis и S3-совместимое хранилище, — и падение любого из них выглядит как невнятная ошибка на экране логина или пустой дашборд без единого трейса. Разбираем частые ошибки Langfuse на сервере по слоям: какой контейнер не поднялся, что написано в его логе и какой командой это проверить за секунды.
Содержание
- С чего начать разбор ошибок Langfuse: три команды на диагностику
- Web-контейнер не поднимается: NEXTAUTH, SALT, ENCRYPTION_KEY
- Postgres и ClickHouse: ошибки миграций и «relation does not exist»
- Воркер падает или зависает: Redis, память и OOM
- Nginx перед Langfuse: 413, 502 и петля редиректов на HTTPS
- Docker Compose и апдейты: переменные, которые «не применились»
- Какой сервер под Langfuse брать в MAATRIX
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →С чего начать разбор ошибок Langfuse: три команды на диагностику
Прежде чем лезть в конфиги, узнайте, какой из шести контейнеров сломан: langfuse-web, langfuse-worker, Postgres, ClickHouse, Redis и S3-хранилище (обычно MinIO).
docker compose ps
Смотрите на STATUS, а не на сам факт «контейнер существует»: Up 2 minutes (unhealthy) и Restarting (1) 15 seconds ago — два разных класса проблем. Restarting значит, что процесс падает прямо при старте, и причина — в первых строчках лога:
docker compose logs --tail=50 langfuse-web
Третья команда — health-эндпоинт приложения, он проверяет реальные зависимости, а не просто «процесс жив»:
curl -s http://127.0.0.1:3000/api/public/health | jq
{"status":"OK","version":"3.xx.x"} значит, что web достучался до Postgres. curl: (7) Failed to connect to 127.0.0.1 port 3000 — процесс не слушает порт вообще, и до проверки зависимостей дело не дошло.
По тому, что показали три команды, проблема попадает в один из слоёв:
| Симптом | Слой | Где искать |
|---|---|---|
langfuse-web в Restarting, лог обрывается на старте | Переменные окружения web | Раздел 2 |
| Web поднялся, но логин зависает на редиректе | NEXTAUTH / прокси | Разделы 2 и 5 |
Дашборд открывается, но ошибки про relation или Code: 81 | Postgres / ClickHouse | Раздел 3 |
langfuse-worker перезапускается каждые 10–30 секунд | Redis / память | Раздел 4 |
Всё зелёное в docker compose ps, но трейсов в интерфейсе нет | Приём событий на стороне SDK | см. отдельный разбор про пропавшие трейсы |
Последняя строка — не отговорка: если инфраструктура здорова, а не долетают именно данные, это отдельная диагностика SDK и очереди приёма, а не поломка сервера. Здесь — только то, что ломает сам стек.
Web-контейнер не поднимается: NEXTAUTH, SALT, ENCRYPTION_KEY
langfuse-web требует пять обязательных переменных: DATABASE_URL, NEXTAUTH_URL, NEXTAUTH_SECRET, SALT, ENCRYPTION_KEY. Не хватает любой — контейнер не стартует: секреты по умолчанию разработчики Langfuse сознательно не подставляют.
Самая частая находка в логе — от NextAuth, на котором построена аутентификация:
[next-auth][error][NO_SECRET]
https://next-auth.js.org/errors#no_secret
Please define a `secret` in production.
Лечится одной строкой: NEXTAUTH_SECRET=$(openssl rand -base64 32). Тот же принцип для SALT — отдельное значение для хеширования, не то же самое, что NEXTAUTH_SECRET: перепутанные местами переменные не уронят контейнер сразу, а аукнутся позже, при сверке уже сохранённых хешей.
ENCRYPTION_KEY жёстче: значение обязано быть ровно 256 бит — 64 символа в hex, не больше и не меньше. openssl rand -hex 32 (не -base64) даёт нужный формат; ключ из base64 короче и содержит символы, недопустимые для hex, и контейнер откажется стартовать. Эта переменная шифрует хранимые в Postgres ключи провайдеров — потерять её после того, как в базе накопились такие записи, значит потерять возможность их расшифровать.
Отдельный случай — не падение, а зависание на входе. langfuse-web поднялся, /api/public/health отвечает OK, а страница логина после ввода пароля перезагружает саму себя без видимой ошибки. Причина почти всегда в NEXTAUTH_URL: значение должно быть тем адресом, который видит браузер снаружи — https://langfuse.example.com, а не http://langfuse-web:3000 и не http://localhost:3000 за Nginx. Проверка — вкладка Network в браузере: если редирект уводит на localhost или на http вместо https, дело в этом.
Развернуть за пару минут
Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Развернуть LangfusePostgres и ClickHouse: ошибки миграций и «relation does not exist»
langfuse-web сам прогоняет миграции Prisma на Postgres при старте. Не хватает прав у пользователя БД — например, урезанная учётка на managed-Postgres вместо владельца схемы — и миграция обрывается, а запрос к интерфейсу отвечает ошибкой вида:
PrismaClientKnownRequestError:
Invalid `prisma.project.findMany()` invocation:
The table `public.Project` does not exist in the current database.
Prisma сохраняет регистр имён моделей и берёт их в кавычки, поэтому в логе таблицы называются "Project", "ApiKey" — с большой буквы, не project/api_key. relation "Project" does not exist значит, что миграции не докатились: чините права пользователя БД или прогоняйте миграцию вручную, а не ищите баг в самом Langfuse.
С ClickHouse — путаница из-за двух переменных на два протокола. CLICKHOUSE_URL — HTTP-интерфейс, порт 8123, для обычных запросов приложения. CLICKHOUSE_MIGRATION_URL — нативный протокол, порт 9000, через него при старте прогоняются миграции схемы. Рабочий ClickHouse на 8123 и connect ECONNREFUSED 127.0.0.1:9000 в логе миграций — не противоречие: порты проверяются независимо, и в конфиге легко указать только один из них.
Code: 81. DB::Exception: Database langfuse_prod does not exist
— ClickHouse поднялся, а базы с нужным именем никто не создал: CLICKHOUSE_DB должна совпадать с тем, что реально есть на сервере, сам Langfuse её не создаёт.
Третья категория — не ошибка старта, а деградация со временем: ClickHouse хранит трейсы в виде партов, периодически сливает их (merge), и на тесном диске это кончается отказом писать новые данные:
DB::Exception: Not enough space to add ... GiB. Envisioned disk usage
Self-hosted Langfuse не чистит старые трейсы сам: автоудаления данных старше N дней в open-source версии нет, только вручную — ALTER TABLE ... DELETE по TTL или скрипт из крона. На активном проекте диск ClickHouse стоит проверять docker exec clickhouse df -h /var/lib/clickhouse, а не ждать, пока он забьётся сам.
Воркер падает или зависает: Redis, память и OOM
langfuse-worker обрабатывает очередь на BullMQ поверх Redis: без рабочего подключения он не держится стабильно, а перезапускается каждые 10–30 секунд. Классика Docker Compose — адрес Redis должен быть именем сервиса, а не localhost:
ioredis: connect ECONNREFUSED 127.0.0.1:6379
значит, в REDIS_HOST (или в REDIS_CONNECTION_STRING) стоит localhost, а Redis — отдельный контейнер redis, доступный из сети compose только по имени сервиса. Если адрес правильный, а ошибка про авторизацию —
ReplyError: NOAUTH Authentication required.
— у Redis включён requirepass, а у worker либо не задан REDIS_AUTH, либо задан со значением, не совпадающим с конфигом Redis.
Второй класс — не «упал с понятной ошибкой», а «пропал молча». Шесть процессов на одной машине, и ClickHouse отдельно любит забирать память под кэш и буферы вставки при всплеске нагрузки. Контейнер в Exited (137) при тишине в собственном логе ровно там, где должна быть ошибка, — это код SIGKILL (128 + 9), и почти всегда за ним стоит OOM killer ядра, а не баг приложения:
dmesg -T | grep -i "killed process"
docker inspect langfuse-worker --format='{{.State.OOMKilled}}'
true во втором выводе закрывает вопрос: памяти физически не хватило. Перезапускать контейнер на той же RAM — не решение; решение — развести сервисы по разным машинам либо увеличить память сервера целиком (раздел 7 — сколько её закладывать).
Nginx перед Langfuse: 413, 502 и петля редиректов на HTTPS
Приём событий у Langfuse идёт батчами: SDK копит несколько observation'ов и одним POST скидывает их на /api/public/ingestion, а не шлёт каждый трейс отдельно. Мультимодальные данные — картинки, аудио в base64 — легко перерастают лимит Nginx в 1 МБ, и запрос обрывает сам прокси, не дожидаясь приложения:
413 Request Entity Too Large
в ответе клиенту и client intended to send too large body в /var/log/nginx/error.log. Правка — одна строка:
client_max_body_size 20m;
Второй частый гость — 502 Bad Gateway: апстрим не успел ответить вовремя, выборка большого диапазона трейсов из ClickHouse или тяжёлая batch-вставка легко превышает стандартные 60 секунд. Таймауты стоит поднять явно:
proxy_read_timeout 120s;
proxy_send_timeout 120s;
Третья категория — не ошибка, а бесконечный редирект на логин, если Nginx терминирует HTTPS, а до langfuse-web запрос доходит уже как http. NextAuth считает соединение небезопасным, не ставит secure-cookie, и браузер каждый раз просит войти заново. Без этих заголовков NEXTAUTH_URL не спасает:
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
Docker Compose и апдейты: переменные, которые «не применились»
Частый тупик после правки .env: значения меняете, docker compose restart langfuse-web выполняете — а поведение не меняется. restart перезапускает уже существующий контейнер с уже подставленным при создании окружением, .env заново он не перечитывает. Правильная команда —
docker compose up -d --force-recreate langfuse-web
пересоздаёт контейнер с текущими значениями; без имени сервиса docker compose up -d пересоздаст все контейнеры, чей конфиг изменился.
Отдельная ловушка — переменные LANGFUSE_INIT_ORG_ID, LANGFUSE_INIT_USER_EMAIL, LANGFUSE_INIT_USER_PASSWORD: создают организацию, проект и администратора автоматически при пустой базе Postgres, но срабатывают ровно один раз. Поменяли LANGFUSE_INIT_USER_PASSWORD и перезапустили контейнер на базе, где организация уже создана, — ничего не произойдёт: entrypoint видит существующие записи и молча пропускает инициализацию, хотя кажется, что пароль должен был смениться.
Рядом — честный, не гипотетический риск: по умолчанию self-hosted Langfuse пускает регистрацию через /auth/sign-up кого угодно с сетевым доступом до домена. Создали первого администратора — сразу выставляйте AUTH_DISABLE_SIGNUP=true: открытая форма на боевом инстансе с чужими трейсами (а там может быть что угодно, вплоть до персональных данных) — не баг, а дыра, которую оставили сами.
Последнее — обновление образа. langfuse/langfuse:latest без фиксации версии однажды перепрыгнет архитектурную границу: переход с 2.x на 3.x сделал ClickHouse и S3-хранилище обязательными вместо опциональных, и контейнер без них на latest откажется стартовать вместо тихой работы по-старому. Фиксируйте версию тегом в docker-compose.yml, поднимайте осознанно — читая changelog, а не при каждом docker compose pull.
Какой сервер под Langfuse брать в MAATRIX
Часть ошибок из этого текста — следствие того, что шесть сервисов ужимаются на тесной по ресурсам машине: ClickHouse режет кэш, диск забивается партами без встроенной чистки, воркер получает OOM первым — стартует последним и претендует на память, которой уже не осталось.
Честный минимум: 4 vCPU, 8 ГБ RAM, 80 ГБ NVMe. Хватает поднять весь стек — web, worker, Postgres, ClickHouse, Redis, MinIO — и пользоваться Langfuse одному или небольшой командой с умеренным потоком трейсов. Ниже 8 ГБ совокупно на шесть контейнеров велик риск того же OOM, что разбирали в разделе 4: ClickHouse при мердже партов и Postgres при батче миграций одновременно просят память, а делить им особо нечего. 80 ГБ NVMe — запас на несколько месяцев логирования, прежде чем встанет вопрос ретеншена вручную.
Комфортный вариант: 8 vCPU, 16–32 ГБ RAM, 150–200 ГБ NVMe. Точная цифра зависит от объёма LLM-трафика, который вы логируете: чем больше трейсов в день, тем больше ClickHouse хочет держать в кэше ради быстрых выборок в дашборде. Такая конфигурация переживает всплески без перезапусков воркера и без еженедельной слежки за диском.
В каталоге приложений MAATRIX Langfuse разворачивается автоматически при заказе сервера — весь стек из шести контейнеров поднимается сразу настроенным, без ручного docker compose up, а адрес панели и ключи доступа появляются в личном кабинете, в разделе «Доступ». Работает на Ubuntu и на Debian; дальше остаётся привязать домен, включить HTTPS и, если разворачиваете с нуля, сразу выставить AUTH_DISABLE_SIGNUP=true — привычка из раздела 6, которая экономит нервы позже.
Локация — Великобритания (Лондон). В трейсах Langfuse оседают промпты и ответы пользователей — по сути, копия переписки с вашим ИИ-продуктом вместе с любыми персональными данными, которые в неё попали. Команде на европейскую аудиторию держать эту копию в юрисдикции по соседству с GDPR — осознанный выбор, а не формальность. Плюс короткий пинг от серверов приложения в ЕС: batch-вставки идут чаще, чем кажется, и разница в RTT заметна на отзывчивости дашборда.
Оплата — картой российского банка, СБП, криптовалютой или токеном MAAT; иностранная карта не нужна, хотя сервер физически стоит в Лондоне.
Развернуть за пару минут
Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Развернуть LangfuseОбсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Частые вопросы
docker compose ps показывает langfuse-web в вечном Restarting — с чего начать?
Смотрите первые строки docker compose logs langfuse-web: контейнер падает раньше, чем успевает вывести что-то, кроме стектрейса нехватающей переменной. Проверьте DATABASE_URL, NEXTAUTH_SECRET, SALT, ENCRYPTION_KEY, NEXTAUTH_URL.
После входа страница логина сама перезагружается, ошибки не видно — что не так?
Чаще всего NEXTAUTH_URL не совпадает с реальным внешним адресом, либо Nginx не передаёт X-Forwarded-Proto, и secure-cookie не выставляется. Откройте Network в браузере: редирект на localhost или на http вместо https подтверждает диагноз.
Забыли пароль администратора, а «забыли пароль» ничего не присылает на почту — как быть?
У self-hosted Langfuse нет почты из коробки: письмо со сбросом уходит только если заданы переменные SMTP-подключения и EMAIL_FROM_ADDRESS. Без них форма молча ничего не отправляет. Быстрее всего — обновить пароль напрямую в Postgres, либо, если данные ещё не жалко, поднять базу заново и завести администратора через LANGFUSE_INIT_USER_EMAIL/LANGFUSE_INIT_USER_PASSWORD на пустой БД.
Нужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.