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

# Analog SSR Better Auth

This Analog SSR app uses Better Auth for email/password authentication on Postgres, with all the essentials for a real deployment — Valkey-cached profile reads, a NATS-driven activity feed that indexes into Meilisearch, and presigned object-storage avatar uploads.

### Available Environments

- [AI Agent](https://app.zerops.io/recipes/recipe.md?environment=ai-agent)
- [Remote (CDE)](https://app.zerops.io/recipes/recipe.md?environment=remote-cde)
- [Local](https://app.zerops.io/recipes/recipe.md?environment=local)
- [Stage](https://app.zerops.io/recipes/recipe.md?environment=stage)
- **Small Production** ← current
- [Highly-available Production](https://app.zerops.io/recipes/recipe.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** (ubuntu/nodejs@22) :3000
  - Containers: 2 × Shared Core, 0.75 GB RAM, 1 GB Disk
  - Repository: [zerops-recipe-apps/analog-ssr-better-auth-app](https://github.com/zerops-recipe-apps/analog-ssr-better-auth-app)
- **db** (postgresql:single@18) :5432, :6432
  - Containers: 1 × Shared Core, 0.50 GB RAM, 1 GB Disk
- **cache** (valkey:single@7.2) :6379, :6380
  - Containers: 1 × Shared Core, 0.50 GB RAM, 1 GB Disk
- **broker** (nats:single@2.12) :4222, :8222
  - Containers: 1 × Shared Core, 0.50 GB RAM, 1 GB Disk
- **storage** (object-storage)
  - Containers: 1 × Shared Core, 0.00 GB RAM, 0 GB Disk
- **search** (meilisearch:single@1.20) :7700
  - Containers: 1 × Shared Core, 0.50 GB RAM, 1 GB Disk

**Total Resources:** 8 containers, 3.50 GB RAM, 6 GB Disk

### One-Click Deploy (Import YAML)

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

```yaml
#zeropsPreprocessor=on

# Small-production project for moderate live traffic.
# BETTER_AUTH_SECRET is a project-level value generated once at
# import, so every app container verifies sessions against the
# identical secret without needing its own copy.
project:
  name: analog-ssr-better-auth-small-prod
  envVariables:
    BETTER_AUTH_SECRET: <@generateRandomString(<32>)>

services:
  # Two app containers — capacity for concurrent traffic, and a
  # single container crash no longer drops requests. Both
  # replicas share one NATS queue group for the signup consumer,
  # so scaling to two adds capacity without double-processing an
  # event. Bump verticalAutoscaling.maxRam if monitoring shows
  # containers nearing the RAM ceiling.
  - hostname: app
    type: nodejs@22
    zeropsSetup: prod
    buildFromGit: https://github.com/zerops-recipe-apps/analog-ssr-better-auth-app
    enableSubdomainAccess: true
    minContainers: 2
    verticalAutoscaling:
      minRam: 0.5
      minFreeRamGB: 0.25

  # Set higher priority on db, cache, broker, storage, and search than the app,
  # so every dependency is ready before the app's first init command or request
  # runs.
  #
  # Single-instance Postgres, used by Better Auth to store accounts, sessions,
  # and profile fields — restoring from a snapshot means downtime, the cost
  # trade-off at this scale. Bump verticalAutoscaling.minRam if production write
  # volume pushes query latency up.
  - hostname: db
    type: postgresql:single@18
    priority: 10
    profile: oltp-staging
    verticalAutoscaling:
      minRam: 0.25
      minFreeRamGB: 0.25

  # Single-instance Valkey — a failure means a brief cache-cold
  # window while Postgres absorbs the extra profile-read load.
  # Bump verticalAutoscaling.minRam if the dashboard's hit/miss
  # counters shift toward more misses under production traffic.
  - hostname: cache
    type: valkey:single@7.2
    priority: 10
    profile: staging
    verticalAutoscaling:
      minRam: 0.25
      minFreeRamGB: 0.25

  # Single-instance NATS — a restart briefly interrupts the
  # signup/profile-update queue group, and core pub/sub means
  # nothing published during that gap is queued for replay. Bump
  # verticalAutoscaling.minRam if production event volume grows
  # past the current floor.
  - hostname: broker
    type: nats:single@2.12
    priority: 10
    verticalAutoscaling:
      minRam: 0.25
      minFreeRamGB: 0.25

  # Object storage for avatar uploads — reads always go through
  # a signed, time-limited URL regardless of this service's
  # bucket policy. Bump objectStorageSize when production
  # uploads outgrow the current quota.
  - hostname: storage
    type: object-storage
    priority: 10
    objectStorageSize: 1
    objectStoragePolicy: private

  # Single-node Meilisearch, used by the app for full-text search over user name
  # and bio fields — a restart briefly pauses indexing of new activity while
  # the service returns; existing search results are unaffected. Bump
  # verticalAutoscaling.minRam if production search latency grows with the
  # indexed dataset.
  - hostname: search
    type: meilisearch:single@1.20
    priority: 10
    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/recipe.md?environment=small-production&guideFlow=template) or [Integrate Flow](https://app.zerops.io/recipes/recipe.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/analog-ssr-better-auth-app](https://github.com/zerops-recipe-apps/analog-ssr-better-auth-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 repository root. It tells Zerops how to build, deploy and run your app. This one declares 2 setups (`dev`, `prod`), runs `initCommands` at boot (migrations, seed), and ships readiness + health checks.

```yaml
zerops:
  - setup: prod
    build:
      base: nodejs@22
      buildCommands:
        - npm ci
        - node scripts/bundle-migrate.mjs
        - node scripts/bundle-seed.mjs
        - NODE_OPTIONS=--max-old-space-size=1500 npm run build
      deployFiles:
        - dist/analog
        - migrate.cjs
        - seed.cjs
      cache:
        - node_modules
    deploy:
      readinessCheck:
        httpGet:
          port: 3000
          path: /
    run:
      base: nodejs@22
      initCommands:
        - zsc execOnce ${appVersionId} --retryUntilSuccessful -- node migrate.cjs
        - zsc execOnce seed-v1 --retryUntilSuccessful -- node seed.cjs
      ports:
        - port: 3000
          httpSupport: true
      envVariables:
        NODE_ENV: production
        DB_HOST: ${db_hostname}
        DB_PORT: ${db_port}
        DB_NAME: ${db_dbName}
        DB_USER: ${db_user}
        DB_PASSWORD: ${db_password}
        APP_URL: ${zeropsSubdomain}
        CACHE_URL: ${cache_connectionString}
        NATS_HOST: ${broker_hostname}
        NATS_PORT: ${broker_port}
        NATS_USER: ${broker_user}
        NATS_PASS: ${broker_password}
        SEARCH_URL: ${search_connectionString}
        SEARCH_MASTER_KEY: ${search_masterKey}
        S3_ENDPOINT: ${storage_apiUrl}
        S3_REGION: us-east-1
        S3_BUCKET: ${storage_bucketName}
        S3_ACCESS_KEY_ID: ${storage_accessKeyId}
        S3_SECRET_ACCESS_KEY: ${storage_secretAccessKey}
      start: node dist/analog/server/index.mjs
      healthCheck:
        httpGet:
          port: 3000
          path: /

  - setup: dev
    build:
      base: nodejs@22
      os: ubuntu
      buildCommands:
        - npm install
      deployFiles: ./
      cache:
        - node_modules
    run:
      base: nodejs@22
      os: ubuntu
      initCommands:
        - zsc execOnce ${appVersionId} --retryUntilSuccessful -- node scripts/migrate.mjs
        - zsc execOnce seed-v1 --retryUntilSuccessful -- node scripts/seed.mjs
      ports:
        - port: 5173
          httpSupport: true
      envVariables:
        NODE_ENV: development
        DB_HOST: ${db_hostname}
        DB_PORT: ${db_port}
        DB_NAME: ${db_dbName}
        DB_USER: ${db_user}
        DB_PASSWORD: ${db_password}
        APP_URL: ${zeropsSubdomain}
        CACHE_URL: ${cache_connectionString}
        NATS_HOST: ${broker_hostname}
        NATS_PORT: ${broker_port}
        NATS_USER: ${broker_user}
        NATS_PASS: ${broker_password}
        SEARCH_URL: ${search_connectionString}
        SEARCH_MASTER_KEY: ${search_masterKey}
        S3_ENDPOINT: ${storage_apiUrl}
        S3_REGION: us-east-1
        S3_BUCKET: ${storage_bucketName}
        S3_ACCESS_KEY_ID: ${storage_accessKeyId}
        S3_SECRET_ACCESS_KEY: ${storage_secretAccessKey}
      start: zsc noop --silent
```

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

**Valkey**

- [Configuration & Access](https://docs.zerops.io/valkey/overview#service-configuration)

**NATS**

- [Configuration](https://docs.zerops.io/nats/overview#service-configuration)
- [Monitoring](https://docs.zerops.io/nats/overview#health-monitoring)
- [Backup & Restore](https://docs.zerops.io/nats/overview#backup-and-recovery)

**Meilisearch**

- [Configuration](https://docs.zerops.io/meilisearch/overview#service-configuration)
- [Access](https://docs.zerops.io/typesense/overview#access-methods)
- [Backup & Restore](https://docs.zerops.io/typesense/overview#backup)

---

## Related Recipes

- [Analog SSR Hello World](https://app.zerops.io/recipes/analog-ssr-hello-world.md)
- [Analog Static Hello World ](https://app.zerops.io/recipes/analog-static-hello-world.md)
- [Topcoat Hello World](https://app.zerops.io/recipes/topcoat-hello-world.md)
- [TanStack Start Hello World](https://app.zerops.io/recipes/tanstack-start-hello-world-app.md)

