Self-Hosting Medusa.js with Docker Compose

What Is Medusa.js?

Medusa is an open-source, headless e-commerce platform built on Node.js. It provides a modular backend with REST and GraphQL APIs, a built-in admin dashboard, and a Next.js storefront starter. Medusa replaces Shopify, WooCommerce, and BigCommerce with a developer-friendly, self-hosted solution that gives you full control over your store’s data and checkout flow. It’s MIT-licensed and free to use commercially.

Prerequisites

  • A Linux server (Ubuntu 22.04+ recommended)
  • Docker and Docker Compose installed (guide)
  • Node.js 20+ (for building the Medusa project)
  • 4 GB of RAM minimum
  • 20 GB of free disk space
  • A domain name (for the storefront and admin panel)

Docker Compose Configuration

Medusa does not publish pre-built Docker images — you build from your project source. The stack requires Medusa (backend API + admin), PostgreSQL, and Redis.

First, create a Medusa project:

npx create-medusa-app@latest my-store
cd my-store

Create a Dockerfile in your project root:

FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
RUN npx medusa build

FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/.medusa ./.medusa
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
COPY --from=builder /app/medusa-config.ts ./
EXPOSE 9000
CMD ["npx", "medusa", "start"]

Create a docker-compose.yml file:

services:
  medusa:
    build:
      context: .
      dockerfile: Dockerfile
    restart: unless-stopped
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started
    ports:
      - "9000:9000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgres://medusa:${POSTGRES_PASSWORD}@postgres:5432/medusa
      - REDIS_URL=redis://redis:6379
      - JWT_SECRET=${JWT_SECRET}
      - COOKIE_SECRET=${COOKIE_SECRET}
      - STORE_CORS=${STORE_CORS:-http://localhost:8000}
      - ADMIN_CORS=${ADMIN_CORS:-http://localhost:9000}
      - AUTH_CORS=${AUTH_CORS:-http://localhost:9000}
      - PORT=9000
    volumes:
      - medusa-uploads:/app/uploads
    networks:
      - medusa

  postgres:
    image: postgres:16.6-alpine
    restart: unless-stopped
    environment:
      - POSTGRES_DB=medusa
      - POSTGRES_USER=medusa
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
    volumes:
      - postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U medusa"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - medusa

  redis:
    image: redis:7.4.2-alpine
    restart: unless-stopped
    command: redis-server --appendonly yes
    volumes:
      - redis-data:/data
    networks:
      - medusa

volumes:
  postgres-data:
  redis-data:
  medusa-uploads:

networks:
  medusa:
    driver: bridge

Create a .env file alongside:

# PostgreSQL password — use a strong random value
POSTGRES_PASSWORD=CHANGE_ME_GENERATE_RANDOM

# Medusa secrets — generate with: openssl rand -hex 32
JWT_SECRET=CHANGE_ME_GENERATE_RANDOM
COOKIE_SECRET=CHANGE_ME_GENERATE_RANDOM

# CORS origins — set to your storefront and admin domains
STORE_CORS=https://store.example.com
ADMIN_CORS=https://admin.example.com
AUTH_CORS=https://admin.example.com

Run database migrations and start:

docker compose up -d
docker compose exec medusa npx medusa db:migrate

Create your admin user:

docker compose exec medusa npx medusa user -e [email protected] -p your-password

Initial Setup

  1. Open http://your-server:9000/app to access the admin dashboard
  2. Log in with the admin credentials you created
  3. Configure your store under Settings:
    • Store details: Name, currency, region
    • Regions: Define shipping zones and tax rates
    • Payment providers: Configure Stripe, PayPal, or manual payment
    • Shipping options: Define methods and rates per region
  4. Add your first products under Products → Add Product
Access PointURL
Admin dashboardhttp://your-server:9000/app
REST APIhttp://your-server:9000/store (storefront)
Admin APIhttp://your-server:9000/admin

Configuration

Payment Providers

Medusa uses a plugin system for payment processing. Install plugins in your project:

# Stripe
npm install @medusajs/payment-stripe

# PayPal
npm install @medusajs/payment-paypal

Configure in medusa-config.ts:

modules: [
  {
    resolve: "@medusajs/payment-stripe",
    options: {
      api_key: process.env.STRIPE_API_KEY,
    },
  },
]

File Storage

By default, Medusa stores uploads locally. For production, configure S3-compatible storage:

npm install @medusajs/file-s3

Worker Mode

For production deployments with high traffic, split Medusa into separate API and worker containers:

  medusa-api:
    # ... same as medusa service above
    environment:
      - MEDUSA_WORKER_MODE=server

  medusa-worker:
    # ... same image and config
    environment:
      - MEDUSA_WORKER_MODE=worker

This separates API request handling from background jobs (order processing, email sending, inventory sync).

Storefront

Medusa is headless — you need a separate frontend. The official Next.js starter:

npx create-medusa-app@latest --with-nextjs-starter

Or build your own using the Medusa JS Client or REST API. Any frontend framework works — Next.js, Nuxt, SvelteKit, Remix, or a static site.

Reverse Proxy

With Nginx Proxy Manager, create two proxy hosts:

  1. Admin/API: admin.example.comlocalhost:9000
  2. Storefront: store.example.comlocalhost:8000 (if using Next.js starter)

Update your CORS settings in .env to match the proxy domains.

For more details, see our Reverse Proxy Setup guide.

Backup

Back up PostgreSQL (contains all products, orders, customers) and the uploads volume:

# Database dump
docker compose exec postgres pg_dump -U medusa medusa > medusa-backup-$(date +%Y%m%d).sql

# Upload files
docker compose cp medusa:/app/uploads ./uploads-backup-$(date +%Y%m%d)/

For a comprehensive backup strategy, see our Backup Strategy guide.

Troubleshooting

Database Connection Refused on Startup

Symptom: Medusa exits with “ECONNREFUSED” to PostgreSQL.

Fix: PostgreSQL takes a few seconds to initialize. The health check in the Compose file handles this, but if issues persist:

docker compose logs postgres
docker compose restart medusa

Admin Dashboard Shows Blank Page

Symptom: http://server:9000/app loads but shows nothing.

Fix: Ensure ADMIN_CORS matches the URL you’re accessing the admin from. If behind a reverse proxy, set it to the proxy domain:

ADMIN_CORS=https://admin.example.com

Products Not Showing in Storefront

Symptom: API returns empty product list.

Fix: Products must be published and assigned to a sales channel. In the admin: Products → [Product] → Publish → Select sales channel.

Migration Errors

Symptom: medusa db:migrate fails with schema errors.

Fix: Ensure you’re running migrations against a fresh database or the correct Medusa version. Drop and recreate if starting fresh:

docker compose exec postgres psql -U medusa -c "DROP DATABASE medusa; CREATE DATABASE medusa;"
docker compose exec medusa npx medusa db:migrate

Build Fails with Node.js Version Error

Symptom: create-medusa-app or npm install fails.

Fix: Medusa v2 requires Node.js 20+. The Next.js storefront does not support Node 25+. Use Node 20 LTS or 22 LTS.

Resource Requirements

  • RAM: 2 GB minimum, 4 GB recommended for the full stack
  • CPU: 2 cores minimum
  • Disk: 5 GB for the application, plus product images and database growth
ComponentRAM Usage
Medusa (API + Admin)~300-500 MB
PostgreSQL~200-500 MB
Redis~50-100 MB
Next.js Storefront~200-400 MB

Verdict

Medusa is the best self-hosted e-commerce platform for developers who want full control. Its headless architecture means you can use any frontend, its module system is genuinely flexible, and the admin dashboard is polished. The tradeoff is that it’s developer-oriented — you need Node.js knowledge to deploy and customize. Non-technical users should look at WooCommerce or PrestaShop for a more traditional experience. For developers building a custom storefront who want a modern, API-first backend they fully own, Medusa is the right choice over Shopify.

Comments