Deployment
How Eziseller ships to production: Next.js frontend on Vercel, Express backend on Azure App Service, managed PostgreSQL. Audience: new dev running their first deploy or debugging a failed one.
1. Overview
Eziseller is deployed as two independently-shipped artifacts against one shared database. The frontend (Next.js 14) builds and runs on Vercel, which rewrites /api/* to the backend URL. The backend (Express + Prisma) builds on GitHub Actions and is published to an Azure App Service Windows plan running under IIS via iisnode. The database is a managed PostgreSQL instance reachable from both. There is no shared infrastructure beyond the DB — the two halves can be deployed, rolled back, or scaled independently.
2. Architecture
The frontend is public-facing; the backend is only hit via Vercel's rewrite and by third-party webhooks (Razorpay, Meta). Azure runs a single slot (Production) — there is no staging slot configured.
3. Build processes
Frontend (Vercel):
npm ciat repo root,next buildfrom package.json:L6.NEXT_PUBLIC_*vars are inlined at build time; changing them requires a redeploy./api/:path*rewrite target is read fromNEXT_PUBLIC_API_URLat runtime (next.config.js:L10-L17).
Backend (GitHub Actions → Azure):
npm ciinbackend/, thennpx prisma generate+tsc -p tsconfig.json+ copy ofassets/intodist/(backend/package.json:L7).- The built
backend/folder (includingweb.config,dist/,node_modules) is uploaded viaazure/webapps-deploy@v3(.github/workflows/backend-deploy.yml:L61-L74). - On Azure,
iisnodereads backend/web.config and routes all non-static requests todist/server.js.
4. Deploy flow
4.1 Backend (master → Azure)
cialways runs (PR + push).deployonly runs onpushtomasterorworkflow_dispatch.- PRs get type-check only — no preview env is provisioned for the backend.
4.2 Frontend (any push → Vercel)
Vercel's GitHub integration triggers on every push. PRs get a preview URL; pushes to master promote to the production domain. No custom workflow file — configuration lives entirely in the Vercel dashboard.
4.3 Database migrations
There is no automatic prisma migrate deploy step in either workflow. Migrations are applied manually before merging the PR that depends on them. See Gotchas.
5. Environments & env var split
| Surface | Where it lives | Examples |
|---|---|---|
| Frontend build-time | Vercel project env | NEXT_PUBLIC_API_URL, NEXT_PUBLIC_MAINTENANCE_MODE |
| Backend runtime | Azure App Service → Configuration → Application settings | DATABASE_URL, JWT_SECRET, FRONTEND_URL, CLOUDINARY_*, WHATSAPP_*, INSTAGRAM_*, RAZORPAY_*, MAINTENANCE_MODE |
| CI secrets | GitHub repo → Settings → Secrets | AZUREAPPSERVICE_PUBLISHPROFILE_A7031B9CEA0D413CA0F1D46F237500CB |
Full env surface: .env.example. Anything prefixed NEXT_PUBLIC_ is embedded in the JS bundle and public — never put secrets there.
6. Key files
- .github/workflows/backend-deploy.yml:L1-L75 — the real backend pipeline (path-filtered to
backend/**). - .github/workflows/testcode_Eziseller.yml:L1-L57 — legacy workflow for the
testCodebranch; same Azure app, no path filter,npm install(notnpm ci). Do not remove without confirming no one pushes totestCode. - backend/web.config:L1-L33 — IIS/iisnode rewrite rules; without this Azure returns 404 for everything.
- backend/package.json:L7-L13 —
build,azure:build,azure:startscripts. Thepostinstallhook runsprisma generateso Azure's Oryx build can still produce a working client if it ever runsnpm installat startup. - next.config.js:L10-L17 — the
/api/*rewrite that couples FE to BE URL. - backend/server.ts:L70-L90 — maintenance-mode gate on the backend.
- src/middleware.ts:L1-L20 — maintenance-mode gate on the frontend (uses
NEXT_PUBLIC_MAINTENANCE_MODE).
7. Env vars & config
| Var | Required | Purpose | What breaks |
|---|---|---|---|
DATABASE_URL | yes (BE) | Postgres connection string | Backend crashes on boot |
JWT_SECRET | yes (BE) | Signs access + refresh tokens | All auth fails; existing tokens invalidated on change |
FRONTEND_URL | yes (BE) | CORS allowlist | Browser requests blocked by CORS |
NEXT_PUBLIC_API_URL | yes (FE build) | Target of /api/* rewrite | Frontend talks to localhost:3001 in prod |
MAINTENANCE_MODE | no (BE) | "true" returns 503 on all /api/* | Health endpoint reports MAINTENANCE |
NEXT_PUBLIC_MAINTENANCE_MODE | no (FE) | Frontend maintenance page | Users still see the app while API is down |
NODE_ENV | yes (BE) | production in Azure | Affects logging, cookie flags, error verbosity |
8. Gotchas & troubleshooting
- Azure 404 on every route after deploy → Cause:
web.configmissing from the deployed artifact (e.g. changed build script dropped it) → Fix: confirmbackend/web.configis at the package root uploaded to Azure.iisnodeneeds it to route todist/server.js. - Cold starts: Azure App Service can idle the worker; the first request after a quiet period takes 5-15s while
iisnodebootsdist/server.jsand Prisma warms its pool. Third-party webhooks that don't retry (rare) can miss this. - Migrations do not auto-run. The deploy workflow has no
prisma migrate deploystep. If you ship code that references a new column before running the migration, the backend boots but every query touching that table throws. Runnpm run db:migrate(orprisma migrate deploy) against the prod DB before merging the PR. - In-memory refresh-token store (system-overview): refresh tokens live in a
Setin backend memory. Every Azure restart (deploy, crash, config change) forces all logged-in users to re-login. Do not scale the backend horizontally without moving this to Redis/Postgres — different instances will reject each other's refresh tokens. node-cronschedulers double-fire under horizontal scale. The backend currently assumes exactly one instance. Scaling out without a cron lock will send duplicate subscription charges, duplicate emails, etc. See cron-and-jobs.md.- Two workflows, one app. Both
backend-deploy.yml(frommaster) andtestcode_Eziseller.yml(fromtestCode) publish to the sameEzisellerAzure app, Production slot. A push totestCodewill overwrite prod. TreattestCodeas effectively disabled. - Maintenance mode is split. Setting
MAINTENANCE_MODE=trueon Azure returns 503 from the API, but the frontend keeps serving pages and users see a broken app. FlipNEXT_PUBLIC_MAINTENANCE_MODE=trueon Vercel and redeploy in tandem. NEXT_PUBLIC_*changes require a rebuild. They are inlined at build time; flipping them in the Vercel dashboard without redeploying has no effect.- CORS ==
FRONTEND_URL. If Vercel assigns a new production domain (custom domain change) andFRONTEND_URLon Azure is not updated, all browser calls die with CORS errors while server-side calls keep working — easy to miss in smoke tests.
9. Extension points
- Preview envs: Vercel already provisions per-PR frontend previews. Point them at a dedicated staging backend (separate Azure slot + separate DB) via a preview-scoped
NEXT_PUBLIC_API_URL. - Blue-green on Azure: add a
Stagingdeployment slot to theEzisellerapp, deploy there, swap on success. The publish profile in GH secrets would need replacing with a slot-specific one. - Auto-migrations: add a
prisma migrate deploystep tobackend-deploy.ymlbeforeazure/webapps-deploy, using a CI-onlyDATABASE_URLsecret. Only safe once migrations are reliably additive. - Horizontal scale: requires moving refresh tokens to a shared store and adding a cron leader-lock (advisory lock in Postgres is cheapest) before raising the Azure instance count above 1.
10. Related docs
- cron-and-jobs.md — scheduler constraints that block horizontal scaling.
- ../01-core/auth/authentication.md — why refresh-token restarts log everyone out.
- ../00-architecture/system-overview.md — whole-platform map.