3 MIN READ

Installing Medusa v2: From Zero to a Running Store

The install that works, the prerequisites people miss, and what to do in the first hour after the seed data loads.

BY RAHUL MEHTAUPDATED
Illustration for “Installing Medusa v2: From Zero to a Running Store” — Deployment

Installing Medusa is genuinely one command. The interesting part is the twenty minutes afterwards: what got created, which defaults are wrong for anything real, and what to change before you write a line of your own code.

Prerequisites

RequirementVersionNotes
Node.js20 or 22Odd-numbered releases are not supported
PostgreSQL15+Local, Docker or hosted
GitAny
Redis7+Optional locally, required in production

Confirm Postgres is actually reachable before you start. Roughly half of all failed installs are a database that is installed but not running, or a role without create-database permission.

bash
psql -h localhost -U postgres -c "SELECT version();"

The install

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

You will be asked whether to include the Next.js storefront. Say yes for a first project — having a working storefront to read is worth more than a clean directory.

The installer creates the project, installs dependencies, creates the database, runs migrations, seeds sample data, and prompts for an admin email. When it finishes:

bash
cd my-store
npx medusa develop

Backend on http://localhost:9000, admin dashboard on http://localhost:9000/app, storefront on http://localhost:8000.

If the automatic setup fails

Common enough to be worth writing down. Do it manually:

bash
# 1. Create the database
createdb medusa-store

# 2. Point the app at it
echo 'DATABASE_URL=postgres://postgres:postgres@localhost:5432/medusa-store' >> .env

# 3. Run migrations
npx medusa db:migrate

# 4. Seed sample data
yarn seed

# 5. Create an admin user
npx medusa user -e admin@example.com -p supersecret

That last command is how admin users are created — there is no self-service signup, by design.

What got created

text
my-store/
├── src/
│   ├── admin/          # dashboard extensions
│   ├── api/            # custom API routes
│   ├── jobs/           # scheduled jobs
│   ├── links/          # module links
│   ├── modules/        # your commerce domains
│   ├── scripts/        # one-off scripts, including seed
│   ├── subscribers/    # event handlers
│   └── workflows/      # business processes
├── medusa-config.ts    # configuration and module registration
└── .env

Every directory is convention-based: put a file in the right place and Medusa loads it. The project structure guide covers what belongs where.

The first-hour configuration

The scaffolded config is fine for development and wrong for anything you intend to keep.

Real secrets. The defaults are placeholders:

.envbash
JWT_SECRET=$(openssl rand -base64 32)
COOKIE_SECRET=$(openssl rand -base64 32)

Restrict CORS. The scaffold allows localhost. Before any deployment, set STORE_CORS, ADMIN_CORS and AUTH_CORS to your real origins.

Add Redis. Even locally it is worth it, because it makes your development environment behave like production:

medusa-config.tsts
modules: [
  {
    resolve: "@medusajs/medusa/event-bus-redis",
    options: { redisUrl: process.env.REDIS_URL },
  },
]

Decide about seed data now. The sample catalog is useful for a day and confusing for a month. Either clear it before you start modelling, or keep it deliberately in a separate database.

Verifying the install

Three checks that together prove the stack works:

bash
# Health
curl http://localhost:9000/health

# Store API — needs a publishable key from the admin dashboard
curl http://localhost:9000/store/products \
  -H "x-publishable-api-key: pk_..."

# Admin API — log in first
curl -X POST http://localhost:9000/auth/user/emailpass \
  -H "Content-Type: application/json" \
  -d '{"email":"admin@example.com","password":"supersecret"}'

The publishable key catches people out: Store API requests require one, it is created in the dashboard under Settings, and the storefront reads it from an environment variable.

What to do next

  1. Read the project structure so you know where things go.
  2. Build a small module — a wishlist, a brand — to learn the shape.
  3. Wire Stripe in test mode and complete an order end to end.
  4. Read deploying to production before you get attached to a development-only architecture.

Docker Postgres in thirty seconds

If you would rather not install Postgres locally:

bash
docker run --name medusa-pg \
  -e POSTGRES_USER=medusa \
  -e POSTGRES_PASSWORD=medusa \
  -e POSTGRES_DB=medusa \
  -p 5432:5432 \
  -d postgres:16-alpine

docker run --name medusa-redis -p 6379:6379 -d redis:7-alpine
.envbash
DATABASE_URL=postgres://medusa:medusa@localhost:5432/medusa
REDIS_URL=redis://localhost:6379

Two containers, no system packages, and trivially resettable when you want a clean database. For a full local stack see Dockerising Medusa.

Troubleshooting the first hour

SymptomCauseFix
`ECONNREFUSED 5432`Postgres not runningStart it; verify with `psql`
`permission denied to create database`Role lacks createdbCreate the DB manually, then migrate
Admin loads but API calls 401No admin user`npx medusa user -e … -p …`
Store API returns 400Missing publishable keyCreate one in Settings, set it in the storefront
Storefront shows no productsNo sales channel or no keyLink products to the default channel
Prices missingNo region contextPass `region_id` and request `calculated_price`
`Cannot find module` after buildDependencies not installed in `.medusa/server`Run install inside the build output

The publishable key and the region context account for most of the confusion in a first project — neither produces an error that names the actual problem.

Upgrading later

Keep the upgrade path in mind from day one, because the habits that make it painless are cheap now and expensive to retrofit.

bash
npx medusa-upgrade   # or bump @medusajs/* together, then:
npx medusa db:migrate

Three habits that keep upgrades boring: pin exact versions rather than ranges so upgrades are deliberate; bump every @medusajs/* package together, since mixed minor versions produce module resolution errors that name the wrong thing; and read the release notes for migration steps before running db:migrate, not after.

The architectural habit matters more than any of those. Because modules sit beside core rather than extending it, an upgrade rarely touches your code — which is the whole reason v2 was rebuilt this way.


If you would rather skip the learning curve on a project with a deadline, that is what we do.

Frequently asked questions

What are the system requirements for Medusa v2?

Node.js 20 or 22, PostgreSQL 15 or newer, and Git. Redis is optional in development and required in production. Medusa runs on macOS, Linux and Windows via WSL2.

How do I create an admin user in Medusa?

With the CLI: `npx medusa user -e you@example.com -p yourpassword`. There is no signup form in the dashboard, which is deliberate — admin access is provisioned, not self-served.

Why does create-medusa-app fail on database creation?

Almost always because Postgres is not running, or the connecting role lacks permission to create databases. Verify with `psql -h localhost -U postgres -c "SELECT 1;"`, then create the database manually and run `npx medusa db:migrate` yourself.

Do I need the Next.js storefront?

No — Medusa exposes a Store API you can build against with any framework. For a first project the starter is worth including, because reading a working implementation of cart and checkout teaches the API faster than the reference does.

What is a publishable API key?

A key that scopes Store API requests to specific sales channels. Every storefront request must send it as the `x-publishable-api-key` header. Create it in the dashboard under Settings and put it in your storefront's environment.

How do I remove the seed data?

Re-create the database and run migrations without seeding: `npx medusa db:migrate` on a fresh database. Deleting seeded records individually through the dashboard works but tends to leave regions, sales channels and stock locations behind.

[ Keep reading ]