Saleor в Docker Compose: готовый файл
Если вы выросли из коробочного интернет-магазина и упёрлись в его шаблоны — нужен свой мобильный клиент, кастомная логика ценообразования, витрина на Next.js вместо готовой темы — классический WooCommerce или PrestaShop начинают мешать больше, чем помогать. Saleor решает задачу иначе: это не магазин с сайтом внутри, а GraphQL API для e-commerce, к которому пристёгивается любой фронтенд. Ниже — рабочий docker-compose.yml, который поднимает весь бэкенд на своём сервере за один запуск.
Содержание
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Что такое Saleor и что нам понадобится
Saleor построен на Django и GraphQL, но в отличие от монолитных движков сайт и логика магазина здесь разделены. Бэкенд отдаёт данные (товары, заказы, скидки, склад, платежи) через один GraphQL-эндпоинт, а витрину вы пишете сами или берёте готовый шаблон на Next.js/React. Это осознанный компромисс: больше контроля и гибкости, но и больше движущихся частей, чем в связке "PHP + база + тема" — если задача проще, посмотрите установку и настройку WooCommerce, для среднего магазина это часто быстрее и дешевле.
Стек, который нужно поднять для Saleor целиком, состоит из пяти сервисов:
api— сам Django-бэкенд с GraphQL-эндпоинтом, обслуживает и админку, и витрину;worker— Celery-воркер: отправка писем, обработка вебхуков, экспорт каталога, активация скидок по расписанию — всё, что не должно тормозить ответ API;dashboard— административная панель (React SPA), через неё управляют товарами, заказами, настройками;db— PostgreSQL, основное хранилище;redis— кэш и брокер сообщений для Celery.
Это заметно тяжелее одного контейнера с CMS, поэтому и требования к серверу другие:
| Ресурс | Минимум (тест/разработка) | Комфортно (боевой магазин) |
|---|---|---|
| CPU | 2 vCPU | 4-8 vCPU |
| RAM | 4 ГБ | 8-16 ГБ |
| Диск | 40 ГБ SSD | 80+ ГБ SSD (растёт с каталогом фото) |
| ОС | Ubuntu 24.04 | Ubuntu 24.04 |
Если Docker ещё не установлен — разверните его по инструкции Docker Compose для продакшена на Ubuntu 24.04, там же плагин docker compose, без которого команды ниже не заработают.
Готовый docker-compose.yml
Создайте директорию проекта и файл окружения с секретами:
mkdir -p ~/saleor && cd ~/saleor
echo "DB_PASSWORD=$(openssl rand -base64 24)" > .env
echo "SECRET_KEY=$(openssl rand -base64 48)" >> .env
chmod 600 .env
nano docker-compose.yml
Содержимое docker-compose.yml:
services:
db:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_DB: saleor
POSTGRES_USER: saleor
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- saleor-db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U saleor"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
restart: unless-stopped
command: redis-server --appendonly yes
volumes:
- saleor-redis-data:/data
api:
image: ghcr.io/saleor/saleor:3.20
restart: unless-stopped
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
ports:
- "127.0.0.1:8000:8000"
environment:
SECRET_KEY: ${SECRET_KEY}
DATABASE_URL: postgres://saleor:${DB_PASSWORD}@db:5432/saleor
CELERY_BROKER_URL: redis://redis:6379/1
DEFAULT_FROM_EMAIL: noreply@вашдомен.ру
ALLOWED_HOSTS: api.вашдомен.ру,localhost
ALLOWED_CLIENT_HOSTS: https://dashboard.вашдомен.ру
volumes:
- saleor-media:/app/media
command: >
sh -c "python manage.py migrate &&
python manage.py collectstatic --noinput &&
uwsgi --http-socket :8000 --module saleor.wsgi --processes 4 --threads 2"
worker:
image: ghcr.io/saleor/saleor:3.20
restart: unless-stopped
depends_on:
- api
environment:
SECRET_KEY: ${SECRET_KEY}
DATABASE_URL: postgres://saleor:${DB_PASSWORD}@db:5432/saleor
CELERY_BROKER_URL: redis://redis:6379/1
volumes:
- saleor-media:/app/media
command: celery -A saleor worker -B --loglevel=info
dashboard:
image: ghcr.io/saleor/saleor-dashboard:latest
restart: unless-stopped
ports:
- "127.0.0.1:9000:80"
environment:
API_URL: https://api.вашдомен.ру/graphql/
volumes:
saleor-db-data:
saleor-redis-data:
saleor-media:
Пара нюансов. Порты 8000 и 9000 пробрасываются только на 127.0.0.1 — наружу стек отдаёт реверс-прокси, о нём в отдельном разделе ниже. Тег образа ghcr.io/saleor/saleor:3.20 зафиксирован сознательно (не latest) — Saleor выпускает минорные релизы регулярно, и перед docker compose pull стоит свериться с актуальным списком тегов на GitHub Container Registry, а не слепо брать версию из этой статьи. То же касается имён переменных окружения: между релизами Saleor их состав иногда меняется, поэтому перед боевым запуском сверьтесь с CHANGELOG в репозитории saleor/saleor на предмет вашей конкретной версии.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверПервый запуск: миграции, суперпользователь, демо-данные
Сначала поднимите базу и Redis отдельно и дождитесь, пока PostgreSQL пройдёт healthcheck:
docker compose up -d db redis
docker compose ps
Дальше поднимайте остальное:
docker compose up -d api worker dashboard
docker compose logs -f api
Команда api сама прогоняет migrate и collectstatic при каждом старте — это удобно для первого запуска, но на боевом сервере после обновления версии лучше прогонять миграции осознанно и следить за логом, а не полагаться на автозапуск при рестарте контейнера. Дождитесь в логе строки о старте uwsgi-воркеров — обычно первый прогон миграций занимает от 30 секунд до пары минут в зависимости от диска.
Создайте администратора для входа в дашборд:
docker compose exec api python manage.py createsuperuser
Для теста функциональности без ручного заполнения каталога есть команда с демо-данными (тестовые категории, товары, скидки, набор тестовых заказов):
docker compose exec api python manage.py populatedb --createsuperuser
Флаг --createsuperuser создаёт тестового администратора с предсказуемыми учётными данными — это удобно локально, но на боевом сервере такой аккаунт нужно сразу удалить или сменить ему пароль, иначе это открытая дверь в админку магазина.
Дашборд, GraphQL API и подключение витрины
Дашборд — статичное React-приложение, которое при старте контейнера читает переменную API_URL и обращается к указанному GraphQL-эндпоинту. Откройте http://ваш-сервер:9000 (или домен после настройки прокси) — попадёте на экран логина, вводите e-mail и пароль суперпользователя, созданного на предыдущем шаге.
Сам GraphQL-эндпоинт живёт на /graphql/ — через него дашборд, витрина и мобильные клиенты работают с одними и теми же данными. У него есть встроенный интроспективный обозреватель схемы, полезный при написании запросов и мутаций под витрину: какие поля доступны у товара, как оформить мутацию создания заказа, какие фильтры есть у списка товаров.
Собственно витрину Saleor не поставляет "из коробки" в этом compose-файле — это осознанная особенность headless-архитектуры. Варианты:
- написать свой фронтенд на Next.js/React, используя GraphQL-запросы к
/graphql/(в репозиториях Saleor есть примеры-стартеры — уточняйте актуальный на момент чтения, эта часть экосистемы меняется быстрее ядра); - подключить готовую тему от сторонних интеграторов, если вам не критична полная кастомизация;
- использовать API для мобильного приложения или интеграции с маркетплейсами без веб-витрины вовсе.
Платёжные шлюзы (Stripe, Adyen и другие) подключаются как приложения (Apps) через раздел Configuration → Apps в дашборде — там же живёт встроенный тестовый Dummy-плагин, полезный для проверки процесса оформления заказа без реальных денег на этапе разработки.
Хранилище медиафайлов: MinIO вместо локального диска
По умолчанию фотографии товаров и другие медиафайлы складываются в volume saleor-media, примонтированный в /app/media у api и worker. Для одного сервера это рабочий вариант, но у него два ограничения: том растёт вместе с каталогом и требует отдельного бэкапа помимо базы, а если вы позже захотите вынести обработку изображений или масштабировать api на несколько реплик за балансировщиком — общий локальный volume превращается в проблему.
Более надёжный путь для магазина, который планирует расти, — S3-совместимое хранилище через django-storages, которое Saleor поддерживает штатно (переменные вида AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_STORAGE_BUCKET_NAME, AWS_S3_ENDPOINT_URL). Их точный набор слегка отличается между релизами Saleor, поэтому перед боевым запуском сверьтесь с актуальным разделом про хранилище в документации вашей версии — тут не тот случай, где стоит копировать значения вслепую.
Если не хотите зависеть от внешнего облака, разверните S3-совместимое хранилище у себя: MinIO в Docker Compose поднимается тем же способом, что и весь остальной стек, и его можно добавить как ещё один сервис в этот же docker-compose.yml, указав его внутренний адрес в AWS_S3_ENDPOINT_URL.
Реверс-прокси, HTTPS и бэкапы
Отдавать 8000 и 9000 порты напрямую в интернет не стоит — без TLS логин администратора и данные заказов уходят открытым текстом. Ставьте перед стеком Traefik (общий принцип разобран в статье Traefik как reverse proxy для докера) с двумя поддоменами — например, api.вашдомен.ру на сервис api и admin.вашдомен.ру на dashboard:
labels:
- "traefik.enable=true"
- "traefik.http.routers.saleor-api.rule=Host(`api.вашдомен.ру`)"
- "traefik.http.routers.saleor-api.entrypoints=websecure"
- "traefik.http.routers.saleor-api.tls.certresolver=le"
- "traefik.http.services.saleor-api.loadbalancer.server.port=8000"
Аналогичный блок с другим именем роутера и портом 80 — для сервиса dashboard. После смены доменов не забудьте обновить ALLOWED_HOSTS и ALLOWED_CLIENT_HOSTS у api — иначе получите либо 400 Bad Request от Django, либо ошибку CORS в консоли браузера при обращении дашборда к API.
Бэкап — дамп базы плюс архив медиатома (или снимок бакета, если вы уже перешли на S3/MinIO):
#!/bin/bash
# /opt/scripts/saleor-backup.sh
set -e
BACKUP_DIR="/backup/saleor/$(date +%F)"
mkdir -p "$BACKUP_DIR"
cd ~/saleor
docker compose exec -T db pg_dump -U saleor -Fc saleor > "$BACKUP_DIR/saleor.dump"
docker run --rm -v saleor_saleor-media:/data -v "$BACKUP_DIR":/backup alpine \
tar czf /backup/media.tar.gz -C /data .
find /backup/saleor -mindepth 1 -maxdepth 1 -mtime +14 -exec rm -rf {} \;
Добавьте в cron ежедневный запуск. Обновление версии — это docker compose pull, повторный up -d и внимательное чтение CHANGELOG перед major-апгрейдом: e-commerce с живыми заказами не то место, где стоит обновляться вслепую, — сначала прогоните миграции на копии базы.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Saleor бесплатен?
Ядро распространяется как open source, и то, что описано в этой статье, можно развернуть и использовать бесплатно на своём сервере. Отдельно существует коммерческое облачное предложение от компании-разработчика — это другой продукт, не обязательный для самостоятельного хостинга.
Нужен ли отдельный сервер под витрину?
Да, фронтенд — отдельное приложение, обычно на Next.js/React, обращающееся к вашему GraphQL API. Его можно разместить как ещё один контейнер рядом с описанным стеком либо вынести отдельно — в этот compose-файл витрина не входит намеренно, это часть headless-архитектуры.
Можно ли перенести каталог из WooCommerce или другой платформы?
Технически да — через GraphQL-мутации или встроенный CSV-импорт товаров в дашборде (Configuration → Import). Для небольшого каталога хватит ручного экспорта-импорта, для тысяч товаров с атрибутами обычно пишут отдельный скрипт под схему исходной платформы.
Celery-воркер не отправляет письма и не обрабатывает вебхуки — с чего начать?
Проверьте, что контейнер worker запущен (docker compose ps), затем его логи (docker compose logs worker) и доступность Redis по адресу из CELERY_BROKER_URL — частая причина простоя в том, что воркер не может достучаться до брокера или падает при старте из-за несовпадения SECRET_KEY/DATABASE_URL с api.
Хватит ли минимальной конфигурации из таблицы для боевого магазина?
Для теста и первой настройки — да, но при живом трафике узким местом обычно становится PostgreSQL и число uwsgi-процессов у api: если ответы GraphQL начали тормозить, сначала проверьте нагрузку на базу и только потом увеличивайте --processes.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →