Payload CMS на сервере: частые ошибки и решения
Payload CMS хвалят за то, что схема контента описывается прямо в TypeScript-коде, а не собирается мышкой в админке — но именно эта «код как источник правды» модель добавляет проблем при переносе с локальной машины на боевой сервер. Локально всё работает, потому что Node видит переменные окружения из .env, диск не ограничен, а Postgres крутится рядом на localhost. На VPS тот же билд падает по памяти, админка открывается белым экраном, а загруженные картинки пропадают после рестарта контейнера. Ниже — конкретные причины и то, как это чинить, без пересказа официальной документации.
Содержание
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Минимальные требования и первый запуск
Payload 3.x построен поверх Next.js (App Router), поэтому требования к серверу — это требования Next.js плюс сама CMS с её админкой и панелью GraphQL/REST. На практике:
- Node.js 18.20+ или 20 LTS — более старые версии Node ловят непонятные ошибки на
fetchиcryptoAPI, которые Payload использует нативно; - база данных — PostgreSQL 14+ (адаптер
@payloadcms/db-postgres) или MongoDB 5+ (@payloadcms/db-mongodb); смешивать нельзя, адаптер выбирается один раз при инициализации проекта; - от 1 vCPU / 2 ГБ RAM для небольшого сайта с редким трафиком в админку; для продакшена с несколькими редакторами и активной генерацией превью изображений комфортнее 2 vCPU / 4 ГБ — сборка Next.js и обработка медиа через sharp прожорливы по памяти именно в моменты пиковой нагрузки, а не постоянно.
Первый запуск на сервере обычно выглядит так:
git clone git@github.com:your-org/your-payload-app.git
cd your-payload-app
npm ci
cp .env.example .env
# отредактировать .env: DATABASE_URI, PAYLOAD_SECRET, PAYLOAD_PUBLIC_SERVER_URL
npm run build
npm run start
Если на этом этапе процесс молча падает или зависает — почти всегда виноваты память при билде (раздел ниже) или незаполненные переменные окружения, которые Payload не всегда явно проверяет при старте, а просто падает в рантайме на первом обращении к нужному значению.
База данных: ошибки подключения к Postgres или MongoDB
Самая частая ошибка новичков — ECONNREFUSED или getaddrinfo ENOTFOUND при попытке Payload подключиться к БД. Причина почти всегда одна из трёх:
- БД слушает только localhost внутри своего контейнера, а Payload запущен в соседнем контейнере — если оба сервиса не в одной docker-сети,
DATABASE_URIсlocalhostработать не будет, нужно имя сервиса из docker-compose; - SSL требуется, но не указан — управляемые Postgres-инстансы часто требуют
sslmode=require, тогда как локальный контейнер — нет; - URI собран вручную с ошибкой в спецсимволах пароля — если в пароле есть
@,#или/, их нужно URL-энкодить, иначе парсер строки подключения обрежет часть адреса.
Рабочий пример для Postgres в .env:
DATABASE_URI=postgresql://payload_user:P%40ssw0rd@postgres:5432/payload_db
и в docker-compose сервис БД должен называться именно postgres, чтобы совпадать с хостом в URI:
services:
app:
build: .
env_file: .env
depends_on:
- postgres
ports:
- "3000:3000"
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: payload_user
POSTGRES_PASSWORD: P@ssw0rd
POSTGRES_DB: payload_db
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
Отдельно проверьте, что при первом деплое Payload успел выполнить миграции (npx payload migrate для Postgres-адаптера) — MongoDB создаёт коллекции на лету, а Postgres требует явных миграций, и без них падение будет не на подключении, а чуть позже, на первом запросе к отсутствующей таблице. Если держите БД отдельно от приложения, статья про настройку PostgreSQL на VPS и про типичные проблемы PostgreSQL на сервере закрывает большинство вопросов с доступом, лимитами соединений и правами.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверБилд падает: JavaScript heap out of memory
Классическая картина: npm run build доходит до генерации статических страниц админки или до сборки клиентских бандлов и падает с FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory. Это не баг Payload, а стандартное поведение Node на серверах с 1-2 ГБ RAM, где сборка Next.js конкурирует за память с самой ОС и, если есть, с БД в соседнем контейнере.
Три рабочих варианта решения, от простого к правильному:
- Временно поднять лимит heap — быстрый костыль на один билд:
NODE_OPTIONS="--max-old-space-size=2048" npm run build
- Добавить swap, если оперативной памяти физически мало (актуально для тарифов на 1-2 ГБ):
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
Swap не заменяет память для рантайма (диск на порядки медленнее), но именно для разовой сборки, которая упирается в пиковое потребление на несколько секунд, он снимает проблему почти всегда.
- Собирать образ не на самом сервере, а в CI или локально, и на сервер выкатывать уже готовый Docker-образ — правильный путь для продакшена, потому что тогда лимиты по памяти на боевой машине настраиваются под рантайм-нагрузку, а не под пиковую нагрузку сборки, которая обычно в разы выше.
Если сайт растёт и сборки регулярно упираются в лимиты — это сигнал, что тарифа на 2 ГБ уже не хватает; на 4 ГБ и выше heap-ошибки при билде Payload с типовым набором коллекций практически не встречаются.
Медиа, sharp и загрузка файлов
Payload использует библиотеку sharp для генерации миниатюр и обработки загружаемых изображений — это нативный биндинг на libvips, и он частая причина ошибок именно при переезде между окружениями:
Error: Could not load the "sharp" module— почти всегда значит, чтоnode_modulesсобирались на другой архитектуре (например, локально на Apple Silicon, а сервер — x86_64) или в другом Node ABI. Решение — не копироватьnode_modulesмежду машинами, а ставить зависимости заново прямо на сервере или внутри Docker-образа с тем же base image, что и в проде;- Alpine-образы требуют дополнительных системных библиотек для sharp; надёжнее использовать
node:20-slim(Debian) в Dockerfile для приложений с обработкой изображений, если не готовы разбираться с musl-специфичными сборками libvips; - Файлы пропадают после рестарта контейнера — если папка загрузок (
media/по умолчанию) не вынесена в volume, Docker удаляет её вместе с контейнером при пересоздании. Обязательно монтируйте том:
volumes:
- media_uploads:/app/media
- Для продакшена лучше не хранить медиа на диске сервера вообще, а подключить S3-совместимое хранилище через официальный плагин
@payloadcms/storage-s3— это снимает и вопрос volume, и вопрос бэкапов медиатеки отдельно от БД. Через тот же плагин Payload прекрасно работает с self-hosted MinIO, если не хотите зависеть от внешних облаков — разворачивается он на том же сервере или отдельной VPS, конфигурация описана в статье про установку MinIO на VPS.
Reverse proxy, переменные окружения и куки
Белый экран вместо админки или ошибка авторизации сразу после логина — почти всегда рассинхрон между тем, что видит браузер, и тем, что думает о себе сервер:
PAYLOAD_PUBLIC_SERVER_URL(илиserverURLв конфиге для Payload 3) должен точно совпадать с публичным адресом, включая протокол —https://cms.example.com, а неhttp://localhost:3000. Если значение не совпадает, CSRF-проверка Payload будет отклонять запросы из админки с ошибкой, похожей на «invalid CSRF token» или тихим редиректом на логин по кругу;- cookie с
secure: trueне будет сохраняться в браузере, если сайт открыт по HTTP или если nginx не передаёт заголовокX-Forwarded-Proto— Payload ориентируется на него, чтобы понять, что соединение зашифровано на уровне прокси, даже если само приложение внутри слушает обычный HTTP; - лимит на размер тела запроса в nginx по умолчанию — 1 МБ, а загрузка изображений через админку легко его превышает, тогда nginx возвращает
413 Request Entity Too Largeещё до того, как запрос дойдёт до Node.
Рабочий фрагмент конфига nginx как reverse proxy перед Payload:
server {
listen 443 ssl http2;
server_name cms.example.com;
client_max_body_size 25m;
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_read_timeout 90s;
}
}
proxy_read_timeout стоит увеличить отдельно — генерация нескольких размеров превью для больших изображений или тяжёлые GraphQL-запросы с глубокой вложенностью связей могут занимать больше стандартных 60 секунд. Если вместо nginx у вас Caddy с автоматическим SSL, логика та же самая — сравнение подходов есть в статье Caddy или nginx: что выбрать для сервера.
Процесс-менеджер, миграции и деплой без даунтайма
Payload — это обычное Node-приложение, и на голом VPS (без Docker) его нельзя просто оставить в терминале через npm run start — процесс умрёт при разрыве SSH-сессии. Нужен процесс-менеджер: PM2 — самый простой вариант для одиночного сервера без оркестрации.
npm i -g pm2
pm2 start npm --name payload-cms -- run start
pm2 save
pm2 startup
Файл ecosystem.config.js для более контролируемого запуска с ограничением по памяти и автоперезапуском:
module.exports = {
apps: [{
name: 'payload-cms',
script: 'npm',
args: 'run start',
max_memory_restart: '800M',
env: {
NODE_ENV: 'production',
PORT: 3000
}
}]
}
Отдельная грабля — миграции при деплое новой версии. Если менять схему коллекций (добавлять поля, менять типы) и просто перезапускать процесс без payload migrate, для MongoDB это часто проходит незаметно, а вот Postgres-адаптер начнёт падать на запросах к полям, которых ещё нет в таблице. Правильный порядок в деплой-скрипте:
npm ci
npm run build
npx payload migrate
pm2 reload payload-cms
pm2 reload (в отличие от restart) держит старый процесс живым, пока новый не пройдёт health-check — это даёт деплой без обрыва активных соединений, что для CMS с редакторами онлайн не косметика, а разница между «плавно обновилось» и «редактор потерял несохранённые правки». Если разворачиваете в Docker и Docker Compose, тот же принцип актуален для описания сервиса — общие практики собраны в статье про Docker Compose для продакшена.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Payload CMS требует MongoDB или можно на PostgreSQL?
Можно на PostgreSQL через официальный адаптер @payloadcms/db-postgres, он полноценно поддерживается наравне с MongoDB. Выбор адаптера фиксируется на старте проекта и меняется только через ручной перенос данных — «на лету» переключить БД под работающим сайтом нельзя.
Почему после деплоя админка открывается, но не даёт логиниться?
В 9 случаях из 10 — несовпадение PAYLOAD_PUBLIC_SERVER_URL с реальным адресом сайта или отсутствие X-Forwarded-Proto в конфиге reverse proxy, из-за чего secure-cookie не выставляется браузером.
Сколько RAM реально нужно Payload CMS в проде?
Для небольшого сайта с 1-2 редакторами хватает 2 ГБ, но сама сборка (npm run build) требовательнее рантайма — если сервер слабее 2 ГБ, закладывайте swap или собирайте образ отдельно от боевой машины. Ориентир справедлив для типового набора из 10-20 коллекций; на сильно более сложных схемах контента память может понадобиться и большая.
Можно ли хранить загруженные файлы прямо на диске VPS?
Можно для небольших проектов, но обязательно выносите папку медиа в отдельный volume (при Docker-деплое) и настраивайте резервное копирование отдельно от бэкапа БД — миграции и volume бэкапятся по-разному. Для растущих проектов удобнее сразу подключить S3-совместимое хранилище через встроенный плагин.
Нужен ли обязательно Docker для Payload CMS?
Нет, Payload прекрасно работает и как обычное Node-приложение под PM2 или systemd на голом VPS. Docker удобнее, когда нужна воспроизводимая среда сборки (в том числе из-за нативных зависимостей вроде sharp) или когда сервисов несколько и их проще держать в одном docker-compose.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →