Удельта

Пошаговая инструкция · Самостоятельная установка

Как установить Saleor и Next.js Storefront на сервер Ubuntu

Пошаговая инструкция для самостоятельной установки. Развернём Saleor API, административную панель и production-витрину Next.js на одном сервере. Создадим тестовый товар, проверим изображение и корзину, сохраним базу, медиа и ключи для восстановления.

Первый товар переходит с витрины в корзину собственного интернет-магазина

Что подготовить и как читать инструкцию #

Saleor — сервер электронной торговли с GraphQL API. Dashboard управляет каталогом и заказами, а Next.js Storefront показывает товары покупателю. Дополнительно нужны PostgreSQL, очередь Valkey, Celery worker и один планировщик Beat. Пример рассчитан на небольшой магазин на одном сервере; отказ этого VPS остановит все компоненты.

Подготовьте Ubuntu 26.04 LTS, Docker Engine, Compose v2, Nginx, git и openssl по общей инструкции. Для сборки витрины на том же сервере стартуйте с 4–8 vCPU и 12–16 ГБ памяти. Каталог, обработка изображений и число одновременных корзин определяют дальнейшие ресурсы. База и медиа должны храниться постоянно, а резервные копии — вне этого сервера.

Команды выполняйте в терминале сервера после подключения по SSH, если шаг не указывает другое место. sudo даёт команде права администратора. Действия в браузере выполняйте на своём компьютере. Домены, IP-адреса и значения ВАШ_ПАРОЛЬ из примеров заменяйте своими до выполнения команды.

Двигайтесь по одному шагу и проверяйте результат перед следующим. Блок с <<EOF или другим именем завершения копируйте целиком: он записывает содержимое одного файла. При работе в nano сохраняйте файл клавишами Ctrl+O, Enter и выходите Ctrl+X. Если прервали SSH-сеанс, снова перейдите в указанный каталог; переменные предыдущего сеанса потребуется задать заново.

Настройка внешнего доступа использует обратный прокси Nginx и Certbot для Nginx. Приложение остаётся доступным на локальном порту; снаружи открыт его HTTPS-домен.

Дополнительные официальные источники: Миграции и первый администратор, Релиз API 3.23.39, Production-шаблон Paper, Каналы магазина, Товары и варианты, Dockerfile API релиза 3.23.39.

Шаг 1. Подготовьте каталог магазина #

В /srv/saleor будут конфигурация API, файлы магазина и исходники витрины. Создадим каталог и дадим владельцу SSH-сеанса право работать с ним.

Подготовьте каталог магазина
sudo mkdir -p /srv/saleor/media
sudo chown -R "$USER":"$USER" /srv/saleor
chmod 755 /srv/saleor /srv/saleor/media
cd /srv/saleor

Текущий каталог — /srv/saleor.

Шаг 2. Создайте серверный ключ JWT #

Создаём закрытый RSA-ключ. Он используется сервером для подписания токенов и должен сохраняться при восстановлении.

Создайте серверный ключ JWT
umask 077
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out jwt.pem

jwt.pem создан; его содержимое не передавайте в браузер или публичный репозиторий.

Шаг 3. Сохраните секреты приложения в .env #

В .env запишем независимые секреты и подготовленный RSA-ключ. Небольшой Python-скрипт только переводит ключ в нужное для окружения представление; chmod закрывает файлы от посторонних. SMTP-адреса пока примеры: на следующем шаге заменим их реальными реквизитами. Генератор предназначен только новой установке.

Сохраните секреты приложения в .env
cat > .env <<EOF
POSTGRES_PASSWORD=$(openssl rand -hex 32)
SECRET_KEY=$(openssl rand -hex 48)
REVALIDATE_SECRET=$(openssl rand -hex 32)
ORDER_VIEW_SECRET=$(openssl rand -hex 32)
EMAIL_URL=smtp+tls://replace-user:replace-password@smtp.example.com:587
USER_EMAIL_URL=smtp+tls://replace-user:replace-password@smtp.example.com:587
DEFAULT_FROM_EMAIL=shop@example.com
DEFAULT_COUNTRY=RU
DEFAULT_CURRENCY=RUB
EOF
python3 - <<'KEY'
from pathlib import Path
with Path('.env').open('a') as f:
    f.write("RSA_PRIVATE_KEY='" + Path('jwt.pem').read_text() + "'\n")
KEY
chmod 600 .env jwt.pem

Создан .env, JWT-настройка заполнена; права .env и jwt.pem — 600.

Шаг 4. Заполните адреса отправки почты #

Откройте .env. Измените EMAIL_URL, USER_EMAIL_URL и DEFAULT_FROM_EMAIL по данным своего SMTP; специальные символы логина/пароля внутри URL требуют кодирования. Сохраните созданные ключи.

Заполните адреса отправки почты
nano .env

В SMTP нет replace-user/replace-password; отправитель ваш.

Шаг 5. Скачайте закреплённую витрину #

Получим официальный Storefront Paper и переключим его на выбранную ревизию. Файлы витрины — отдельный компонент от API Saleor.

Скачайте закреплённую витрину
git clone https://github.com/saleor/storefront.git storefront
git -C storefront checkout b73bdce3269cceb08feff856af5067d117c79cb6

Исходники находятся в storefront, выбран указанный коммит.

Шаг 6. Сохраните описание всех компонентов #

Откройте nano compose.yaml и вставьте YAML. API обслуживает GraphQL, worker/beat обрабатывают задания, dashboard — управление магазином, storefront — покупательскую витрину. Замените домены примера во всём файле.

Сохраните описание всех компонентов
x-saleor: &saleor
  image: ghcr.io/saleor/saleor:3.23.39
  restart: unless-stopped
  env_file: .env
  environment: &saleor-env
    DEBUG: "False"
    ALLOWED_HOSTS: saleor-api.example.com,api,localhost,127.0.0.1
    ALLOWED_CLIENT_HOSTS: shop.example.com,saleor-admin.example.com
    ALLOWED_GRAPHQL_ORIGINS: https://shop.example.com,https://saleor-admin.example.com
    PUBLIC_URL: https://saleor-api.example.com/
    DASHBOARD_URL: https://saleor-admin.example.com/dashboard/
    MEDIA_URL: https://saleor-api.example.com/media/
    DATABASE_URL: postgres://saleor:${POSTGRES_PASSWORD}@db:5432/saleor
    CACHE_URL: redis://cache:6379/0
    CELERY_BROKER_URL: redis://cache:6379/1
    PLAYGROUND_ENABLED: "False"
  volumes:
    - ./media:/app/media
  depends_on:
    db:
      condition: service_healthy
    cache:
      condition: service_started
services:
  db:
    image: postgres:15-alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: saleor
      POSTGRES_USER: saleor
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set password}
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: [CMD-SHELL, "pg_isready -U saleor -d saleor"]
      interval: 10s
      timeout: 5s
      retries: 10
  cache:
    image: valkey/valkey:8.1-alpine
    restart: unless-stopped
    command: [valkey-server, --appendonly, "yes", --appendfsync, everysec]
    volumes:
      - cachedata:/data
  api:
    <<: *saleor
    ports:
      - "127.0.0.1:8000:8000"
  worker:
    <<: *saleor
    command: [celery, --app, "saleor.celeryconf:app", worker, -E, --loglevel=info, --concurrency=2]
  beat:
    <<: *saleor
    command: [celery, --app, "saleor.celeryconf:app", beat, --scheduler, saleor.schedulers.schedulers.DatabaseScheduler]
  dashboard:
    image: ghcr.io/saleor/saleor-dashboard:3.23.39
    restart: unless-stopped
    ports:
      - "127.0.0.1:9002:80"
    environment:
      API_URL: https://saleor-api.example.com/graphql/
      APP_MOUNT_URI: /dashboard/
  storefront:
    image: saleor-paper-local:b73bdce
    build:
      context: ./storefront
      dockerfile: Storefront.Dockerfile
      args:
        NEXT_PUBLIC_SALEOR_API_URL: https://saleor-api.example.com/graphql/
        NEXT_PUBLIC_STOREFRONT_URL: https://shop.example.com
        NEXT_PUBLIC_DEFAULT_CHANNEL: default-channel
    restart: unless-stopped
    ports:
      - "127.0.0.1:3011:3000"
    environment:
      NODE_ENV: production
      HOSTNAME: 0.0.0.0
      NEXT_PUBLIC_SALEOR_API_URL: https://saleor-api.example.com/graphql/
      NEXT_PUBLIC_STOREFRONT_URL: https://shop.example.com
      NEXT_PUBLIC_DEFAULT_CHANNEL: default-channel
      REVALIDATE_SECRET: ${REVALIDATE_SECRET:?set secret}
      ORDER_VIEW_SECRET: ${ORDER_VIEW_SECRET:?set secret}
      ALLOW_DUMMY_PAYMENT: "false"
      NEXT_PUBLIC_ALLOW_DUMMY_PAYMENT: "false"
