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.
| Component | Role |
|---|---|
| Medusa server | API requests |
| Medusa worker | Background jobs, scheduled tasks |
| PostgreSQL | Primary data store |
| Redis | Event 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:migrateCreate your first admin user once:
npx medusa user --email admin@example.com --password supersecretSecrets 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.comMisconfigured CORS is the #1 reason a freshly deployed storefront can't reach the backend.
Deploying on PandaStack
- 1Create a PostgreSQL database and a Redis instance — connection URLs are injected as env vars.
- 2Connect your repo as a container app (the server,
MEDUSA_WORKER_MODE=server). - 3Deploy a second container app from the same repo as the worker (
MEDUSA_WORKER_MODE=worker). - 4Set
JWT_SECRET,COOKIE_SECRET, and CORS vars in the dashboard. - 5Add
npx medusa db:migrateas 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
sharedmode 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