Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions apps/docs/content/guides/backup.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Zerops auto-backs up databases and storage daily (00:00-01:00 UTC) with X25519 e
## Supported Services
MariaDB, PostgreSQL, Qdrant, Elasticsearch, NATS, Meilisearch, Shared Storage.

**ClickHouse**: not on the standard auto-backup path — back it up with the native `BACKUP ALL ...` SQL command (super user), stored as `.tar.gz`.

**Not supported**: Runtimes, Object Storage (use S3 lifecycle policies), Valkey/KeyDB (in-memory).

## Schedule Options
Expand Down Expand Up @@ -40,17 +42,15 @@ End-to-end with X25519 per-project keys. Decrypted only on download.
7 days after service or project deletion before backups are permanently removed.

## Backup Formats by Service
| Service | Format |
|---------|--------|
| PostgreSQL | pg_dump |
| MariaDB | mysqldump |
| Elasticsearch | elasticdump (.gz) |
| Meilisearch | .dump |
| Qdrant | .snapshot |
| NATS | .tar.gz |
| Shared Storage | filesystem archive |
| Service | Tool → Format |
|---------|---------------|
| PostgreSQL | `pg_dump` → `.zip` (per-schema custom-format `-Fc` dumps) |
| MariaDB | `mariabackup` → `.xb.gz` (xbstream + gzip) — **not** `mysqldump` (that is the manual-export tool, a different operation) |
| Elasticsearch | elasticdump → `.gz` |
| Meilisearch | `.dump` |
| Qdrant | `.snapshot` |
| NATS | `.tar.gz` |
| Shared Storage | tar → `.tar.gz` |

## Gotchas
1. **Object Storage has no Zerops backup**: Use S3 lifecycle policies or external backup
2. **Valkey/KeyDB not backed up**: In-memory data — use persistence or application-level backup
3. **Backup storage is shared**: All services in a project share the backup quota
- Valkey/KeyDB are not backed up → rely on service persistence or application-level backup.
12 changes: 2 additions & 10 deletions apps/docs/content/guides/build-cache.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,7 @@ Any change to these zerops.yml fields invalidates **both layers**:

---

## Build Container Specs

CPU 1-5 cores, RAM 8 GB fixed, Disk 1-100 GB, Timeout 60 min. User `zerops` with **sudo**. Default OS: **Alpine** (use `apt-get` with `os: ubuntu`).

---

## Common Pitfalls

1. **Cascade invalidation**: Changing `prepareCommands` wipes build-layer cache too (e.g., adding `sqlite` to prepare also clears cached `node_modules`)
2. **`cache: false` is misleading**: Only clears `/build/source` cache. Globally installed packages (Go modules, pip packages) persist in the base layer
3. **No-clobber restore**: If source repo contains a file also in cache, **source wins** -- the cached version is silently skipped (logged but does not fail)
4. **Lock file caching**: Cache lock files (`package-lock.json`, `composer.lock`) alongside dependency directories for consistent installs
1. **No-clobber restore**: If source repo contains a file also in cache, **source wins** -- the cached version is silently skipped (logged but does not fail)
2. **Lock file caching**: Cache lock files (`package-lock.json`, `composer.lock`) alongside dependency directories for consistent installs
12 changes: 6 additions & 6 deletions apps/docs/content/guides/cdn.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,13 @@ DNS TTL: 30 seconds. Geo-steering routes to nearest node. EU Prague is fallback
Wildcard must be at end. Use `$` suffix for exact file match.

### Purge via zsc

Signature: `zsc cdn purge <domain> [path]` — the **domain is required first**; the path/pattern is the optional second arg (defaults to `*`). Works for **Static-Mode CDN only** — Object-Storage CDN content is purged via the REST API, not `zsc`. Run from a container whose CDN domain is active.
```bash
zsc cdn purge /* # Purge all cached content
zsc cdn purge /images/* # Purge directory
zsc cdn purge /style.css$ # Purge exact file
zsc cdn purge example.com # Purge all cached content for the domain
zsc cdn purge example.com "/images/*" # Purge a directory
zsc cdn purge example.com "/style.css$" # Purge exact file
```

## Gotchas
1. **30-day fixed TTL**: Cannot be changed — `Cache-Control: max-age=3600` has no effect on CDN
2. **No wildcard domains on static CDN**: `*.domain.com` is not supported
3. **Purge wildcards at end only**: `/images/*.jpg` is invalid — use `/images/*`
1. **CDN URLs are project-scoped env vars**: `${storageCdnUrl}`, `${staticCdnUrl}`, `${apiCdnUrl}` are referenced directly with no hostname prefix (unlike service vars like `${storage_apiUrl}`)
19 changes: 3 additions & 16 deletions apps/docs/content/guides/choose-cache.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,11 @@ description: "**Use Valkey.** KeyDB development has stalled and is effectively d

| Need | Choice | Why |
|------|--------|-----|
| **Any caching need** | **Valkey** (default) | Active development, full HA, Redis-compatible |
| **Any caching need** | **Valkey** (default) | Active development, optional HA, Redis-compatible |
| Legacy KeyDB apps | KeyDB | Only if migrating existing KeyDB deployment |

## Valkey (Default Choice)

- Redis-compatible drop-in replacement
- HA: 3 nodes (1 master + 2 replicas) with automatic failover
- Ports: 6379 (non-TLS), 6380 (TLS), 7000 (read replica non-TLS), 7001 (read replica TLS)
- Connection: `redis://${user}:${password}@${hostname}:6379`
- HA detail: Ports 6379/6380 on replicas forward traffic to current master (Zerops-specific, not native Valkey)

## KeyDB (Deprecated)

- Development activity has slowed significantly
- Port: 6379
- **Do not use for new projects**

## Gotchas
1. **HA replication is async**: Brief data loss possible during master failover
2. **Port forwarding is Zerops-specific**: Replicas forward 6379/6380 to master — this is not standard Redis/Valkey behavior
3. **Read replicas use different ports**: 7000/7001 for direct replica reads
- Version: use `valkey@7.2` — the platform rejects `valkey@8` at import (`serviceStackTypeNotFound`)
- Connection: `redis://${hostname}:6379` — Valkey runs **unauthenticated** on Zerops; there are NO `user`/`password` env vars. Do **not** template `${cache_user}`/`${cache_password}` — they don't exist and produce a broken DSN
24 changes: 5 additions & 19 deletions apps/docs/content/guides/choose-database.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,24 @@ title: "Choosing a Database on Zerops"
description: "**Use PostgreSQL** for everything unless you have a specific reason not to. It's the best-supported database on Zerops with full HA, read replicas, and pgBouncer."
---

**Use PostgreSQL** for everything unless you have a specific reason not to. It's the best-supported database on Zerops with full HA, read replicas, and pgBouncer.
**Use PostgreSQL** for everything unless you have a specific reason not to the best-supported database on Zerops, with optional HA, read replicas, and pgBouncer. Default mode is **NON_HA** (single node); HA is opt-in and immutable after creation.

## Decision Matrix

| Need | Choice | Why |
|------|--------|-----|
| **General-purpose** | **PostgreSQL** (default) | Full HA, read replicas, pgBouncer, best Zerops support |
| **General-purpose** | **PostgreSQL** (default) | Optional HA, read replicas, pgBouncer, best Zerops support |
| MySQL compatibility | MariaDB | MaxScale routing, async replication |
| Analytics / OLAP | ClickHouse | Columnar storage, ReplicatedMergeTree, 4 protocol ports |

## PostgreSQL (Default Choice)

- HA: 3 nodes (1 primary + 2 replicas)
- Ports: 5432 (primary), 5433 (read replicas), 6432 (external TLS via pgBouncer)
- Connection: `postgresql://${user}:${password}@${hostname}:5432/${db}`
- Read scaling: Use port 5433 for read-heavy workloads
- Connection: the generated `${connectionString}` is `postgresql://${user}:${password}@${hostname}:5432` (no database path). Append `/${dbName}` yourself if your driver needs one — the db-name var is `dbName` (default `db`)

