Deployment
Storefront to Vercel, Medusa to Railway, and a release pipeline nobody has to remember to run.
Both apps deploy from main. Nothing is uploaded by hand.
Storefront → Vercel
A standard Next.js deployment of apps/storefront. Push to main and it builds.
Environment, set in the Vercel project:
| Variable | Notes |
|---|---|
NEXT_PUBLIC_MEDUSA_BACKEND_URL | The Railway URL. Unset, the shop serves the static catalogue |
NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY | pk_..., printed by the seed script |
STRIPE_SECRET_KEY | Server-only. Absent, /api/checkout answers 503 with the WhatsApp message |
STRIPE_WEBHOOK_SECRET | For POST /api/stripe/webhook |
NEXT_PUBLIC_SITE_URL | Canonical URL; only a fallback when the request carries no Host header |
Live at glemor-storefront.cenvora.dev.
Medusa → Railway
Postgres and Redis are Railway services beside it. apps/medusa/railway.json pins the
build and the start command:
{
"build": { "builder": "NIXPACKS", "buildCommand": "npm ci && npm run build" },
"deploy": {
"startCommand": "cd .medusa/server && npm ci --omit=dev && npx medusa db:migrate && npm run start",
"healthcheckPath": "/health",
"healthcheckTimeout": 300,
"restartPolicyType": "ON_FAILURE",
"restartPolicyMaxRetries": 3
}
}medusa build emits a self-contained server into .medusa/server, which is why both the
install and the start happen from inside that directory, and why migrations run on every
boot rather than as a separate manual step.
Environment that actually decides behaviour:
| Variable | Why it matters |
|---|---|
DATABASE_URL | Postgres |
DATABASE_SSL | Opt in with "true". Railway's private network (*.railway.internal) has no TLS; a public Postgres URL does. Defaulting to off means a missing var fails loudly instead of silently disabling certificate checks |
REDIS_URL | Present, Medusa uses the Redis cache, event bus and workflow engine. Absent, it falls back to in-memory — right on a laptop, wrong in production, which is why the modules are gated on the variable |
MEDUSA_WORKER_MODE | shared by default: API and background worker in one process, correct for a single Railway service. Split into server + worker when jobs start competing with request latency |
DISABLE_MEDUSA_ADMIN | "true" on a worker-only service. Note the spelling — Medusa's own convention. The reversed spelling silently never matches |
STORE_CORS, ADMIN_CORS, AUTH_CORS | Required, non-null-asserted in the config |
JWT_SECRET, COOKIE_SECRET |
Live at glemor-admin.cenvora.dev,
admin at /app.
Releases
.github/workflows/release.yml runs on every push to main, with
concurrency: release so two runs cannot race to compute "the next version" from the same
tag. It checks out with fetch-depth: 0 — release.mjs walks history back to the last tag,
and a shallow clone would silently produce a wrong version.
The workflow runs the three gates as three separate steps, then the release tooling
self-test, then node scripts/release.mjs --tag, which:
- computes the next semver from the Conventional Commits since the last tag;
- writes
CHANGELOG.md— every commit, developer voice; - writes
apps/docs/content/changelog/<version>.mdx— theRelease-Note:trailers, plain language; - bumps
package.json, commits, tags, and publishes the GitHub release.
Both changelogs are generated. Neither is ever hand-edited — see Changelog.
Docs
This site is a plain Next.js app (npm run docs:build) and can deploy anywhere Next runs.
It has no runtime dependencies: every page is built from files in the repository.