Saleor на сервере: частые ошибки и решения
Saleor — не обычная коробочная CMS: это GraphQL-first платформа на Django и PostgreSQL с отдельным API-слоем, очередью Celery и React-дашбордом, которые нужно поднимать и синхронизировать вручную. Отсюда и специфика ошибок: недоступный API вместо белого экрана, зависшие фоновые задачи вместо «не сохраняется форма», путаница с переменными окружения вместо проблем с правами на файлы. Разберём типичные сбои Saleor на сервере по порядку — от установки до продакшен-нагрузки — с конкретными командами и объяснением, откуда растут ноги у каждой проблемы.
Содержание
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Миграции базы падают или зависают
Первое, с чем сталкиваются при установке Saleor, — команда python manage.py migrate завершается с ошибкой или висит без вывода. Чаще всего причина в версии PostgreSQL: Saleor требует PostgreSQL 13 или новее, и на более старой версии часть миграций падает на несовместимых типах данных или расширениях. Проверьте версию:
psql --version
Вторая частая причина — не установлено расширение pg_trgm, которое Saleor использует для полнотекстового поиска и триграммного индексирования. Без него миграции, создающие индексы GIN, обрываются с ошибкой вида function similarity(text, text) does not exist. Подключитесь к базе Saleor и создайте расширение вручную:
psql -U saleor -d saleor -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;"
Если миграция «висит» без явной ошибки — это почти всегда блокировка (lock) от предыдущего незавершённого процесса миграции или открытой транзакции. Посмотрите активные блокировки:
psql -U saleor -d saleor -c "SELECT pid, query, state FROM pg_stat_activity WHERE state != 'idle';"
Завершите зависший процесс через SELECT pg_terminate_backend(pid); и повторите миграцию. На чистом сервере разумно сразу настроить PostgreSQL под нагрузку — Saleor открывает много одновременных соединений даже на старте, и параметры по умолчанию быстро упираются в max_connections.
GraphQL API отдаёт 500 или недоступен снаружи
Дашборд открывается, но при любом запросе к API получаете 500 или белый ответ без деталей — это самый частый источник паники у тех, кто впервые настраивает Saleor: в отличие от обычного сайта, здесь нет привычной страницы с ошибкой PHP, есть только GraphQL-ответ или пустой JSON. Первым делом посмотрите логи контейнера или процесса API:
docker logs saleor-api --tail 100
Частая причина 500 на GraphQL-запросах — не заданы или неверно заданы переменные окружения SECRET_KEY, DATABASE_URL или ALLOWED_HOSTS. Saleor построен на Django, и если домен сервера не входит в ALLOWED_HOSTS, запросы к API будут падать с DisallowedHost в логе, даже если снаружи это выглядит как безликая 500. Пропишите домен явно в .env:
ALLOWED_HOSTS=api.vashdomen.ru,localhost
Если API вовсе не отвечает снаружи (connection refused), проверьте, что процесс слушает не только localhost. Saleor по умолчанию через Gunicorn/Uvicorn биндится на 127.0.0.1:8000 внутри контейнера — снаружи к нему обращается реверс-прокси. Убедитесь, что Nginx действительно проксирует на правильный порт и что контейнер API вообще поднят:
docker ps | grep saleor
Здесь же стоит свериться с общей схемой: Nginx в роли реверс-прокси на VPS — Saleor всегда идёт за прокси, напрямую в интернет API отдавать не стоит ни по безопасности, ни по производительности.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверCelery не забирает задачи, письма и вебхуки не уходят
Заказ создаётся, но письмо клиенту не приходит, вебхуки не срабатывают, отложенные задачи копятся и не выполняются — почти всегда это означает, что воркер Celery либо не запущен, либо не видит очередь. Saleor использует Celery с Redis или RabbitMQ в качестве брокера, и это отдельный процесс, который часто забывают запустить рядом с самим API при ручной установке. Проверьте, что воркер вообще работает:
docker ps | grep celery
celery -A saleor status
Если воркер запущен, но задачи не выполняются, проблема почти всегда в CELERY_BROKER_URL — он должен указывать на тот же Redis/RabbitMQ, что видит и API-контейнер, включая правильное имя хоста в docker-сети (не localhost, а имя сервиса из docker-compose.yml, например redis). Если у вас несколько окружений (staging/prod) — легко перепутать URL и слать задачи «в никуда», где они просто накапливаются без ошибок.
Отдельно проверьте память Redis: под нагрузкой очередь может забить всю выделенную память, и тогда новые задачи начнут отбрасываться без явного лога об этом со стороны Saleor. Посмотрите текущее потребление:
redis-cli info memory | grep used_memory_human
Если брокер — RabbitMQ, а не Redis, полезно свериться с общими причинами сбоев очередей на сервере, но в целом логика та же: воркер должен быть жив, брокер — доступен по правильному адресу, память — не исчерпана.
Медиафайлы товаров не отображаются
Товары создаются, но изображения не грузятся или после загрузки отдают 404 — типичная проблема разделения API и storage в Saleor. Если вы храните медиа локально на диске сервера, а не в S3-совместимом хранилище, убедитесь, что путь MEDIA_ROOT смонтирован как volume, который переживает пересоздание контейнера:
volumes:
- saleor_media:/app/media
Без именованного volume при каждом docker compose up -d --build содержимое каталога медиа стирается вместе с контейнером — это одна из самых обидных ошибок: магазин выглядит рабочим, пока не случится очередной деплой, после которого исчезают все фото товаров. Также проверьте, что Nginx отдаёт статику по пути /media/ напрямую с диска, а не пытается проксировать её через Django — это и медленнее, и может упираться в таймауты на больших изображениях.
Если используете внешнее S3-хранилище (что для продакшена предпочтительнее — так медиа переживают пересборку и масштабирование на несколько инстансов), проверьте переменные AWS_STORAGE_BUCKET_NAME, AWS_S3_ENDPOINT_URL и права доступа ключа — неверный endpoint_url даёт молчаливую ошибку загрузки без внятного сообщения в UI дашборда, детали смотрите только в логе API-контейнера.
CORS блокирует storefront или дашборд
Витрина (storefront) или админ-дашборд открывается, но запросы к API падают в консоли браузера с ошибкой CORS — Saleor по умолчанию не разрешает кросс-доменные запросы с произвольных доменов, и если дашборд или витрина крутятся на отдельном поддомене (например, shop.vashdomen.ru обращается к api.vashdomen.ru), это нужно явно прописать. В .env API добавьте:
CORS_ALLOWED_ORIGINS=https://shop.vashdomen.ru,https://admin.vashdomen.ru
Обратите внимание: CORS_ALLOWED_ORIGINS требует точного совпадения схемы и домена, включая https:// и отсутствие завершающего слэша — частая опечатка, из-за которой правило «вроде прописано, но не работает». Если используете GraphQL Playground или сторонние интеграции для тестирования API, для них тоже нужен отдельный домен в списке, иначе запросы будут молча блокироваться браузером ещё до того, как дойдут до сервера.
Если после исправления CORS запросы всё равно падают, проверьте заголовки ответа Nginx — иногда прокси сам перезаписывает или дублирует Access-Control-Allow-Origin, и тогда два конфликтующих заголовка от Django и Nginx ломают то, что на бэкенде настроено верно.
Высокая нагрузка на PostgreSQL при росте каталога
Saleor — это десятки связанных таблиц (товары, варианты, атрибуты, склады, цены по каналам продаж), и GraphQL-запросы с глубокой вложенностью легко порождают N+1-проблему на стороне базы, даже если фронтенд запрашивает вроде бы один список товаров. Симптом — растущее время ответа API и высокая загрузка PostgreSQL по CPU при относительно небольшом трафике. Посмотрите медленные запросы:
psql -U saleor -d saleor -c "SELECT query, calls, mean_exec_time FROM pg_stat_statements ORDER BY mean_exec_time DESC LIMIT 10;"
Для расширения pg_stat_statements нужно предварительно включить его в postgresql.conf и перезапустить сервис. Частая практическая мера — вынести аналитические и экспортные запросы (выгрузки каталога, отчёты) на реплику для чтения, не нагружая основную базу, обслуживающую живые заказы. Если у вас каталог на десятки тысяч товаров, стоит заранее прикинуть, сколько ресурсов нужно VPS под интернет-магазин — для Saleor цифры обычно выше, чем для классических коробочных решений, из-за отдельных процессов API, воркера и дашборда, которые работают параллельно, а не в одном PHP-процессе.
Здесь же стоит регулярно проверять размер WAL-журналов — Saleor генерирует много write-операций на каждый заказ (резервирование склада, пересчёт цен, логирование событий), и без настроенной ротации wal_keep_size диск может неожиданно закончиться в разгар распродажи.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Saleor можно ставить без Docker, напрямую на сервер?
Технически да, но официально и практически поддерживаемый путь — Docker Compose: слишком много взаимосвязанных сервисов (API, воркер Celery, Redis, PostgreSQL, дашборд), чтобы разворачивать их вручную без риска рассинхронизации версий.
Почему после деплоя пропадают фотографии товаров?
Медиа хранились в каталоге контейнера без именованного volume, и при пересборке контейнер создался заново с пустым диском. Вынесите /app/media в volume или переходите на S3-совместимое хранилище.
Дашборд открывается, но список заказов пустой при наличии заказов в базе?
Обычно это или не тот channel выбран в интерфейсе (Saleor поддерживает мультиканальные продажи с разными наборами данных), или устаревший кэш GraphQL на клиенте — очистите кэш браузера и проверьте выбранный канал в шапке дашборда.
Нужен ли отдельный сервер под Celery-воркер?
Для небольшого магазина воркер спокойно живёт на одном сервере с API. При росте потока заказов и вебхуков (особенно интеграций с 1С или внешними платёжными системами) имеет смысл вынести воркер на отдельный VPS, чтобы фоновые задачи не конкурировали с API за CPU и память.
Как понять, что не хватает памяти именно PostgreSQL, а не Redis или API?
Смотрите потребление по каждому процессу отдельно — docker stats покажет, какой контейнер упирается в лимит. Часто именно PostgreSQL первым выедает память под shared_buffers и кэш планов запросов при растущем каталоге.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →