**Tags:** "Hello World" Examples · Node.js · Nest.js

# Nest.js minimal

A [Nestjs](https://nestjs.com) application connected to [PostgreSQL](https://www.postgresql.org/), running on [Zerops](https://zerops.io) with six ready-made environment configurations — from AI agent and remote development to stage and highly-available production.

### Available Environments

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

### Services in this Environment

**Services:**

- **core** (core@1)
  - Containers: 1 × Shared Core, 0.00 GB RAM, 0 GB Disk
- **app** (nodejs@22) :3000
  - Containers: 2 × Shared Core, 0.75 GB RAM, 1 GB Disk
  - Repository: [zerops-recipe-apps/nestjs-minimal-app](https://github.com/zerops-recipe-apps/nestjs-minimal-app)
- **db** (postgresql@18) :5432, :6432
  - Containers: 1 × Shared Core, 0.50 GB RAM, 1 GB Disk

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

### One-Click Deploy (Import YAML)

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

```yaml
# Small production environment offers a production-ready setup optimized for
# moderate throughput.

# No project-level secrets — NestJS minimal has no encryption or session
# signing that requires a shared key across containers.
project:
  name: nestjs-minimal-small-prod

services:
  # Small production — minContainers: 2 keeps at least two app containers
  # running at all times, spreading load across nodes and maintaining
  # availability during rolling deploys when Zerops replaces containers one at a
  # time. Vertical autoscaling adjusts RAM within bounds; minFreeRamGB leaves
  # headroom for V8 GC spikes.
  - hostname: app
    type: nodejs@22
    zeropsSetup: prod
    buildFromGit: https://github.com/zerops-recipe-apps/nestjs-minimal-app
    enableSubdomainAccess: true
    minContainers: 2
    verticalAutoscaling:
      minRam: 0.5
      minFreeRamGB: 0.25

  # PostgreSQL single-node — sufficient for small production workloads. The
  # database auto-scales disk (never shrinks) and RAM within vertical bounds.
  # For higher durability with automatic failover, deploy with HA mode.
  - hostname: db
    type: postgresql@18
    priority: 10
    mode: NON_HA
    verticalAutoscaling:
      minRam: 0.25
      minFreeRamGB: 0.25


```

---

## 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/nestjs-minimal.md?environment=small-production&guideFlow=template) or [Integrate Flow](https://app.zerops.io/recipes/nestjs-minimal.md?environment=small-production&guideFlow=integrate)

Both flows are shown below:

## How to take over the Small Production environment

### 📦 Clone the template repositories

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

- [zerops-recipe-apps/nestjs-minimal-app](https://github.com/zerops-recipe-apps/nestjs-minimal-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

Go to Service Settings > Pipelines & CI/CD Settings in the Zerops Dashboard and connect your repository.

For production, use a trigger on new tags. This keeps deployments intentional and tied to a specific version. You can also add a regex filter, such as `^v[0-9]+\.[0-9]+\.[0-9]+$`, if you want to allow only semantic version tags.

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

Alternatively, add `zcli push` to your existing CI/CD pipeline if you want full control over when deployments happen.

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

### 3. Deploy to production

Create and push a new Git tag to deploy a specific version of your app:

```bash
git tag -a v1.0.0 -m "Release version 1.0.0"
git push origin v1.0.0
```

> [!TIP]
> Open the pipeline detail in the Zerops Dashboard to check the build progress and verify that all steps finish successfully.

### 4. Configure autoscaling

Review the autoscaling settings for your runtime services and databases in Service Settings > Automatic Scaling Configuration in the Zerops Dashboard.

<img src="https://storage-prg1.zerops.io/4gfos-storage/scaling_ac0880aef5.png" style="display: block; margin: 0 auto;" alt="Zerops GUI: Autoscaling configuration" width="500" />

The most important settings are:

```yaml
verticalAutoscaling:
  minRam: 1
  minFreeRamGB: 0.5
  minFreeRamPercent: 20
```

> [!CAUTION]
> Pay attention to `minFreeRamGB`. This value tells Zerops when to scale RAM vertically. Adjust it based on your app’s real memory needs. RAM scales up immediately, while CPU scales after two consecutive measurements below the threshold.

> [!TIP]
> Run a quick stress test with a tool like hey before real users arrive. This helps you see how your app behaves under load and tune the autoscaling settings.

### 5. Set up your domain

To send real traffic to your app, configure public HTTP access in Service Settings > Public Access & Internal Ports in the Zerops Dashboard.

Add your custom domain and point your DNS records to the Zerops IPs shown in the dashboard:

<img src="https://storage-prg1.zerops.io/4gfos-storage/subdomain_8cafd801e8.jpg" style="display: block; margin: 0 auto;" alt="Zerops GUI: Public access and custom domain" width="500" />

```text
Type   Name          Content          TTL
A      example.com   <zerops-ipv4>    Auto
AAAA   example.com   <project-ipv6>   Auto
```

For wildcard domains, add a CNAME record for SSL validation.

Check the public access documentation: https://docs.zerops.io/features/access

> [!TIP]
> When changing DNS records for production, start with a low TTL value. Make sure SSL certificates are active before you disable the fallback Zerops subdomain.

Once everything works, you can disable the Zerops subdomain so all traffic goes through your custom domain.

---

### 🎉 You are good to go!

Your application is live in production and the core setup is complete.

The following sections are optional. They cover extra production features such as log forwarding, backups, and diagnostic access. You can stop here and come back later when you need them.

---

### 6. Set up log forwarding (Optional)

To send logs to an external service, go to Project Settings > Log Forwarding & Logs Overview in the Zerops Dashboard.

You can forward logs to services like Better Stack, Papertrail, or your own self-hosted solution.

Learn more about log forwarding: https://docs.zerops.io/references/logging

### 7. Configure database backups (Optional)

Manage automated encrypted backups in Service Settings > Backups in the Zerops Dashboard.

By default, backups run daily between 00:00 and 01:00 UTC.

Before a major deployment, create a manual protected backup:

```bash
zcli backup create <db-service> --tags pre-deploy,protected
```

Read the backup documentation for more options: https://docs.zerops.io/features/backup

### 8. Set up diagnostic access (Optional)

Use zCLI and VPN access when you need to inspect or maintain services directly.

For runtime services:

```bash
zcli vpn up
ssh <service-name>.zerops
```

For databases, connect through the VPN to reach the project’s private network, or set up secure direct IP access for your database admin tools.

Check the VPN documentation: https://docs.zerops.io/references/cli/commands#vpn-up

## How to integrate app with Zerops

### 1. Adding `zerops.yaml`
The main configuration file — place at your repository root. It tells Zerops how to build, deploy and run your NestJS app.

```yaml
zerops:
  # Production setup — compile TypeScript, deploy only the
  # compiled output with production dependencies.
  - setup: prod
    build:
      base: nodejs@22

      buildCommands:
        # npm ci locks to package-lock.json for reproducible
        # builds — safer than npm install in CI/CD pipelines.
        - npm ci
        - npm run build
        # Strip devDependencies (TypeScript, testing, linting)
        # after compilation — runtime needs only production deps.
        - npm prune --omit=dev

      deployFiles:
        - ./dist          # compiled JS output
        - ./node_modules  # production dependencies only
        - ./package.json

      # Cache node_modules between builds to skip re-downloading
      # unchanged packages on every build trigger.
      cache:
        - node_modules

    # Readiness check — L7 balancer holds traffic until the
    # new container responds, preventing requests to containers
    # that are still initializing TypeORM or running migrations.
    deploy:
      readinessCheck:
        httpGet:
          port: 3000
          path: /api/health

    run:
      base: nodejs@22

      # Migrations and seeding run once per deploy version —
      # execOnce gates concurrent containers so only one runs
      # the command while others wait for completion.
      # --retryUntilSuccessful handles the brief window when
      # the database port is not yet accepting connections.
      initCommands:
        - zsc execOnce ${appVersionId} --retryUntilSuccessful -- node dist/migrate.js
        - zsc execOnce ${appVersionId} --retryUntilSuccessful -- node dist/seed.js

      ports:
        - port: 3000
          httpSupport: true

      envVariables:
        NODE_ENV: production
        # Cross-service references — resolved at container start
        # from the 'db' service's auto-generated credentials.
        DB_NAME: ${db_dbName}
        DB_HOST: ${db_hostname}
        DB_PORT: ${db_port}
        DB_USER: ${db_user}
        DB_PASS: ${db_password}

      start: node dist/main.js

      # Health check restarts unresponsive containers — catches
      # process hangs and lost database connections that would
      # otherwise serve errors indefinitely.
      healthCheck:
        httpGet:
          port: 3000
          path: /api/health

  # Development setup — deploy full source for interactive
  # development via SSH. Container stays idle until the
  # developer starts the app manually.
  - setup: dev
    build:
      base: nodejs@22

      buildCommands:
        # npm install (not ci) — works without a lock file,
        # giving flexibility during early development.
        - npm install

      # Deploy the entire working directory — source, config,
      # and node_modules with devDependencies included.
      deployFiles: ./

      cache:
        - node_modules

    run:
      base: nodejs@22
      # Ubuntu provides richer tooling (apt, curl, git, vim)
      # for interactive development sessions via SSH.
      os: ubuntu

      # Migrate and seed on every deploy — execOnce ensures
      # each command runs only once per deploy version even
      # when multiple containers exist.
      initCommands:
        - zsc execOnce ${appVersionId} --retryUntilSuccessful -- npx ts-node src/migrate.ts
        - zsc execOnce ${appVersionId} --retryUntilSuccessful -- npx ts-node src/seed.ts

      ports:
        - port: 3000
          httpSupport: true

      envVariables:
        NODE_ENV: development
        # Same cross-service references as prod — only the
        # mode flag (NODE_ENV) differs between setups.
        DB_NAME: ${db_dbName}
        DB_HOST: ${db_hostname}
        DB_PORT: ${db_port}
        DB_USER: ${db_user}
        DB_PASS: ${db_password}

      # Container stays idle — SSH in and run:
      #   npm run start:dev  (NestJS watch mode with hot-reload)
      # or
      #   npm run start      (single run via nest start)
      start: zsc noop --silent
```

### 2. Trust proxy and bind `0.0.0.0`

Zerops terminates SSL at its L7 balancer and forwards requests via reverse proxy. Without proxy trust, Express (under NestJS) misreports `req.ip` and `req.protocol`. Binding `localhost` causes 502 errors because the L7 balancer routes to the container's VXLAN IP.

```typescript
// in src/main.ts
const expressApp = app.getHttpAdapter().getInstance();
expressApp.set('trust proxy', true);
await app.listen(process.env.PORT ?? 3000, '0.0.0.0');
```

### 3. Database configuration via environment variables

NestJS + TypeORM reads database credentials from environment variables injected by Zerops at container start. Never create `.env` files — they shadow OS-level vars with empty values.

```typescript
// in src/app.module.ts — TypeOrmModule.forRoot({...})
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT, 10),
username: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
```

### 🎯 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/nestjs-minimal.md?environment=ai-agent)
- [Remote (CDE)](https://app.zerops.io/recipes/nestjs-minimal.md?environment=remote-cde)
- [Local](https://app.zerops.io/recipes/nestjs-minimal.md?environment=local)
- [Stage](https://app.zerops.io/recipes/nestjs-minimal.md?environment=stage)
- [Highly-available Production](https://app.zerops.io/recipes/nestjs-minimal.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

**Node.js**

- [Build & Deploy](https://docs.zerops.io/nodejs/how-to/build-pipeline)
- [Customize Runtime](https://docs.zerops.io/nodejs/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)

### Application Reference

#### app Knowledge Base

### Gotchas
- **No `.env` files on Zerops** — Zerops injects all environment variables as OS-level env vars. Creating a `.env` file with empty values will shadow the platform-injected values, causing silent connection failures. Delete any `.env` file from your deploy.
- **TypeORM `synchronize: true` in production** — never use `synchronize: true` in production as it auto-modifies the schema on every startup. Use a separate migration script executed via `initCommands` with `zsc execOnce` to ensure safe, one-time schema changes per deploy.
- **NestJS listens on `localhost` by default** — the `app.listen(port)` call without an explicit host binds to `127.0.0.1`. On Zerops, the L7 balancer routes to the container's VXLAN IP, so you must explicitly pass `'0.0.0.0'` as the second argument or the container returns 502.
- **`ts-node` needs devDependencies** — the dev setup uses `npx ts-node` for migration/seed scripts, which requires TypeScript and ts-node in node_modules. The dev `buildCommands` uses `npm install` (not `npm ci --omit=dev`) specifically for this reason.

---

## Related Recipes

- [Nest.js showcase](https://app.zerops.io/recipes/nestjs-showcase.md)
- [Node.js Hello World](https://app.zerops.io/recipes/node-js-hello-world.md)
- [Bun Hello World](https://app.zerops.io/recipes/bun-hello-world.md)
- [Go Hello World](https://app.zerops.io/recipes/go-hello-world.md)