volumes:
  pgdata:
  cachedata:

Сохранён Compose, внутренние порты привязаны к localhost.

Шаг 7. Проверьте конфигурацию #

Проверим YAML и переменные до запуска. Витрину будем собирать после появления работающего HTTPS API.

Проверьте конфигурацию
docker compose config --quiet

config --quiet завершился без ошибки.

Шаг 8. Скачайте готовые компоненты #

Загрузим API, панель, базу и очередь. Storefront — локальная сборка из исходников, поэтому её здесь ещё не скачиваем.

Скачайте готовые компоненты
docker compose pull api worker beat dashboard db cache

pull завершился успешно.

Шаг 9. Запустите базу и кеш #

База и кеш нужны миграции. После создания PostgreSQL не меняйте POSTGRES_PASSWORD только в .env: пароль существующей базы автоматически не изменится.

Запустите базу и кеш
docker compose up -d db cache

db и cache запущены.

Шаг 10. Создайте таблицы Saleor #

Штатная migrate подготавливает структуру новой базы. Выполняется одноразовый контейнер API и удаляется после окончания.

Создайте таблицы Saleor
docker compose run --rm api python3 manage.py migrate

Миграции завершились без ошибки.

Шаг 11. Создайте администратора магазина #

createsuperuser интерактивно спросит email и пароль. Это административная запись магазина, а не пароль базы.

Создайте администратора магазина
docker compose run --rm api python3 manage.py createsuperuser

Администратор создан; реквизиты сохранены.

Шаг 12. Запустите API и панель #

Запустим основные компоненты и посмотрим журнал обработки. Витрина пока отсутствует — это нормальный промежуточный результат.

Запустите API и панель
docker compose up -d api worker beat dashboard
docker compose logs --tail=80 api worker beat

API, worker, beat и dashboard работают без повторных ошибок.

Шаг 13. Сохраните сайт Nginx #

Установите Nginx и Certbot по подготовке Ubuntu, если ещё не установили. Откройте sudo nano /etc/nginx/sites-available/saleor-nextjs и вставьте файл ниже. Замените все домены своими. Замените 203.0.113.10 внешним IP своего рабочего компьютера: ограничение действует только на панель, а проверка сертификата остаётся доступной. Nginx передаёт приложению домен, адрес клиента и протокол; Upgrade и Connection сохраняют WebSocket. До получения сертификата условие $scheme блокирует обычные HTTP-запросы кодом 403, чтобы вход не происходил без шифрования. /media/ выдаётся из /srv/saleor/media/ с сохранением соответствия пути; Nginx должен иметь право чтения файлов. Пока storefront ещё не запущен, его HTTPS-сайт может отвечать 502; сертификат всё равно можно получить.

Файл /etc/nginx/sites-available/saleor-nextjs
map $http_upgrade $saleor_nextjs_connection_upgrade {
    default upgrade;
    "" close;
}

server {
    listen 80;
    server_name saleor-api.example.com;
    client_max_body_size 100m;
    location /media/ {
        if ($scheme = http) { return 403; }
        alias /srv/saleor/media/;
    }
    location / {
        if ($scheme = http) { return 403; }
        proxy_pass http://127.0.0.1:8000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $saleor_nextjs_connection_upgrade;
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
        proxy_buffering off;
    }
}

server {
    listen 80;
    server_name saleor-admin.example.com;
    client_max_body_size 100m;
    location / {
        if ($scheme = http) { return 403; }
        allow 203.0.113.10;
        deny all;
        proxy_pass http://127.0.0.1:9002;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $saleor_nextjs_connection_upgrade;
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
        proxy_buffering off;
    }
}

server {
    listen 80;
    server_name shop.example.com;
    client_max_body_size 100m;
    location / {
        if ($scheme = http) { return 403; }
        proxy_pass http://127.0.0.1:3011;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $saleor_nextjs_connection_upgrade;
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
        proxy_buffering off;
    }
}

Сохраните Ctrl+O, Enter, Ctrl+X. В файле пока нет путей к сертификатам: Nginx сможет проверить его до выпуска HTTPS.

Шаг 14. Подключите сайт и проверьте HTTP #

Ссылка подключает сохранённый сайт. nginx -t проверяет все настройки сервера; && разрешает перечитать их только при успешной проверке. curl проверяет ответ для вашего домена. При ошибке остановитесь, исправьте указанную строку и повторите проверку.

Подключите сайт и проверьте HTTP
sudo ln -sfn /etc/nginx/sites-available/saleor-nextjs /etc/nginx/sites-enabled/saleor-nextjs
sudo nginx -t && sudo systemctl reload nginx
curl -I http://saleor-api.example.com/

Ожидается successful от nginx -t и HTTP 403 для обычного запроса: до HTTPS это намеренная блокировка. DNS должен указывать на сервер, TCP 80/443 — быть доступен у провайдера. Ничего не вводите в форму входа по HTTP.

Шаг 15. Получите сертификат и включите HTTPS #

Каждое имя из команды должно указывать на этот сервер. Certbot проверит домены, сам добавит сертификат в сайт Nginx и включит перенаправление HTTP → HTTPS. Укажите свою почту при запросе и подтвердите условия Let’s Encrypt. Это отдельное действие после успешной проверки HTTP.

Получите сертификат и включите HTTPS
sudo /snap/bin/certbot --nginx -d saleor-api.example.com -d saleor-admin.example.com -d shop.example.com --redirect

Certbot сообщил об успешной установке. Откройте HTTPS-адрес со своего компьютера: браузер не должен предупреждать о сертификате. Для закрытой панели используйте разрешённый IP. При ошибке исправьте DNS, в том числе AAAA, либо доступ к TCP 80; не повторяйте запросы выпуска подряд.

Шаг 16. Проверьте продление сертификата #

Certbot из snap создаёт службу автоматического продления. Dry-run выполняет пробное продление: настоящие сертификаты не заменяются. Затем проверьте, что таймер планирует следующий запуск. Сохраняйте доступ к TCP 80 для следующих проверок домена.

Проверьте продление сертификата
sudo /snap/bin/certbot renew --dry-run
systemctl list-timers --all 'snap.certbot.renew*'

Пробное продление успешно, у таймера есть следующий запуск. Если тест не прошёл, проверьте /var/log/letsencrypt/letsencrypt.log; сначала исправьте причину. После этого можно создавать учётную запись и приглашать пользователей.

Шаг 17. Сохраните Dockerfile витрины #

Создайте nano storefront/Storefront.Dockerfile и вставьте шаблон. Он ставит зависимости из lock-файла и собирает standalone Next.js. В storefront/.dockerignore добавьте .env*, node_modules и .next; рабочие секреты не должны попасть в build context.

Сохраните Dockerfile витрины
FROM node:24-alpine AS builder
RUN apk add --no-cache libc6-compat
RUN corepack enable && corepack prepare pnpm@10.28.1 --activate
WORKDIR /app
COPY . .
RUN pnpm install --frozen-lockfile
ARG NEXT_PUBLIC_SALEOR_API_URL
ARG NEXT_PUBLIC_STOREFRONT_URL
ARG NEXT_PUBLIC_DEFAULT_CHANNEL=default-channel
ENV NEXT_PUBLIC_SALEOR_API_URL=${NEXT_PUBLIC_SALEOR_API_URL}
ENV NEXT_PUBLIC_STOREFRONT_URL=${NEXT_PUBLIC_STOREFRONT_URL}
ENV NEXT_PUBLIC_DEFAULT_CHANNEL=${NEXT_PUBLIC_DEFAULT_CHANNEL}
ENV NEXT_TELEMETRY_DISABLED=1 NEXT_PUBLIC_ALLOW_DUMMY_PAYMENT=false
RUN NEXT_OUTPUT=standalone pnpm build

FROM node:24-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production HOSTNAME=0.0.0.0 PORT=3000
RUN addgroup -S -g 1001 app && adduser -S -u 1001 -G app app
COPY --from=builder --chown=app:app /app/public ./public
COPY --from=builder --chown=app:app /app/.next/standalone ./
COPY --from=builder --chown=app:app /app/.next/static ./.next/static
RUN mkdir -p .next/cache && chown -R app:app .next
USER app
CMD ["node", "server.js"]