## MariaDB

- HA: MaxScale routing with async replication
- Port: 3306
- Connection: `mysql://${user}:${password}@${hostname}:3306/${db}`
- Use when: Application requires MySQL wire protocol
- Connection: the generated `${connectionString}` is `mysql://${user}:${password}@${hostname}:3306` (no database path); append `/${dbName}` if your driver needs one

## ClickHouse

- HA: 3 data nodes, replication factor 3
- Ports: 9000 (native), 8123 (HTTP), 9004 (MySQL), 9005 (PostgreSQL)
- Requires `ReplicatedMergeTree` engine in HA mode
- Use when: Analytics, time-series, OLAP workloads

## Gotchas
1. **HA mode is immutable**: Cannot switch HA/NON_HA after creation — delete and recreate
2. **No internal TLS**: Use `http://hostname:port` internally — VPN provides encryption
3. **PostgreSQL URI scheme**: Some libraries need `postgres://` not `postgresql://` — create a custom env var
- HA: replicated databases use a `Replicated(...)` engine `ON CLUSTER`; tables use a `Replicated*MergeTree` engine (without `ON CLUSTER`)
37 changes: 0 additions & 37 deletions apps/docs/content/guides/choose-queue.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,40 +14,3 @@ description: "**Use NATS** for most cases (simple, fast, JetStream persistence).
| Lightweight pub/sub | NATS — core | Low overhead, 8MB default messages, fire-and-forget |
| Durable queues, replay, at-least-once | NATS — JetStream | Persistent streams, durable consumers, ack/redeliver |
| Event sourcing / audit logs | Kafka | Indefinite topic retention, strong ordering |

## NATS (Default Choice)

NATS exposes **two distinct messaging shapes**. Pick ONE per recipe and write yaml comments / KB content describing only that shape — mixing them confuses porters about what the recipe actually does.

- **Core pub/sub + queue groups**: `nc.subscribe('subject', { queue: 'workers' })`. No persistence; queue groups load-balance delivery across replicas; lost messages stay lost. HA story: surviving cluster nodes keep delivering, no consumer position to restore. Use when fan-out + load balance + at-most-once is enough.
- **JetStream streams + durable consumers**: opens an explicit stream via `JetStreamManager`, subscribes durably via `js.subscribe(...)`. Persistent message store; replay on reconnect; ack/redeliver. HA story: cluster replicates stream state, acked-but-unprocessed messages survive node loss. Use when at-least-once + replay + persistence are required.

**Authoring rule**: a recipe's yaml comments and KB bullets should reflect the shape the code actually uses. If the worker only calls `nc.subscribe()` with a queue group and never opens a stream, do not invoke JetStream language at HA tiers — the recipe has no stream to replicate. If the worker opens a JetStream stream, the JetStream HA story is the relevant one.

- Ports: 4222 (client), 8222 (HTTP monitoring)
- Auth: user `zerops` + auto-generated password
- **Connection** — two supported patterns, pick ONE:
- **Separate env vars** (recommended, works with every NATS client library): pass `servers: ${hostname}:${port}` plus `user: ${user}, pass: ${password}` as client-side connect options. The servers list stays credential-free.
- **Opaque connection string**: pass `${connectionString}` directly as the servers option — the platform builds a correctly-formatted URL with embedded auth that the NATS server expects.
- JetStream capability: enabled by default (`JET_STREAM_ENABLED=1`); recipes opt in by writing JetStream client code. Setting `JET_STREAM_ENABLED=0` hard-disables the capability across the project.
- Storage: Up to 40GB memory + 250GB file store
- Max message: 8MB default, 64MB max (`MAX_PAYLOAD`)
- Health check: `GET /healthz` on port 8222
- **Config changes require restart** (no hot-reload)

## Kafka

