Back to Blog
Tutorial12 min read2026-07-03

How to Deploy Medusa.js E-commerce to Production

Medusa is a modular e-commerce backend that needs PostgreSQL, Redis, and a worker process in production. This guide covers the full architecture, config, migrations, and a clean cloud deployment.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

Medusa.js is a headless, modular commerce platform — a Node.js backend plus an optional admin and storefront. Unlike a simple web app, a production Medusa deployment has several moving parts: PostgreSQL, Redis, and a separation between the server and worker processes. Get the architecture right and it scales cleanly.

The production architecture

Medusa v2 introduces an important deployment concept: server mode vs worker mode. The same codebase runs in two roles:

  • Server — handles HTTP API requests (storefront and admin).
  • Worker — processes background jobs, scheduled tasks, and subscribers (order events, emails, etc.).

Running both responsibilities in one process works for small loads but doesn't scale. In production you run at least one of each.

ComponentRole
Medusa serverAPI requests
Medusa workerBackground jobs, scheduled tasks
PostgreSQLPrimary data store
RedisEvent bus, cache, workflow engine

Configure the mode

In medusa-config.ts, set the mode from an environment variable so the same image can run as either role:

module.exports = defineConfig({
  projectConfig: {
    databaseUrl: process.env.DATABASE_URL,
    redisUrl: process.env.REDIS_URL,
    workerMode: process.env.MEDUSA_WORKER_MODE as 'shared' | 'worker' | 'server',
    http: {
      storeCors: process.env.STORE_CORS,
      adminCors: process.env.ADMIN_CORS,
      authCors: process.env.AUTH_CORS,
      jwtSecret: process.env.JWT_SECRET,
      cookieSecret: process.env.COOKIE_SECRET,
    },
  },
});

Deploy two services from the same image: one with MEDUSA_WORKER_MODE=server, one with MEDUSA_WORKER_MODE=worker. For a minimal start, shared runs both in one process.

Redis modules

In production you should wire Medusa's event bus, cache, and workflow engine to Redis rather than the in-memory defaults (which don't survive restarts and can't coordinate across processes):

modules: [
  {
    resolve: '@medusajs/medusa/event-bus-redis',
    options: { redisUrl: process.env.REDIS_URL },
  },
  {
    resolve: '@medusajs/medusa/workflow-engine-redis',
    options: { redis: { url: process.env.REDIS_URL } },
  },
],

This is what lets the server and worker processes coordinate reliably.

Building and running

Medusa compiles to a .medusa/server directory. Build it in your image:

FROM node:20-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-slim
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app ./
EXPOSE 9000
CMD ["npm", "run", "start"]

Migrations and admin setup

Run migrations as a deploy step before the new code serves traffic:

npx medusa db:migrate

Create your first admin user once:

npx medusa user --email admin@example.com --password supersecret

Secrets and CORS

Medusa needs JWT_SECRET and COOKIE_SECRET set to strong random values, plus correct CORS settings so your storefront and admin can talk to the API:

JWT_SECRET=...
COOKIE_SECRET=...
STORE_CORS=https://shop.example.com
ADMIN_CORS=https://admin.example.com

Misconfigured CORS is the #1 reason a freshly deployed storefront can't reach the backend.

Deploying on PandaStack

  1. 1Create a PostgreSQL database and a Redis instance — connection URLs are injected as env vars.
  2. 2Connect your repo as a container app (the server, MEDUSA_WORKER_MODE=server).
  3. 3Deploy a second container app from the same repo as the worker (MEDUSA_WORKER_MODE=worker).
  4. 4Set JWT_SECRET, COOKIE_SECRET, and CORS vars in the dashboard.
  5. 5Add npx medusa db:migrate as a release command and push.

Builds run in rootless BuildKit; you get automatic SSL, live logs, rollbacks, and deploy history. Add scheduled tasks via cronjobs if you have report or sync jobs.

Common pitfalls

  • Running only shared mode at scale — background jobs compete with API traffic; split server and worker.
  • In-memory event bus — events are lost on restart and don't cross processes; use Redis.
  • Bad CORS — the storefront/admin can't reach the API.
  • Migrations on boot across replicas — run them once as a release step.
  • Weak or missing secrets — sessions and auth break.

References

  • Medusa deployment overview: https://docs.medusajs.com/learn/deployment
  • Medusa worker mode: https://docs.medusajs.com/learn/production/worker-mode
  • Medusa configuration reference: https://docs.medusajs.com/learn/configurations/medusa-config
  • Medusa Redis modules: https://docs.medusajs.com/resources/architectural-modules/event/redis
  • Medusa CLI (migrations): https://docs.medusajs.com/resources/medusa-cli

---

PandaStack's free tier covers container apps plus a managed PostgreSQL and Redis with connection URLs auto-injected — everything Medusa's server and worker need. Run two services from one repo and push: https://dashboard.pandastack.io

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Tutorial

Browse all Tutorial articles →

See also