Dockerfile сохранён; в папке исходников нет рабочих .env с секретами.

Шаг 18. Соберите и запустите витрину #

Сборка обращается к HTTPS GraphQL, поэтому API должен уже отвечать. Публичный API-адрес входит в браузерную сборку: при его изменении понадобится новая сборка.

Соберите и запустите витрину
docker compose build storefront
docker compose up -d storefront

Сборка успешна; shop.example.com открывает витрину.

Шаг 19. Подготовьте тестовый товар #

В Dashboard создайте канал продаж с идентификатором из настроек витрины, склад, товар с вариантом, ценой и остатком. Опубликуйте товар для этого канала.

Товар виден в витрине с ценой и доступным вариантом.

Шаг 20. Проверьте тестовый заказ #

Добавьте товар в корзину, настройте доставку и выбранный платёжный модуль по его документации. Проводите проверку в тестовом режиме провайдера, не включайте фиктивную оплату для рабочего магазина.

Тестовый заказ появляется в Dashboard, меняет остаток по выбранным правилам и доставляет письмо покупателю.

Шаг 21. Остановите запись перед копией #

Создадим закрытый каталог и остановим API, фоновые задания и витрину; база останется доступной. Предупредите о перерыве продаж.

Остановите запись перед копией
cd /srv/saleor
umask 077
backup_dir="$HOME/backups/saleor/$(date -u +%Y%m%dT%H%M%SZ)"
mkdir -p "$backup_dir"
docker compose stop api worker beat storefront

Компоненты остановлены, backup_dir задан в этом SSH-сеансе.

Шаг 22. Скопируйте базу и файлы #

Дамп содержит каталог и заказы, архив — media, ключи и конфигурацию. Не сохраняем пересоздаваемые node_modules и .next. Перенесите обе части во внешнее закрытое хранилище.

Скопируйте базу и файлы
docker compose exec -T db pg_dump -U saleor saleor > "$backup_dir/database.sql"
tar --exclude=storefront/node_modules --exclude=storefront/.next \
  -czf "$backup_dir/files.tar.gz" media .env jwt.pem compose.yaml storefront

Оба файла созданы без ошибки; копия содержит базу и исходники/настройки витрины.

Шаг 23. Верните магазин и проверьте копию #

Запустите сервисы. Восстановление проверяйте на отдельном сервере с тестовыми SMTP и оплатой: оно не должно отправлять повторные рабочие письма или платежи.

Верните магазин и проверьте копию
docker compose start api worker beat storefront

Тестовый товар и заказ доступны после запуска и восстановления.

Если результат шага отличается #

Вернитесь к проверке того шага, на котором появилась ошибка. Первая ошибка в выводе обычно полезнее последних строк: последующие команды могли не получить нужный файл или работающую службу. Не повторяйте установку целиком поверх частично настроенного приложения.

Если страница не открывается, сначала проверьте домен и входящие порты, затем состояние приложения и его журнал. Если сайт открывается, но отдельное действие не работает, проверьте связанные компоненты — обработчик фоновых задач, почту или права пользователя. Официальные источники в конце статьи относятся к выбранному способу установки.

Настройки внешнего доступа храните вместе с копией приложения: /etc/nginx/sites-available/saleor-nextjs и /etc/letsencrypt содержат сайт и сертификаты с закрытыми ключами. Архивируйте их с sudo в закрытое хранилище. После восстановления подключите сайт через sites-enabled, выполните sudo nginx -t, затем перечитайте Nginx. Для ошибки 502 сначала проверьте локальное приложение и /var/log/nginx/error.log; для сертификата — /var/log/letsencrypt/letsencrypt.log и таймер продления.

Обсудить установку и настройку

Материал был полезен?

Сообщить об ошибке или задать вопрос редакции

Сообщение увидит редакция. Не указывайте пароли и личные данные. Текст удаляется через 180 дней.

О материале

ИнфраструктураРуководствоЗадача: установитьУровень: практикаSaleor + Next.js

Последнее обновление:

Обсуждение

Комментарии проходят подтверждение адреса и проверку редакцией.