**Tags:** "Hello World" Examples · Frameworks Examples · React

# Wasp Hello World

A full-stack [Wasp](https://wasp.sh/) application — React client, Node.js API, and [PostgreSQL](https://www.postgresql.org) — deployed on [Zerops](https://zerops.io). The client ships as static Nginx assets; the API runs on Node.js 24 with Prisma migrations.

### Available Environments

- [AI Agent](https://app.zerops.io/recipes/wasp-hello-world.md?environment=ai-agent)
- [Remote (CDE)](https://app.zerops.io/recipes/wasp-hello-world.md?environment=remote-cde)
- [Local](https://app.zerops.io/recipes/wasp-hello-world.md?environment=local)
- **Stage** ← current
- [Small Production](https://app.zerops.io/recipes/wasp-hello-world.md?environment=small-production)
- [Highly-available Production](https://app.zerops.io/recipes/wasp-hello-world.md?environment=highly-available-production)

### Services in this Environment

**Services:**

- **core** (core:single@2)
  - Containers: 1 × Shared Core, 0.00 GB RAM, 0 GB Disk
- **app** (alpine/static@1.0)
  - Containers: 1 × Shared Core, 0.38 GB RAM, 1 GB Disk
  - Repository: [zerops-recipe-apps/wasp-hello-world-app](https://github.com/zerops-recipe-apps/wasp-hello-world-app)
- **api** (ubuntu/nodejs@24) :3001
  - Containers: 1 × Shared Core, 0.75 GB RAM, 1 GB Disk
  - Repository: [zerops-recipe-apps/wasp-hello-world-app](https://github.com/zerops-recipe-apps/wasp-hello-world-app)
- **db** (postgresql:single@16) :5432, :6432
  - Containers: 1 × Shared Core, 0.38 GB RAM, 1 GB Disk

**Total Resources:** 4 containers, 1.50 GB RAM, 3 GB Disk

### One-Click Deploy (Import YAML)

Use this YAML with `zcli project import` to deploy this environment:

```yaml
#zeropsPreprocessor=on

# Wasp hello world — stage environment.
# Single client + API pair using production setups on minimal
# resources — lighter than small prod (one container each, oltp-hobby DB).
#
# Project vault are a value store — zerops.yaml maps them:
#   REACT_APP_API_URL / WASP_SERVER_URL ← ${API_URL}
#   WASP_WEB_CLIENT_URL                 ← ${APP_URL}
project:
  name: wasp-hello-world-stage
  vault:
    APP_URL: https://app-${zeropsSubdomainHost}.prg1.zerops.app
    API_URL: https://api-${zeropsSubdomainHost}-3001.prg1.zerops.app

services:
  - hostname: app
    type: static
    priority: 5
    zeropsSetup: prod-client
    buildFromGit: https://github.com/zerops-recipe-apps/wasp-hello-world-app
    enableSubdomainAccess: true
    verticalAutoscaling:
      minRam: 0.25
      minFreeRamGB: 0.125

  - hostname: api
    type: nodejs@24
    priority: 5
    zeropsSetup: prod-api
    buildFromGit: https://github.com/zerops-recipe-apps/wasp-hello-world-app
    enableSubdomainAccess: true
    verticalAutoscaling:
      minRam: 0.5
      minFreeRamGB: 0.25

  - hostname: db
    type: postgresql:single@16
    # Hello-world demo — oltp-hobby (~0.5 GB), same as local/small prod.
    profile: oltp-hobby
    priority: 10

```

---

## Next Steps

After deploying one of the environments and getting to know Zerops, you have two paths to choose from:

1. **Template Flow** — Clone our GitHub repositories and use the whole recipe as a template
2. **Integrate Flow** — If you already have an existing application on a similar stack, integrate the recipe setup with your application

Select a flow: [Template Flow](https://app.zerops.io/recipes/wasp-hello-world.md?environment=stage&guideFlow=template) or [Integrate Flow](https://app.zerops.io/recipes/wasp-hello-world.md?environment=stage&guideFlow=integrate)

Both flows are shown below:

## How to take over the Stage environment

### 📦 Clone the template repositories

Fork or clone the following repositories to your local machine or GitHub account:

- [zerops-recipe-apps/wasp-hello-world-app](https://github.com/zerops-recipe-apps/wasp-hello-world-app)
- [zerops-recipe-apps/wasp-hello-world-app](https://github.com/zerops-recipe-apps/wasp-hello-world-app)

### 1. Find your service name

Many commands and configurations need the exact name of your service. You can find it in the Zerops Dashboard.

- Open your project in the Zerops Dashboard.
- In the project overview, find the service you want to manage.
- Use this exact name whenever a command or pipeline configuration asks for `<service-name>`.

<img src="https://storage-prg1.zerops.io/4gfos-storage/copy1_cd2a6044c8.jpg" style="display: block; margin: 0 auto;" alt="Zerops GUI: Locating the Service Name" width="500" />

### 2. Configure deployment pipeline

In Service Settings > Pipelines & CI/CD Settings in the Zerops Dashboard, connect your repository.

For staging, use a trigger on push to your staging branch, such as `main`, `develop`, or `staging`. This deploys every change pushed to that branch automatically.

If you want more control, you can use `zcli push` in your existing CI/CD pipeline instead.

Learn more about pipeline triggers: https://docs.zerops.io/features/pipeline

GitHub integration: https://docs.zerops.io/references/github-integration

GitLab integration: https://docs.zerops.io/references/gitlab-integration

> [!TIP]
> To skip builds for documentation-only changes, add `[ci skip]` or `[skip ci]` to your commit message. The webhook is still delivered, but Zerops will not start a build.

### 3. Deploy to staging

Push to your configured branch to trigger a new staging deployment:

```bash
git add .
git commit -m "feat: add new feature for staging"
git push origin main
```

> [!TIP]
> Open the pipeline detail in the Zerops Dashboard to check the build progress, review logs, and verify that all steps finish successfully. This helps you catch issues early and improve build times.

---

### 🎉 You are good to go!

Your staging deployment is triggered and the core setup is complete.

The following section is optional. You can stop here unless you need to configure public access rules for your staging environment.

---

### 4. Set up HTTP access (Optional)

Configure public access to your staging application in Service Settings > Public Access & Internal Ports in the Zerops Dashboard.

You have two common options:

#### Zerops subdomain

Use the Zerops subdomain for instant access without DNS configuration. This is usually enough for staging and QA testing.

Enable it in the Zerops Dashboard or in `zerops.yaml`:

```yaml
enableSubdomainAccess: true
```

#### Custom domain

Use a custom domain if your staging environment needs a stable public URL.

Add your domain in the Zerops Dashboard and configure your DNS records with the values provided by Zerops.

See the public access documentation for details: https://docs.zerops.io/features/access

> [!TIP]
> For staging environments, the Zerops subdomain is often enough and avoids DNS propagation delays. Save custom domains for production unless staging specifically needs one.

## How to integrate app with Zerops

### 1. Adding `zerops.yaml`

The main application configuration file you place at the root of your repository. It tells Zerops how to build, deploy, and run your Wasp app.

Wasp deploys as **two production services** from one repo:

- `prod-client` — builds the React SPA (`wasp build` + `vite build`) and serves static assets via Nginx.
- `prod-api` — runs the bundled Node.js server from `.wasp/out/server/bundle/` and applies Prisma migrations on deploy.

```yaml
zerops:
  - setup: prod-client
    build:
      base: nodejs@24
      buildCommands:
        - npm install --min-release-age=0
        - npx wasp install
        - node scripts/generate-build-env.cjs
        - npx wasp build
        - npm run build:client
      envVariables:
        RUNTIME_APP_ENV: production
      deployFiles:
        - .wasp/out/web-app/build/~
      cache:
        - node_modules
        - .wasp/out
    run:
      base: static

  - setup: prod-api
    build:
      base: nodejs@24
      os: ubuntu
      buildCommands:
        - NPM_CONFIG_PRODUCTION=false NPM_CONFIG_ENGINE_STRICT=false NPM_CONFIG_AUDIT=false npm install --min-release-age=0
        - npx wasp install
        - npx wasp build
        - cp -R migrations/. .wasp/out/db/migrations/
        - sh scripts/bundle-server.sh
        - node scripts/prepare-api-deploy.cjs
      deployFiles:
        - .zerops/deploy/~
      cache:
        - node_modules
        - .wasp/out
    deploy:
      readinessCheck:
        httpGet:
          port: 3001
          path: /auth/me
        failureTimeout: 300
        retryPeriod: 10
    run:
      base: nodejs@24
      os: ubuntu
      initCommands:
        - zsc execOnce ${appVersionId} --retryUntilSuccessful -- node scripts/migrate-prod.cjs
        - zsc execOnce ${appVersionId} --retryUntilSuccessful -- node scripts/seed-demo-user.cjs
      ports:
        - port: 3001
          httpSupport: true
      envVariables:
        NODE_ENV: production
        PORT: 3001
        DATABASE_URL: postgresql://${db_user}:${db_password}@${db_hostname}:${db_port}/${db_dbName}
        APP_DB_NAME: ${db_dbName}
        JWT_SECRET: wasp-zerops-hello-world-demo-jwt-v1
      start: sh -c 'cd .wasp/out/server && NODE_ENV=production node --enable-source-maps bundle/server.js'

  - setup: dev
    build:
      base: nodejs@24
      os: ubuntu
      buildCommands:
        - npm install --min-release-age=0
        - npx wasp install
      deployFiles: ./
      cache:
        - node_modules
    run:
      base: nodejs@24
      os: ubuntu
      ports:
        - port: 3000
          httpSupport: true
        - port: 3001
          httpSupport: true
      envVariables:
        DATABASE_URL: ${db_connectionString}
      start: zsc noop --silent
```

### 2. Environment variables

Set these at the **project** level in your recipe `import.yaml` as a value store (not Wasp runtime key names). `zerops.yaml` maps them into the app:

| Import variable | Mapped in `zerops.yaml` | Purpose |
|-----------------|-------------------------|---------|
| `API_URL` | `REACT_APP_API_URL` (prod-client build), `WASP_SERVER_URL` (prod-api) | Public API URL (port 3001) |
| `APP_URL` | `WASP_WEB_CLIENT_URL` (prod-api) | Public SPA URL (hostname `app`) |

Keep them at the project level, because both services read them. `envVariables` on a single service in `import.yaml` are kept as well, but only that service sees them.

Demo login (seeded on deploy): **username** `demo`, **password** `demo-zerops1`. The home page requires auth; unauthenticated users are redirected to `/login`.

The API service receives `DATABASE_URL` from `${db_*}` placeholders and runs `migrate-prod.cjs` plus `seed-demo-user.cjs` on deploy. If the API returns 502, verify `APP_URL` and `API_URL` exist on the project (re-import `import.yaml` env if needed).

### 3. Local development

```bash
npm install --min-release-age=0
npx wasp install
npx wasp start db   # optional local Postgres via Wasp
npx wasp db migrate-dev
npx wasp db seed seedDemoUser
npx wasp start      # client :3000, server :3001 — sign in with demo / demo-zerops1
```

Production build locally:

```bash
export DATABASE_URL='postgresql://user:pass@localhost:5432/wasp'
wasp build
REACT_APP_API_URL='http://localhost:3001' npm run build:client
```

## How to integrate api with Zerops

### 1. Adding `zerops.yaml`

The main application configuration file you place at the root of your repository. It tells Zerops how to build, deploy, and run your Wasp app.

Wasp deploys as **two production services** from one repo:

- `prod-client` — builds the React SPA (`wasp build` + `vite build`) and serves static assets via Nginx.
- `prod-api` — runs the bundled Node.js server from `.wasp/out/server/bundle/` and applies Prisma migrations on deploy.

```yaml
zerops:
  - setup: prod-client
    build:
      base: nodejs@24
      buildCommands:
        - npm install --min-release-age=0
        - npx wasp install
        - node scripts/generate-build-env.cjs
        - npx wasp build
        - npm run build:client
      envVariables:
        RUNTIME_APP_ENV: production
      deployFiles:
        - .wasp/out/web-app/build/~
      cache:
        - node_modules
        - .wasp/out
    run:
      base: static

  - setup: prod-api
    build:
      base: nodejs@24
      os: ubuntu
      buildCommands:
        - NPM_CONFIG_PRODUCTION=false NPM_CONFIG_ENGINE_STRICT=false NPM_CONFIG_AUDIT=false npm install --min-release-age=0
        - npx wasp install
        - npx wasp build
        - cp -R migrations/. .wasp/out/db/migrations/
        - sh scripts/bundle-server.sh
        - node scripts/prepare-api-deploy.cjs
      deployFiles:
        - .zerops/deploy/~
      cache:
        - node_modules
        - .wasp/out
    deploy:
      readinessCheck:
        httpGet:
          port: 3001
          path: /auth/me
        failureTimeout: 300
        retryPeriod: 10
    run:
      base: nodejs@24
      os: ubuntu
      initCommands:
        - zsc execOnce ${appVersionId} --retryUntilSuccessful -- node scripts/migrate-prod.cjs
        - zsc execOnce ${appVersionId} --retryUntilSuccessful -- node scripts/seed-demo-user.cjs
      ports:
        - port: 3001
          httpSupport: true
      envVariables:
        NODE_ENV: production
        PORT: 3001
        DATABASE_URL: postgresql://${db_user}:${db_password}@${db_hostname}:${db_port}/${db_dbName}
        APP_DB_NAME: ${db_dbName}
        JWT_SECRET: wasp-zerops-hello-world-demo-jwt-v1
      start: sh -c 'cd .wasp/out/server && NODE_ENV=production node --enable-source-maps bundle/server.js'

  - setup: dev
    build:
      base: nodejs@24
      os: ubuntu
      buildCommands:
        - npm install --min-release-age=0
        - npx wasp install
      deployFiles: ./
      cache:
        - node_modules
    run:
      base: nodejs@24
      os: ubuntu
      ports:
        - port: 3000
          httpSupport: true
        - port: 3001
          httpSupport: true
      envVariables:
        DATABASE_URL: ${db_connectionString}
      start: zsc noop --silent
```

### 2. Environment variables

Set these at the **project** level in your recipe `import.yaml` as a value store (not Wasp runtime key names). `zerops.yaml` maps them into the app:

| Import variable | Mapped in `zerops.yaml` | Purpose |
|-----------------|-------------------------|---------|
| `API_URL` | `REACT_APP_API_URL` (prod-client build), `WASP_SERVER_URL` (prod-api) | Public API URL (port 3001) |
| `APP_URL` | `WASP_WEB_CLIENT_URL` (prod-api) | Public SPA URL (hostname `app`) |

Keep them at the project level, because both services read them. `envVariables` on a single service in `import.yaml` are kept as well, but only that service sees them.

Demo login (seeded on deploy): **username** `demo`, **password** `demo-zerops1`. The home page requires auth; unauthenticated users are redirected to `/login`.

The API service receives `DATABASE_URL` from `${db_*}` placeholders and runs `migrate-prod.cjs` plus `seed-demo-user.cjs` on deploy. If the API returns 502, verify `APP_URL` and `API_URL` exist on the project (re-import `import.yaml` env if needed).

### 3. Local development

```bash
npm install --min-release-age=0
npx wasp install
npx wasp start db   # optional local Postgres via Wasp
npx wasp db migrate-dev
npx wasp db seed seedDemoUser
npx wasp start      # client :3000, server :3001 — sign in with demo / demo-zerops1
```

Production build locally:

```bash
export DATABASE_URL='postgresql://user:pass@localhost:5432/wasp'
wasp build
REACT_APP_API_URL='http://localhost:3001' npm run build:client
```

### 🎯 What's next?

**Deploy other environments** — Ready to scale? Deploy additional environments for different stages of your workflow:

- [AI Agent](https://app.zerops.io/recipes/wasp-hello-world.md?environment=ai-agent)
- [Remote (CDE)](https://app.zerops.io/recipes/wasp-hello-world.md?environment=remote-cde)
- [Local](https://app.zerops.io/recipes/wasp-hello-world.md?environment=local)
- [Small Production](https://app.zerops.io/recipes/wasp-hello-world.md?environment=small-production)
- [Highly-available Production](https://app.zerops.io/recipes/wasp-hello-world.md?environment=highly-available-production)

## Knowledge Base

### Platform Reference

- [Routing & Domains](https://docs.zerops.io/features/access)
- [Scaling](https://docs.zerops.io/features/scaling)
- [Environment Variables](https://docs.zerops.io/features/env-variables)
- [CLI (zcli)](https://docs.zerops.io/references/cli)

### Service Type Reference

**Static**

- [Build & Deploy Pipeline](https://docs.zerops.io/alpine/how-to/build-pipeline)
- [Customize Runtime](https://docs.zerops.io/alpine/how-to/customize-runtime)

**Node.js**

- [Build & Deploy](https://docs.zerops.io/bun/how-to/build-pipeline)
- [Customize Runtime](https://docs.zerops.io/bun/how-to/customize-runtime)

**PostgreSQL**

- [Connect](https://docs.zerops.io/postgresql/how-to/connect)
- [Backup & Restore](https://docs.zerops.io/postgresql/how-to/backup)
- [Manage](https://docs.zerops.io/postgresql/how-to/manage)
- [Scale](https://docs.zerops.io/postgresql/how-to/scale)

---

## Related Recipes

- [TanStack Start Hello World](https://app.zerops.io/recipes/tanstack-start-hello-world-app.md)
- [React (Vite) Static Hello World](https://app.zerops.io/recipes/react-static-hello-world.md)
- [React Router Static Hello World](https://app.zerops.io/recipes/react-router-static-hello-world.md)
- [React Router SSR Hello World](https://app.zerops.io/recipes/react-router-ssr-hello-world.md)

