Documenso на сервере: частые ошибки и решения
Documenso — открытая альтернатива DocuSign: договоры подписываются прямо в браузере, а весь стек можно держать на своём сервере, не отдавая документы третьей стороне. Звучит просто, пока не дойдёте до первого деплоя: приложение тянет за собой Postgres, SMTP, Chromium для рендеринга PDF и отдельный сертификат для самой подписи — и почти в каждом из этих узлов есть своя засада. Ниже — конкретные ошибки, с которыми сталкиваются при самостоятельном хостинге Documenso, и что с ними делать.
Содержание
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Что усложняет self-hosted Documenso по сравнению с облаком
В облачной версии Documenso.com вам не нужно думать о четырёх вещах, которые придётся закрыть самому:
- База данных — PostgreSQL с миграциями Prisma, которые должны отработать до первого запуска приложения.
- Почта — без рабочего SMTP получатель просто не увидит письмо с приглашением подписать документ.
- Сертификат подписи — файл
.p12, которым Documenso криптографически подписывает готовый PDF. Без него функция подписания не работает вовсе. - Рендеринг PDF — под капотом Puppeteer с headless Chromium, который расставляет поля и печати на странице. Это тяжёлый процесс, требовательный к памяти и к системным зависимостям контейнера.
Каждый из этих узлов по отдельности несложен, но именно связка "все сразу в одном docker-compose" и рождает большинство тикетов в issues проекта. Дальше — по порядку, от старта контейнеров до готового рабочего инстанса.
Docker Compose: рабочий стек с нуля
Официальный образ — documenso/documenso, база — Postgres 15+. Минимальный рабочий стек выглядит так:
services:
database:
image: postgres:15
restart: unless-stopped
environment:
POSTGRES_USER: documenso
POSTGRES_PASSWORD: change_me
POSTGRES_DB: documenso
volumes:
- db_data:/var/lib/postgresql/data
documenso:
image: documenso/documenso:latest
restart: unless-stopped
depends_on:
- database
ports:
- "3000:3000"
environment:
NEXTAUTH_URL: "https://sign.example.com"
NEXTAUTH_SECRET: "сгенерируйте_openssl_rand_-hex_32"
NEXT_PRIVATE_ENCRYPTION_KEY: "32_символа_хекс"
NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY: "ещё_32_символа_хекс"
NEXT_PUBLIC_WEBAPP_URL: "https://sign.example.com"
DATABASE_URL: "postgresql://documenso:change_me@database:5432/documenso"
DIRECT_DATABASE_URL: "postgresql://documenso:change_me@database:5432/documenso"
volumes:
- ./cert.p12:/opt/documenso/cert.p12
dns:
- 8.8.8.8
volumes:
db_data:
Значения NEXTAUTH_SECRET и оба ключа шифрования генерируются один раз и больше не меняются — если поменять их после того, как в базе уже есть пользователи и подписанные документы, старые зашифрованные данные (в том числе токены и часть полей документов) перестанут расшифровываться. Сохраните их сразу в менеджер паролей или в docker secrets — про это подробно расписано в статье про управление паролями в Docker.
Точный набор переменных окружения у Documenso меняется от релиза к релизу — перед деплоем сверьтесь с .env.example в актуальном репозитории проекта, не переносите конфиг годовой давности один в один.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверДомен, HTTPS и переменные NEXTAUTH — типичный редирект-луп
Самая частая жалоба новичков: "залогинился, страница мигает и кидает на /login по кругу". Причина почти всегда одна — NEXTAUTH_URL (и часто продублированный NEXT_PUBLIC_WEBAPP_URL) не совпадает с тем адресом, по которому реально открывается сайт. Documenso, как любое NextAuth-приложение, сверяет cookie сессии с этим значением, и малейшее расхождение — http вместо https, www вместо голого домена, другой порт — ломает сессию сразу после логина.
Проверочный чек-лист:
- в браузере адресная строка и значение
NEXTAUTH_URLсовпадают посимвольно, включая протокол; - за обратным прокси проброшены заголовки
X-Forwarded-ProtoиX-Forwarded-Host, иначе приложение думает, что работает по http, даже если снаружи https; - cookie не блокируются расширениями или политикой SameSite при открытии из iframe (если встраиваете Documenso в свой портал).
Для терминации TLS удобно взять Caddy — он сам выпускает и обновляет сертификат Let's Encrypt, а типичные грабли с ним разобраны в статье про Caddy с авто-SSL. Конфиг для Documenso простой:
sign.example.com {
reverse_proxy documenso:3000 {
header_up X-Forwarded-Proto {scheme}
}
}
Ключи шифрования и сертификат для подписи
Отдельная и не всегда очевидная сущность — сертификат для самой цифровой подписи PDF, файл cert.p12. Это не TLS-сертификат сайта, а криптографический ключ, которым Documenso подписывает готовый документ изнутри — примерно как печать нотариуса, приложенная к файлу.
Для теста подойдёт самоподписанный сертификат:
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 3650 -nodes \
-subj "/CN=Documenso Test Signing"
openssl pkcs12 -export -out cert.p12 -inkey key.pem -in cert.pem -passout pass:
Ошибка "Signing certificate not found" или "Failed to load signing certificate" почти всегда означает одно из трёх:
- Файл не смонтирован в контейнер по тому пути, который ждёт приложение — проверьте volume в docker-compose.
- Путь в переменной, отвечающей за расположение сертификата, указывает не туда (в разных версиях образа переменная называлась по-разному — сверяйтесь с текущей документацией конкретного релиза).
- У пароля
.p12-файла и значения, переданного контейнеру, разные значения — если экспортировали сертификат с паролем, он должен быть указан явно, пустой пароль и "нет переменной вовсе" — не одно и то же.
Для продакшена самоподписанный сертификат подписи — временная мера. Юридическая значимость подписи для внешних контрагентов обычно требует сертификата от доверенного удостоверяющего центра — это отдельный организационный вопрос, не технический, и его стоит решить до того, как через сервис пойдут реальные договоры.
PostgreSQL и миграции: ошибки при первом запуске
Documenso использует Prisma, и при первом старте контейнер должен применить миграции к пустой базе. Если это не срабатывает автоматически (зависит от версии образа и от того, как вы его собираете), увидите ошибки вида "table does not exist" при первом открытии дашборда.
Ручной прогон миграций из контейнера:
docker compose exec documenso npx prisma migrate deploy
Частые причины провала:
DATABASE_URLуказывает на хостlocalhostвместо имени сервисаdatabase— внутри docker-сети контейнеры видят друг друга по имени сервиса, а не по localhost;- Postgres ещё не успел подняться, когда приложение уже пытается подключиться —
depends_onв compose гарантирует только порядок запуска контейнера, а не готовность базы принимать соединения; добавьтеhealthcheckдля сервисаdatabaseи условиеcondition: service_healthy; - недостаточно прав у пользователя БД на создание таблиц и расширений — особенно если базу поднимали не с нуля, а переиспользовали существующий инстанс Postgres с урезанными правами.
Если база уже разрослась и миграции стали занимать заметное время или падают на таймаутах — пригодится общий разбор в статье PostgreSQL на сервере: частые ошибки и решения, там про блокировки, память и настройки подключений подробнее, чем уместится здесь.
SMTP и Puppeteer: письма не уходят, PDF не рендерится
Два разных по природе, но одинаково частых по частоте обращений блокера.
Почта. Без корректного SMTP получатель никогда не увидит приглашение подписать документ — сам файл лежит на вашем сервере, но ссылка до адресата не долетает. Проверяйте переменные NEXT_PRIVATE_SMTP_HOST, NEXT_PRIVATE_SMTP_PORT, NEXT_PRIVATE_SMTP_USERNAME, NEXT_PRIVATE_SMTP_PASSWORD и адрес отправителя. Частая ошибка — письмо технически уходит, но падает в спам: почтовые провайдеры жёстко проверяют домен отправителя, и без настроенных SPF, DKIM и DMARC для вашего домена доставляемость будет низкой вне зависимости от того, что делает сама Documenso. Как это настроить — в статье SPF, DKIM и DMARC на сервере.
Рендеринг PDF. Documenso использует headless Chromium через Puppeteer, чтобы расставлять поля подписи и печати на страницах документа. Это заметно более прожорливый по памяти процесс, чем обычный Next.js-бэкенд. Симптомы нехватки ресурсов:
- контейнер падает с кодом выхода 137 (OOM killer) при обработке большого PDF или при одновременной обработке нескольких документов;
- в логах — "Failed to launch the browser process" или ошибки нехватки
/dev/shm; Chromium в контейнере по умолчанию использует/dev/shmразмером 64 МБ, чего часто не хватает — либо увеличьте его черезshm_size: '1gb'в сервисе, либо запускайте Chromium с флагом--disable-dev-shm-usage.
Ориентировочно для стабильной работы стека Documenso с базой и рендерингом PDF закладывайте не менее 2 ГБ RAM на инстанс — под нагрузкой с несколькими одновременными подписаниями может понадобиться больше; точную цифру для вашего объёма документов лучше проверить нагрузочным тестом, а не брать на веру чужие цифры.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Documenso бесплатен для самостоятельного хостинга?
Да, ядро проекта открытое (лицензия AGPL), self-hosted версия бесплатна. Отдельные enterprise-функции облачной версии могут быть закрытыми — сверяйтесь с актуальным лицензионным разделом репозитория.
Можно ли хранить документы не на локальном диске, а в S3?
Да, Documenso поддерживает S3-совместимое хранилище, в том числе self-hosted MinIO — это удобно, если сервер с приложением и хранилище документов должны жить раздельно или если нужна репликация. Разворачивать MinIO проще всего по готовому файлу из статьи MinIO в Docker Compose.
После обновления образа документы или подписи потерялись — что делать?
Сначала проверьте, применились ли миграции Prisma новой версии (prisma migrate deploy), и не изменились ли имена переменных окружения между релизами — сами данные в Postgres и на диске/в S3 при обновлении не удаляются, но приложение может не найти их из-за смены конфигурации.
Обязательно ли использовать Puppeteer, нельзя ли обойтись без Chromium в контейнере?
На текущих версиях Documenso рендеринг полей в PDF завязан на headless-браузер, это часть архитектуры, а не опция. Если контейнер стабильно падает по памяти — проще увеличить лимиты RAM и /dev/shm, чем пытаться убрать этот компонент.
Нужен ли отдельный сервер именно под Documenso, или хватит общего с другими сервисами?
Для тестового инстанса хватит совмещённого сервера с остальными приложениями. Для продакшена с реальными договорами лучше выделить ресурсы отдельно — Chromium непредсказуемо потребляет память под нагрузкой и может задеть соседние контейнеры на общем хосте.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →