**Tags:** "Hello World" Examples · Bun · Python

# Zerops showcase

Production-ready image processing pipeline showcasing distributed architecture on [Zerops](https://zerops.io) — a [Bun](https://bun.sh) + [React](https://react.dev) frontend with real-time WebSocket updates and live architecture visualization, a [Python](https://python.org) worker for async image processing via NATS, backed by PostgreSQL, Valkey, and S3-compatible object storage.

### Available Environments

- [AI Agent](https://app.zerops.io/recipes/zerops-showcase.md?environment=ai-agent)
- [Remote (CDE)](https://app.zerops.io/recipes/zerops-showcase.md?environment=remote-cde)
- [Local](https://app.zerops.io/recipes/zerops-showcase.md?environment=local)
- [Stage](https://app.zerops.io/recipes/zerops-showcase.md?environment=stage)
- **Small Production** ← current
- [Highly-available Production](https://app.zerops.io/recipes/zerops-showcase.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/bun@1.2.2) :3000
  - Containers: 2 × Shared Core, 0.75 GB RAM, 1 GB Disk
  - Repository: [zerops-recipe-apps/showcase-recipe-app](https://github.com/zerops-recipe-apps/showcase-recipe-app)
- **worker** (ubuntu/python@3.12)
  - Containers: 2 × Shared Core, 0.75 GB RAM, 1 GB Disk
  - Repository: [zerops-recipe-apps/showcase-recipe-worker](https://github.com/zerops-recipe-apps/showcase-recipe-worker)
- **db** (postgresql:single@17) :5432, :6432
  - Containers: 1 × Shared Core, 1.25 GB RAM, 1 GB Disk
- **redis** (valkey:single@7.2) :6379, :6380
  - Containers: 1 × Shared Core, 0.56 GB RAM, 1 GB Disk
- **queue** (nats:single@2.12) :4222, :8222
  - Containers: 1 × Shared Core, 0.38 GB RAM, 1 GB Disk
- **storage** (object-storage)
  - Containers: 1 × Shared Core, 0.00 GB RAM, 0 GB Disk

**Total Resources:** 9 containers, 5.19 GB RAM, 7 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.

project:
  name: showcase-recipe-small-prod

services:
  # Production app — Zerops pulls source and zerops.yaml from the 'buildFromGit'
  # repo, using the 'prod' setup to build optimized artifacts and deploy.
  # Subdomain access provides a public HTTPS URL; replace with a custom domain
  # for production traffic.
  # minContainers: 2 ensures zero-downtime deploys and load distribution.
  - hostname: app
    type: bun@1.2
    zeropsSetup: prod
    buildFromGit: https://github.com/zerops-recipe-apps/showcase-recipe-app
    enableSubdomainAccess: true
    envSecrets:
      CORE_MODE: serious
    minContainers: 2
    verticalAutoscaling:
      minRam: 0.5
      minFreeRamGB: 0.25

  # Production worker — processes images from the NATS queue.
  # Multiple containers distribute processing load across workers.
  - hostname: worker
    type: python@3.12
    zeropsSetup: prod
    buildFromGit: https://github.com/zerops-recipe-apps/showcase-recipe-worker
    minContainers: 2
    verticalAutoscaling:
      minRam: 0.5
      minFreeRamGB: 0.25

  # PostgreSQL single-node — automatic encrypted backups are on by default.
  # For higher traffic, consider HA mode for replicated durability.
  # Priority 10 starts data services before app containers,
  # preventing connection errors on first deploy.
  - hostname: db
    type: postgresql:single@17
    profile: oltp-production
    priority: 10

  # Valkey (Redis-compatible) for event caching and active job tracking.
  - hostname: redis
    type: valkey:single@7.2
    priority: 10

  # NATS message queue — decouples the app from the worker. Upload events
  # flow through NATS so workers can scale independently.
  - hostname: queue
    type: nats:single@2.12
    priority: 10

  # S3-compatible object storage for images,
  # thumbnails, and resized variants.
  - hostname: storage
    type: object-storage
    objectStorageSize: 5
    objectStoragePolicy: public-read
    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/zerops-showcase.md?environment=small-production&guideFlow=template) or [Integrate Flow](https://app.zerops.io/recipes/zerops-showcase.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/showcase-recipe-app](https://github.com/zerops-recipe-apps/showcase-recipe-app)
- [zerops-recipe-apps/showcase-recipe-worker](https://github.com/zerops-recipe-apps/showcase-recipe-worker)

### 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 application configuration file you place at the root of your repository, it tells Zerops how to build, deploy and run your application.

```yaml
zerops:
  # Production setup — build React frontend + bundle
  # Bun backend into optimized artifacts.
  # Bun's bundler inlines all deps, no node_modules
  # needed at runtime.
  - setup: prod
    build:
      base: bun@1.2

      # BUN_INSTALL redirects Bun's global package cache
      # into the project tree so Zerops can cache it
      # between builds (~/.bun is outside build scope).
      envVariables:
        BUN_INSTALL: ./.bun

      buildCommands:
        # --frozen-lockfile validates bun.lock for
        # reproducible production builds
        - bun install --frozen-lockfile
        # Build React frontend (Vite + Tailwind) — output lands in frontend/dist
        - cd frontend && bun install --frozen-lockfile && bun run build && cd ..
        # Bundle backend — Bun inlines all deps
        # (hono, postgres, ioredis, nats, aws-sdk)
        - bun build src/index.ts --outfile dist/index.js --target bun

      deployFiles:
        # Bundled backend + compiled frontend + DB schema for init
        - ./dist
        - ./frontend/dist
        - ./src/db/schema.sql

      cache:
        - node_modules
        - frontend/node_modules
        - .bun/install/cache  # Must match BUN_INSTALL path above

    # Readiness check: verifies the container is
    # healthy before the balancer routes traffic.
    # /api/health checks PG, Valkey, NATS, and S3.
    deploy:
      readinessCheck:
        httpGet:
          port: 3000
          path: /api/health

    run:
      base: bun@1.2

      ports:
        - port: 3000
          httpSupport: true

      envVariables:
        NODE_ENV: production
        # Database — auto-generated from 'db' service
        DB_HOST: ${db_hostname}
        DB_PORT: ${db_port}
        DB_USER: ${db_user}
        DB_PASS: ${db_password}
        DB_NAME: ${db_dbName}
        # Valkey cache — referenced by 'redis' service hostname
        REDIS_HOST: ${redis_hostname}
        REDIS_PORT: ${redis_port}
        # NATS message queue — referenced by 'queue' service hostname
        NATS_HOST: ${queue_hostname}
        NATS_PORT: ${queue_port}
        NATS_USER: ${queue_user}
        NATS_PASS: ${queue_password}
        # S3-compatible object storage
        S3_ENDPOINT: ${storage_apiUrl}
        S3_ACCESS_KEY: ${storage_accessKeyId}
        S3_SECRET_KEY: ${storage_secretAccessKey}
        S3_BUCKET: ${storage_bucketName}

      start: bun run dist/index.js

  # Development setup — deploy full source for live editing via SSH.
  # Developer SSHs in after deploy and starts
  # the dev server with hot reload.
  - setup: dev
    build:
      base: bun@1.2

      envVariables:
        BUN_INSTALL: ./.bun

      buildCommands:
        # No --frozen-lockfile — lockfile may not
        # exist in fresh forks
        - bun install
        - cd frontend && bun install && cd ..

      deployFiles:
        # Deploy everything — developer runs TypeScript source directly via SSH
        - ./

      cache:
        - node_modules
        - frontend/node_modules
        - .bun/install/cache

    run:
      base: bun@1.2

      ports:
        - port: 3000
          httpSupport: true

      envVariables:
        NODE_ENV: development

      # Container stays idle — developer starts server manually via SSH
      start: zsc noop --silent
```

## How to integrate worker 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 application.

```yaml
zerops:
  # Production setup — deploy source and install dependencies at runtime.
  # Python is interpreted, so no compilation step is needed in buildCommands.
  - setup: prod
    build:
      # Deploy entire source tree — Python runs directly from source
      deploy: ./
      # Ensures requirements.txt is available during run.prepareCommands
      addToRunPrepare: requirements.txt

    run:
      base: python@3.12

      # Install Python packages into the runtime container image.
      # prepareCommands run once per container creation and are cached,
      # unlike initCommands which run on every restart.
      prepareCommands:
        - pip install -r requirements.txt

      envVariables:
        # Database — references auto-generated variables from the 'db' service hostname
        DB_HOST: ${db_hostname}
        DB_PORT: ${db_port}
        DB_USER: ${db_user}
        DB_PASS: ${db_password}
        DB_NAME: ${db_dbName}
        # Valkey cache — referenced by 'redis' service hostname
        REDIS_HOST: ${redis_hostname}
        REDIS_PORT: ${redis_port}
        # NATS connection string — single URI with embedded credentials
        NATS_URL: ${queue_connectionString}
        # S3-compatible object storage — referenced by 'storage' service hostname
        S3_ENDPOINT: ${storage_apiUrl}
        S3_ACCESS_KEY: ${storage_accessKeyId}
        S3_SECRET_KEY: ${storage_secretAccessKey}
        S3_BUCKET: ${storage_bucketName}

      start: python src/main.py

  # Development setup — deploy full source for live editing via SSH.
  # The developer SSHs in and starts the worker manually.
  - setup: dev
    build:
      deploy: ./
      addToRunPrepare: requirements.txt

    run:
      base: python@3.12
      prepareCommands:
        - pip install -r requirements.txt
      # Container stays idle — developer starts worker manually via SSH
      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/zerops-showcase.md?environment=ai-agent)
- [Remote (CDE)](https://app.zerops.io/recipes/zerops-showcase.md?environment=remote-cde)
- [Local](https://app.zerops.io/recipes/zerops-showcase.md?environment=local)
- [Stage](https://app.zerops.io/recipes/zerops-showcase.md?environment=stage)
- [Highly-available Production](https://app.zerops.io/recipes/zerops-showcase.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

**Bun**

- [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)

**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)

---

## Related Recipes

- [Bun Hello World](https://app.zerops.io/recipes/bun-hello-world.md)
- [Python Hello World](https://app.zerops.io/recipes/python-hello-world.md)
- [Go Hello World](https://app.zerops.io/recipes/go-hello-world.md)
- [Java Hello World](https://app.zerops.io/recipes/java-hello-world.md)

