Wasp takes an unusual approach: you describe your app — pages, routes, auth, data entities, server operations — in a declarative .wasp config, and Wasp generates a full React + Node/Express + Prisma application from it. That generated app is what you deploy. This guide covers deploying a Wasp app to production with a managed PostgreSQL database.
How Wasp builds
Wasp compiles your .wasp config plus your code into a standard full-stack app:
- A React client (built with Vite) → static assets.
- A Node/Express server → an API server.
- A Prisma schema → your database layer.
The critical thing to understand: a Wasp app deploys as *two* artifacts — a static client and a Node server — plus a PostgreSQL database. They're separate deployables.
Building the production artifacts
Run the Wasp build:
wasp buildThis produces a .wasp/build/ directory containing:
.wasp/build/web-app/— the client, which you then build to static files..wasp/build/— a Dockerfile for the server.- The Prisma schema and migration setup.
For the client, you run its Vite build:
cd .wasp/build/web-app
npm install && npm run build
# REACT_APP_API_URL must point to your deployed server URLThe client needs to know the server's public URL at build time via REACT_APP_API_URL. This is a key gotcha — the static client bakes in the API URL, so you must know your server's domain before building the client.
Deploying the server
Wasp generates a Dockerfile for the server in .wasp/build/. This is a ready-to-deploy container. Deploy it as a web service. The server reads:
| Env var | Purpose |
|---|---|
DATABASE_URL | PostgreSQL connection (injected by managed DB) |
PORT | listen port (injected) |
JWT_SECRET | auth token signing key |
WASP_WEB_CLIENT_URL | the client's public URL (for CORS) |
WASP_SERVER_URL | the server's own public URL |
Link a managed PostgreSQL instance so DATABASE_URL is injected. Set JWT_SECRET to a stable random value (rotating it invalidates all sessions). Set WASP_WEB_CLIENT_URL to your client's domain so CORS allows the browser app to call the API.
Database migrations
Wasp uses Prisma. Run migrations against the production database as a release step:
npx prisma migrate deployThe generated server build includes the Prisma schema and a migration step. Run prisma migrate deploy once per release, before the new server version takes traffic — never migrate dev (which is interactive and for development only).
Deploying the client
The built client (.wasp/build/web-app/build) is plain static files. Deploy it as a static site to a CDN. It will call the server at the REACT_APP_API_URL you baked in at build time.
The deployment shape
[ Static client (CDN) ] --calls--> [ Node/Express server (container) ]
|
v
[ Managed PostgreSQL ]Three pieces. On PandaStack you deploy the server as a container web service, the client as a static site, and link a managed PostgreSQL database to the server. Custom domains and automatic SSL apply to both the client and server.
The chicken-and-egg of URLs
Because the client bakes in the server URL and the server needs the client URL for CORS, deploy in this order:
- 1Provision the database.
- 2Deploy the server (set
DATABASE_URL,JWT_SECRET; you can setWASP_WEB_CLIENT_URLafter you know the client domain). - 3Note the server's public URL.
- 4Build and deploy the client with
REACT_APP_API_URL= the server URL. - 5Update the server's
WASP_WEB_CLIENT_URLto the client domain and redeploy.
If you assign custom domains up front, you can know both URLs in advance and skip the redeploy dance.
Health checks
The Wasp server exposes routes you can probe; add a simple health operation or use a known lightweight endpoint, and point the platform's readiness probe at the server. The static client needs no health check — the CDN serves files.
Deploying
Connect your repo, run wasp build, then deploy the generated server Dockerfile as a container and the client build as a static site. Link the managed PostgreSQL, set the env vars above, and run prisma migrate deploy. Live logs on the server confirm it connected to the database and bound to the injected port.
wasp build
git push origin mainWasp's tradeoffs
Wasp's declarative model removes a huge amount of boilerplate — auth, CRUD operations, and the client/server wiring are generated. The cost is the slightly involved three-artifact deployment and the build-time URL coupling. Once you've set it up with custom domains, redeploys are routine.
Conclusion
A Wasp app deploys as three pieces: a static React client (with the server URL baked in at build), a Node/Express server container (reading DATABASE_URL and JWT_SECRET), and a managed PostgreSQL. Mind the URL ordering, run prisma migrate deploy as a release step, and use stable secrets.
Try Wasp on PandaStack — deploy the server as a container, the client as a static site, and link a managed PostgreSQL, all on the free tier. Get started at [dashboard.pandastack.io](https://dashboard.pandastack.io).
References
- [Wasp Documentation](https://wasp.sh/docs)
- [Wasp: Deploying (wasp build)](https://wasp.sh/docs/advanced/deployment/overview)
- [Wasp: Manually Deploying](https://wasp.sh/docs/advanced/deployment/manually)
- [Prisma: Deploying Migrations](https://www.prisma.io/docs/orm/prisma-migrate/workflows/deployment)
- [Wasp: Env Variables](https://wasp.sh/docs/project/env-vars)