Skip to main content
Version: Next

Zerops

note

Last checked with Wasp 0.26 and Zerops (as of Oct 23, 2026).

This guide depends on external libraries or services, so it may become outdated over time. We do our best to keep it up to date, but make sure to check their documentation for any changes.

Deploy Wasp on Zerops server client database​

This guide shows you how to deploy the server, the client, and provision a database on Zerops.

Zerops builds your Wasp app from source on its servers, so you don't need to run wasp build locally before deploying. You'll define the project topology in an import.yaml and the build/deploy pipeline in a zerops.yaml. Wasp maps to three Zerops services: a static client (app), a Node.js API (api), and PostgreSQL (db).

Prerequisites​

To get started, follow these steps:

  1. Create a Zerops account.
  2. Push your Wasp project to a Git repository.
  3. Generate your initial database migrations locally by running wasp db migrate-dev and commit the migrations/ directory. Zerops needs these migration files in the repo to set up your database.

Create the zerops.yaml​

Create a zerops.yaml in the root of your repository:

zerops.yaml
# yaml-language-server: $schema=https://api.app-prg1.zerops.io/api/rest/public/settings/zerops-yaml-json-schema.json

zerops:
# React client - Wasp + Vite build, then Nginx serves the static files
- setup: prod-client
build:
base: nodejs@24
buildCommands:
- npm install -g @wasp/cli@<wasp-version>
- export PATH="$(npm prefix -g)/bin:$PATH"
- wasp install
- wasp build
- npx vite build
envVariables:
# Vite embeds this at build time - must be the public API URL
REACT_APP_API_URL: ${API_URL}
deployFiles:
# ~ strips the path prefix so index.html is the Nginx docroot
- .wasp/out/web-app/build/~
cache:
- node_modules
run:
base: static

# Node.js API - Wasp build, Prisma generate, bundle, then run the server
- setup: prod-api
build:
base: nodejs@24
os: ubuntu
buildCommands:
- npm install -g @wasp/cli@<wasp-version>
- export PATH="$(npm prefix -g)/bin:$PATH"
- wasp install
- wasp build
- cd .wasp/out/server && npm install
- cd .wasp/out/server && npx prisma generate --schema=../db/schema.prisma
- cd .wasp/out/server && npm run bundle
deployFiles:
# Keep the .wasp/out tree - runtime commands cd into .wasp/out/server
- .wasp/out
cache:
- node_modules
deploy:
readinessCheck:
httpGet:
port: 3001
path: /up
run:
base: nodejs@24
os: ubuntu
initCommands:
# Runs once per new app version (key includes ${appVersionId})
- zsc execOnce ${appVersionId}-migrate --retryUntilSuccessful -- sh -c 'cd .wasp/out/server && npx prisma migrate deploy --schema=../db/schema.prisma'
ports:
- port: 3001
httpSupport: true
envVariables:
NODE_ENV: production
PORT: 3001
DATABASE_URL: postgresql://${db_user}:${db_password}@${db_hostname}:${db_port}/${db_dbName}
JWT_SECRET: ${APP_SECRET}
WASP_SERVER_URL: ${API_URL}
WASP_WEB_CLIENT_URL: ${APP_URL}
start: sh -c 'cd .wasp/out/server && NODE_ENV=production node --enable-source-maps bundle/server.js'

Replace <wasp-version> with the Wasp CLI version your app uses (for example 0.26).

The prod-client setup runs wasp install && wasp build, then npx vite build. REACT_APP_API_URL must be set at build time - Vite embeds it into the compiled JavaScript. If it's missing, all API calls from the client will fail.

The prod-api setup bundles the server, then on each new deploy version zsc execOnce runs prisma migrate deploy once before the server starts on port 3001. Runtime env vars map from the project value store (set in import.yaml below):

VariableValueNotes
DATABASE_URLpostgresql://${db_user}:...Zerops injects db service credentials automatically
WASP_SERVER_URL${API_URL}Public API URL, including https://
WASP_WEB_CLIENT_URL${APP_URL}Public client URL, including https://
JWT_SECRET${APP_SECRET}At least 32 characters
PORT3001Must match API_URL and the readiness check

Do not self-reference Wasp keys in zerops.yaml (for example ${WASP_SERVER_URL}). Only value-store keys (APP_URL, API_URL, APP_SECRET) and Zerops service keys (${db_password}, ...) resolve.

Create the import.yaml​

import.yaml defines which services exist and the project value store - generic APP_URL and API_URL that zerops.yaml maps into Wasp's env var names. Do not put envVariables on service blocks in import.yaml.

import.yaml
#yamlPreprocessor=on
# yaml-language-server: $schema=https://api.app-prg1.zerops.io/api/rest/public/settings/import-project-yaml-json-schema.json