- Port: 9092 (SASL PLAIN auth)
- Auth: `user` + `password` env vars (auto-generated)
- Bootstrap: `${hostname}:9092`
- HA: 3 brokers, 6 partitions, replication factor 3
- Storage: Up to 40GB RAM + 250GB persistent
- Topic retention: **Indefinite** (no time or size limits)
- Schema Registry: Port 8081 (if enabled)

## Gotchas
1. **NATS config changes need restart**: No hot-reload — changing env vars requires service restart
2. **Kafka single-node has no replication**: 1 broker = 3 partitions but zero redundancy
3. **NATS JetStream HA sync interval**: 1-minute sync across nodes — brief data lag possible. Applies only to recipes that actually open JetStream streams; core pub/sub recipes are unaffected.
4. **Kafka SASL only**: No anonymous connections — always use the generated credentials
5. **NATS authorization violation from a hand-composed URL**: do not build a `nats://user:pass@host:4222` URL from the separate env vars. Most NATS client libraries will parse the embedded credentials AND separately attempt SASL with the same values, producing a double-auth that the server rejects with `Authorization Violation` on the first CONNECT frame (symptom: startup crash, no successful subscription). Use either the separate env vars passed as connect options (credential-free servers list) or the opaque `${connectionString}` the platform builds for you — both patterns in the Connection section above avoid the double-auth path.
24 changes: 5 additions & 19 deletions apps/docs/content/guides/choose-runtime-base.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,22 @@ title: "Choosing a Runtime Base on Zerops"
description: "**Use Alpine** as the default base for all services. Use Ubuntu only when you need system packages not available in Alpine. Use Docker only for pre-built images."
---

**Use Alpine** as the default base for all services. Use Ubuntu only when you need system packages not available in Alpine. Use Docker only for pre-built images.
**Use Alpine** as the default base for all services. Switch to Ubuntu only for **glibc** needs (musl incompatibility): CGO-enabled Go, glibc-built Python/C-extension wheels, or the **Deno** runtime (no Alpine build). Needing a package is NOT itself a reason — both bases install packages (`sudo apk add` / `sudo apt-get install`). Use Docker only for pre-built images.

## Decision Matrix

| Need | Choice | Why |
|------|--------|-----|
| **Any standard app** | **Alpine** (default) | ~5MB, fast, secure, sufficient for 95% of apps |
| System packages (apt) | Ubuntu | Full Debian ecosystem, ~100MB |
| glibc / CGO / C-extensions / Deno | Ubuntu | musl-incompatible binaries; Deno has no Alpine build (~100MB) |
| Pre-built Docker images | Docker | VM-based, bring your own image |
| CGO / native libs | Ubuntu | Better glibc compatibility than Alpine's musl |

## Alpine (Default)

- Size: ~5MB base
- Package manager: `apk add`
- Best for: All runtimes (Node.js, Python, Go, Rust, Java, PHP, etc.)
- Zerops uses Alpine as default base for all managed runtimes

## Ubuntu

- Size: ~100MB base
- Package manager: `apt-get install`
- Version: 24.04 LTS
- Use when: You need packages not available in Alpine, or need glibc (not musl)
- Package manager: `sudo apt-get update && sudo apt-get install -y <pkg>` (sudo required)
- Version: 24.04 LTS (22.04 also available)
- Use when: you need glibc (musl incompatibility) — CGO-linked Go, glibc-built C-extensions, or the Deno runtime (no Alpine build). Needing a package is NOT a reason — both bases install packages
- Example: Go apps with CGO, Python packages with C extensions that don't compile on musl

## Docker
Expand All @@ -37,9 +29,3 @@ description: "**Use Alpine** as the default base for all services. Use Ubuntu on
- Disk: Can only increase, never decrease without recreation
- Build phase runs in containers (not VMs)
- **Always use specific version tags** — `:latest` is cached and won't re-pull

## Gotchas
1. **Alpine uses musl**: Some C libraries may not compile — use Ubuntu if you hit musl issues
2. **Docker is VM-based**: Vertical scaling restarts the VM — expect brief downtime
3. **Docker `:latest` is cached**: Zerops won't re-pull — always use specific tags like `myapp:1.2.3`
4. **Docker requires host networking**: Without `--network=host`, the container can't receive traffic
22 changes: 2 additions & 20 deletions apps/docs/content/guides/choose-search.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,37 +16,19 @@ description: "**Use Meilisearch** for simple full-text search. Use **Elasticsear

## Meilisearch (Default for Simple Search)

- Single-node only (no clustering)
- Port: 7700
- API keys: `masterKey` (admin), `defaultSearchKey` (frontend-safe), `defaultAdminKey` (backend)
- Production mode by default (no search preview dashboard)

## Elasticsearch (Advanced / HA)

- Cluster support with multiple nodes
- Port: 9200 (HTTP only)
- Auth: `elastic` user with auto-generated password
- Plugins via `PLUGINS` env var (comma-separated)
- JVM heap: `HEAP_PERCENT` env var (default 50%)
- Min RAM: 0.25 GB
- Plugins via `PLUGINS` (set in `envSecrets`, comma-separated)
- JVM heap: `HEAP_PERCENT` (in `envSecrets`, default 50%)

## Typesense (Fast Autocomplete)

- HA: 3-node Raft consensus
- API key via `apiKey` env var (immutable after generation)
- CORS enabled by default
- Recovery time: up to 1 minute during failover (503/500 auto-resolves)
- Data persisted at `/var/lib/typesense`

## Qdrant (Vector Search)

- Ports: 6333 (HTTP), 6334 (gRPC)
- API keys: `apiKey` (full access), `readOnlyApiKey` (search only)
- HA: 3 nodes with `automaticClusterReplication=true` by default
- **Internal access only** — no public access available

## Gotchas
1. **Meilisearch has no HA**: Single-node only — for HA full-text search, use Elasticsearch or Typesense
2. **Qdrant is internal-only**: Cannot be exposed publicly — access via your runtime service
3. **Typesense API key is immutable**: Cannot change `apiKey` after service creation
4. **Elasticsearch plugins require restart**: Changing `PLUGINS` env var needs service restart
11 changes: 5 additions & 6 deletions apps/docs/content/guides/ci-cd.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: zeropsio/actions@main
- uses: zeropsio/actions@v1.0.2
with:
access-token: ${{ secrets.ZEROPS_TOKEN }}
service-id: <service-id>
service-id: ${{ secrets.ZEROPS_SERVICE_ID }}
```

- `access-token`: From Settings → Access Token Management
- `service-id`: From service URL or three-dot menu → Copy Service ID
- The compact `zeropsio/actions` wrapper exposes only `access-token`/`service-id` — it **cannot pass `--setup`**. For a multi-setup `zerops.yaml`, install zcli and run `zcli push --service-id "${{ secrets.ZEROPS_SERVICE_ID }}" --setup <name>` instead

## GitLab Integration (Webhook)

Expand All @@ -40,15 +41,13 @@ jobs:
4. Choose trigger: **New tag** (optional regex) or **Push to branch**

## Skip Pipeline
Include `ci skip` or `skip ci` in commit message (case-insensitive).
Include `[ci skip]` or `[skip ci]` (with the square brackets) in the commit message (case-insensitive).

## Disconnect
Service detail → Build, Deploy, Run → Stop automatic build trigger.

## Gotchas
1. **Full repo access required**: Webhook integration needs full access to create/manage webhooks
2. **`ci skip` in commit message**: Prevents pipeline trigger — useful for docs-only changes
3. **Service ID not obvious**: Find it in service URL or three-dot menu → Copy Service ID
1. **External/CI deploys leave ZCP unaware**: a webhook or CI `zcli push` does not record the deploy in ZCP local state — the service stays at `deployState=never-deployed`. Bridge it with `zerops_workflow action="record-deploy" targetService="<hostname>"` when you return to the ZCP develop flow

## GitLab CI

Expand Down
Loading
Loading