**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** ← current
- [Stage](https://app.zerops.io/recipes/wasp-hello-world.md?environment=stage)
- [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 — local development environment.
# Cloud SPA + API (prod setups) and database. The developer runs
# `wasp start` locally and connects to the Zerops database via
# `zcli vpn up`.
#
# 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-local
  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
    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=local&guideFlow=template) or [Integrate Flow](https://app.zerops.io/recipes/wasp-hello-world.md?environment=local&guideFlow=integrate)

Both flows are shown below:

## How to take over the Local 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. Create an access token and install zCLI

Before you start, create an access token and install zCLI so your local machine can work with your Zerops account.

- Open Access Token Management in the Zerops Dashboard: https://app.zerops.io/settings/token-management
- Create a new personal access token and copy it immediately.
- Install zCLI:

```bash
npm i -g @zerops/zcli
```

- Log in with your token:

```bash
zcli login <personal-access-token>
```

If you do not use npm, check the zCLI documentation: https://docs.zerops.io/references/cli

![Zerops GUI: Creating a Personal Access Token](https://storage-prg1.zerops.io/4gfos-storage/Screenshot_2026_03_09_at_07_47_20_88e1562866.png)

> [!TIP]
> Save the token in a password manager right away. You will not be able to see it again after leaving the page. You can always create multiple tokens and revoke them independently.

### 2. Start the VPN

To reach private databases or services from your local machine, open a secure tunnel into the project network.

- Run:

```bash
zcli vpn up
```

- Select your project from the list.
- Keep the VPN connection active while working locally.
- Use internal hostnames such as `db`, `redis`, or `api`.

> [!TIP]
> If a short hostname like `db` does not resolve, try `db.zerops`. For internal HTTP services reached through the VPN, use HTTP rather than HTTPS.

### 3. Find your service name

Many commands 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 asks for `<service-name>`.

![Zerops GUI: Locating the Service Name](https://storage-prg1.zerops.io/4gfos-storage/copy1_cd2a6044c8.jpg)

### 4. Download environment variables

Instead of copying values manually, export your service configuration into a local `.env` file.

- Export only user-defined variables:

```bash
zcli project env --service <service-name> --user-only > .env
```

- Remove `--user-only` if you also need system-generated variables.
- Add `--export` if you want the output to include the `export` keyword for shell sourcing.

> [!TIP]
> You can customize the output with the `--template` flag. The default format is `{{.Key}}={{.Value}}`.

### 5. Configure deployment pipeline

Decide how your code should reach the staging environment.

- In the Zerops Dashboard, go to Service Settings > Pipelines & CI/CD Settings.
- Connect your repository.
- Add a trigger so every push to your main branch starts a deployment automatically.
- If you prefer manual control, you can use `zcli push` later instead of Git triggers.

### 6. Develop and test locally

With the VPN active and your `.env` file ready, run your application locally while it connects to Zerops-hosted services.

Example PostgreSQL connection:

```bash
psql -h db -U user -d database
```

Example internal API check:

```bash
curl http://api:3000/health
```

### 7. Push your code changes

When local tests pass, deploy your changes to the staging environment.

If you configured Git triggers:

```bash
git add .
git commit -m "feat: implement new feature"
git push
```

If you prefer pushing directly from your machine:

```bash
zcli push <service-name>
```

### 8. Test your staging application

Verify the deployment before moving toward production.

- Open your application through the provided Zerops subdomain.
- Check runtime logs in the Zerops Dashboard under Service Settings > Runtime Logs.
- Confirm the app starts correctly and there are no deployment or runtime errors.

## 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)
- [Stage](https://app.zerops.io/recipes/wasp-hello-world.md?environment=stage)
- [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)