project:
name: my-wasp-app-small-prod
envVariables:
APP_URL: https://app-${zeropsSubdomainHost}.prg1.zerops.app
API_URL: https://api-${zeropsSubdomainHost}-3001.prg1.zerops.app
envSecrets:
# Generated once at import - requires #yamlPreprocessor=on as the first line
APP_SECRET: <@generateRandomString(<64>)>

services:
- hostname: app
type: static
priority: 5
zeropsSetup: prod-client
buildFromGit: https://github.com/<your-org>/<your-wasp-app>
enableSubdomainAccess: true
minContainers: 2

- hostname: api
type: nodejs@24
priority: 5
zeropsSetup: prod-api
buildFromGit: https://github.com/<your-org>/<your-wasp-app>
enableSubdomainAccess: true
minContainers: 2

- hostname: db
type: postgresql:single@16
profile: oltp-hobby
priority: 10

Replace the following values for your app:

VariableValueExample
<your-org>/<your-wasp-app>Your GitHub repositorymy-org/my-wasp-app
project.nameA unique name for this Zerops projectmy-wasp-app-small-prod

The zeropsSubdomainHost placeholder is filled in by Zerops at import time. zeropsSetup on each service must match a setup: name in zerops.yaml.

Because zerops.yaml maps JWT_SECRET to ${APP_SECRET}, you must supply APP_SECRET in the project value store. Setting JWT_SECRET in the dashboard will not work - runtime envVariables in zerops.yaml take precedence over dashboard secrets. Generate APP_SECRET at import with the preprocessor above, or add it manually in the dashboard (Project → Environment variables → Secrets) as a random string at least 32 characters long:

Using an external auth method?

If your app is using an external authentication method(s) supported by Wasp (such as Google or GitHub), make sure to additionally set the necessary environment variables specifically required by these method(s).

This example uses the Small Production topology (two containers each). For a single-container stage setup, omit minContainers on the app and api services.

Deploy​

  1. Commit zerops.yaml and push your repository.
  2. In the Zerops dashboard, create a project and Import the import.yaml, or run zcli project project-import.
  3. Point buildFromGit at your repository (or connect the GitHub / GitLab integration so later pushes rebuild automatically).
  4. Wait for db, then api, then app to finish building.

Verify your deployment​

  • Client: open the app URL - the SPA loads.
  • API: GET /up on port 3001 responds (this is the readiness check).
  • Auth: if login fails, check that WASP_WEB_CLIENT_URL and WASP_SERVER_URL match the public client and API URLs in the project value store.

Redeploying after changes​

When the app and api services are connected to your Git repository, push to the linked branch:

git push origin main

Zerops starts a new build of each connected service. You can also trigger a rebuild from the dashboard or with zcli service trigger-build.

If you changed the Prisma schema, run wasp db migrate-dev locally first and commit the generated files in migrations/ along with your code. On the next API deploy, zsc execOnce runs prisma migrate deploy once for that new app version before the server starts.

Changing APP_URL or API_URL in the project value store requires a rebuild of the client and a restart of the API.

Official recipe (optional)​

Zerops maintains an official Wasp Hello World recipe that imports a ready-made project with one click. It covers several environments - from AI-assisted development to production:

EnvironmentBest for
AI AgentCoding agents - SSH into dev containers, run wasp start
Remote (CDE)Cloud dev environment over SSH
LocalLaptop + cloud DB via zCLI VPN
StagePre-production / QA
Small ProductionSmall live apps
Highly-available ProductionProduction with HA PostgreSQL

Recipe import.yaml files live in the recipe repo; the demo app provides the reference zerops.yaml. The official recipe's Small Production import.yaml uses the same service topology but points at the demo app repo and does not define APP_SECRET - the demo hardcodes JWT_SECRET in its zerops.yaml instead. AI Agent and Remote environments use appstage as the preview client (not app) and add appdev / apidev containers for live development.

If you start from the recipe and later switch buildFromGit to your own repo, add APP_SECRET to the project value store first - the recipe import.yaml does not include it.

Local development​

The recipe's Local environment deploys client, API, and PostgreSQL on Zerops while you run wasp start on your machine.

zcli vpn up only gives you network access to project hostnames (so db:5432 resolves). Zerops does not inject environment variables over the VPN - you must pass DATABASE_URL yourself.

  1. Install zCLI if you haven't already.
  2. In the Zerops dashboard, open the db service and copy the PostgreSQL user, password, port, and database name from Environment variables.
  3. Connect to the project network, then start Wasp with an explicit URL (do not put production credentials in .env.server):
zcli vpn up
DATABASE_URL="postgresql://<user>:<password>@db:<port>/<database>" wasp start

Use the hostname db (the service hostname), not a public host - that only works while the VPN is up. If hostname resolution fails, try db.zerops as described in the Zerops VPN docs.

Resources​