Medusa в Docker Compose: готовый файл
Если вы устали упираться в ограничения готовых конструкторов интернет-магазинов и хотите свой фронтенд без компромиссов — Medusa решает именно эту задачу. Это open-source headless-платформа для коммерции на Node.js, где бэкенд отдаёт данные через API, а витрину вы собираете сами на любом фреймворке. Ниже — рабочий docker-compose.yml, который поднимает Medusa вместе с PostgreSQL и Redis на собственном сервере, с пояснениями по каждому шагу.
Содержание
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Что такое Medusa и когда она нужна
Medusa — это бэкенд для e-commerce, а не готовый магазин "из коробки". Он даёт вам: каталог товаров с вариантами, корзину, заказы, оплату, доставку, скидки, мультивалютность и мультирегиональность — всё через REST/GraphQL-подобный API и встроенную админ-панель. А вот витрину (то, что видит покупатель) вы верстаете отдельно — обычно на Next.js, но с равным успехом подойдёт любой фреймворк, который умеет ходить в API.
Это принципиально другой подход, чем у Shopify или коробочных решений вроде PrestaShop: там фронтенд и бэкенд слиты, здесь — разделены. Плюс в том, что дизайн витрины не ограничен темами платформы: вы строите её как обычное веб-приложение. Плата за это — больше работы на старте и необходимость самостоятельно управлять инфраструктурой.
Self-hosted Medusa оправдана, когда:
- нужен полный контроль над UX витрины — нестандартная навигация, кастомные страницы, сложная логика подбора товаров;
- бизнес уже имеет фронтенд-команду или готов её нанять — без разработчика Medusa не запустить, это не no-code;
- важно держать данные о заказах и клиентах на своей инфраструктуре, а не в облаке зарубежного SaaS с непредсказуемой доступностью для российских проектов.
Если нужен магазин за вечер без единой строчки кода — присмотритесь к PrestaShop, это ближе к классической коробке. Medusa — инструмент для тех, кто готов инвестировать в кастомную витрину ради полной свободы над ней.
Требования к серверу
Medusa v2 — это Node.js-приложение, внутри которого крутится админка, API и воркеры для фоновых задач (события, workflow-движок). Плюс отдельно нужны PostgreSQL и Redis. Сама витрина на Next.js, если разворачивается на том же сервере, добавляет ещё один процесс.
Ориентировочная конфигурация (без учёта витрины — только бэкенд Medusa + БД):
| Сценарий | CPU | RAM | Диск |
|---|---|---|---|
| Разработка / тест | 1-2 vCPU | 2 ГБ | 20 ГБ SSD |
| Небольшой магазин, витрина отдельно | 2 vCPU | 4 ГБ | 40 ГБ SSD |
| Бэкенд + витрина на одном сервере | 4 vCPU | 8 ГБ | 60+ ГБ SSD |
На 2 ГБ RAM Medusa стартует и работает, но при сборке образа (medusa build) компиляция админки в память укладывается впритык — на слабых серверах сборку лучше делать отдельно (например, локально или в CI) и деплоить уже готовый образ. Для боевого магазина с постоянной нагрузкой разумно сразу брать сервер от 4 ГБ RAM — PostgreSQL и воркеры Medusa при пиках заказов (акции, распродажи) съедают память рывками.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверПодготовка проекта и Dockerfile
У Medusa нет единого официального образа на Docker Hub, который подошёл бы всем — приложение собирается из вашего проекта, потому что в него встраиваются кастомные модули, плагины оплаты и конфигурация. Сначала создаём проект локально или прямо на сервере:
npx create-medusa-app@latest my-medusa-store
cd my-medusa-store
Мастер спросит про подключение к PostgreSQL — на этом этапе можно указать локальную БД для генерации структуры, а на сервере она будет уже в контейнере. В корне проекта создаём Dockerfile:
FROM node:20-alpine AS base
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npx medusa build
FROM node:20-alpine
WORKDIR /app
COPY --from=base /app/.medusa/server ./
RUN npm ci --omit=dev
EXPOSE 9000
CMD ["npm", "run", "start"]
Двухэтапная сборка (build → рантайм) держит финальный образ компактным: инструменты сборки и dev-зависимости не попадают в продакшн-слой. medusa build собирает и бэкенд, и встроенную админку — на выходе получается каталог .medusa/server, готовый к запуску.
Готовый docker-compose.yml
Собираем три сервиса: PostgreSQL, Redis и саму Medusa, которая использует Redis для очереди событий и workflow-движка (без него часть асинхронных операций — например, отправка вебхуков — будет менее надёжной).
version: "3.8"
services:
postgres:
image: postgres:16-alpine
container_name: medusa_postgres
restart: unless-stopped
environment:
- POSTGRES_USER=medusa
- POSTGRES_PASSWORD=change_me_strong_password
- POSTGRES_DB=medusa
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U medusa"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
container_name: medusa_redis
restart: unless-stopped
command: redis-server --appendonly yes
volumes:
- redisdata:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
medusa:
build: .
container_name: medusa_app
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
ports:
- "127.0.0.1:9000:9000"
environment:
- DATABASE_URL=postgres://medusa:change_me_strong_password@postgres:5432/medusa
- REDIS_URL=redis://redis:6379
- JWT_SECRET=change_me_random_string
- COOKIE_SECRET=change_me_another_random_string
- STORE_CORS=https://shop.example.com
- ADMIN_CORS=https://admin.example.com
- AUTH_CORS=https://shop.example.com,https://admin.example.com
volumes:
pgdata:
redisdata:
Что важно в параметрах:
JWT_SECRETиCOOKIE_SECRET— сгенерируйте случайные строки (openssl rand -hex 32), это ключи подписи сессий и токенов авторизации в админке и API. Совпадение с примером из документации — прямая дыра в безопасности.STORE_CORS/ADMIN_CORS/AUTH_CORS— списки доменов, которым разрешено обращаться к API. Забытый или неверный домен здесь — самая частая причина "магазин не грузит товары" на проде: браузер тихо режет запрос по CORS, а в консоли фронтенда видна ошибка, в логах бэкенда — нет.condition: service_healthyвdepends_on— Medusa при старте сразу пытается подключиться к БД и упадёт, если PostgreSQL ещё не готов принимать соединения; healthcheck решает эту гонку.- Порт 9000 привязан к
127.0.0.1— наружу сервис торчать не должен, доступ только через reverse proxy.
Первый запуск выполняет миграции автоматически при старте (predeploy/migrations run в зависимости от версии) — проверьте package.json вашего проекта, в части шаблонов миграции нужно прогнать вручную:
docker compose up -d --build
docker compose exec medusa npx medusa db:migrate
docker compose exec medusa npx medusa user -e admin@example.com -p change_me
Последняя команда создаёт первого администратора для входа в панель.
Обратный прокси, домен и SSL
API Medusa и встроенная админка работают на одном порту (9000), но по разным путям — /app отдаёт админ-панель, остальное — API для витрины. Один и тот же upstream можно завести под двумя поддоменами через Nginx после выпуска SSL:
server {
listen 443 ssl http2;
server_name admin.example.com api.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
client_max_body_size 20m;
location / {
proxy_pass http://127.0.0.1:9000;
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 60s;
}
}
server {
listen 80;
server_name admin.example.com api.example.com;
return 301 https://$host$request_uri;
}
Публичный API-домен (api.example.com) — тот, что указывается в конфиге витрины как MEDUSA_BACKEND_URL, а admin.example.com можно вообще скрыть за Basic Auth или ограничить по IP на уровне Nginx, если панель нужна только внутренней команде — админка не рассчитана на публичный листинг в поисковиках.
Витрина, файловое хранилище и переменные окружения
Отдельно от бэкенда разворачивается storefront — обычно официальный Next.js-стартер Medusa. Его можно держать на том же сервере отдельным контейнером или развернуть на любом Node-хостинге, который умеет собирать Next.js-приложения — бэкенд об этом ничего не знает, он просто отвечает на API-запросы с любого домена, разрешённого в STORE_CORS.
Для загрузки изображений товаров по умолчанию Medusa пишет файлы на локальный диск контейнера — это неудобно: при пересборке образа без правильно смонтированного volume файлы теряются. Для продакшна ставится плагин @medusajs/file-s3 и подключается S3-совместимое хранилище. Если разворачиваете MinIO на том же сервере, это закрывает вопрос без внешних облачных зависимостей:
npm install @medusajs/file-s3
и в medusa-config.ts добавляется модуль с параметрами доступа к вашему S3/MinIO-бакету (endpoint, access key, secret key, bucket). После этого загруженные изображения хранятся вне контейнера приложения и переживают любые пересборки и деплои.
Бэкап и обновление
Критичные данные — это PostgreSQL (заказы, товары, клиенты) и файловое хранилище (если оно не вынесено в S3/MinIO — тогда бэкапится отдельно). Redis в базовой конфигурации Medusa хранит очереди и кэш, потеря его данных не критична для целостности бизнес-данных, но обрывает текущие фоновые задачи.
Бэкап БД:
docker compose exec postgres pg_dump -U medusa medusa | gzip > medusa-backup-$(date +%F).sql.gz
Вынесите это в cron с ротацией и выгрузкой во внешнее хранилище — на том же сервере, что и рабочая база, бэкап не защищает от отказа диска.
Обновление до новой версии — процесс более чувствительный, чем у приложений с готовым образом, потому что вы пересобираете свой Dockerfile из обновлённого кода проекта:
npm install @medusajs/medusa@latest @medusajs/admin-sdk@latest
docker compose build medusa
docker compose up -d medusa
docker compose exec medusa npx medusa db:migrate
Перед мажорным обновлением обязательно смотрите changelog и делайте свежий бэкап БД — миграции Medusa между значимыми версиями не всегда откатываются одной командой назад.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Можно ли использовать Medusa как обычный коробочный магазин, без своего фронтенда?
Формально да, есть готовые Next.js-стартеры, которые запускаются почти без правок. Но это всё равно требует деплоя отдельного Node-приложения и настройки CORS — это не PrestaShop, где всё в одном контейнере.
Нужен ли Redis обязательно, или можно без него?
Технически бэкенд запускается и без Redis (используется in-memory очередь), но это подходит только для разработки — на одном процессе без Redis workflow-движок и события не переживут перезапуск контейнера.
Почему после деплоя админка не грузится, хотя API отвечает?
Чаще всего это ADMIN_CORS без нужного домена, либо админка не попала в сборку — проверьте, что medusa build отработал без ошибок и что вы обращаетесь на путь /app, а не на корень API.
Сколько товаров выдержит один сервер на 4 ГБ RAM?
Ориентировочно несколько десятков тысяч SKU без деградации на чтение — точная цифра зависит от количества вариантов, изображений и частоты обращений к API, замеряйте под свою нагрузку.
Можно ли перенести уже работающий магазин на PrestaShop или другой платформе на Medusa?
Прямого автоматического импорта нет — нужно писать скрипт миграции через API Medusa (товары, категории, клиенты, заказы переносятся отдельными запросами), это отдельный проект, не одна команда.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →