diff --git a/apps/docs/docusaurus.config.js b/apps/docs/docusaurus.config.js
index 73abfdc0..d660a3da 100644
--- a/apps/docs/docusaurus.config.js
+++ b/apps/docs/docusaurus.config.js
@@ -34,6 +34,28 @@ const config = {
crossorigin: "anonymous",
},
},
+ // Agent/LLM discovery. There is no formal relation for llms.txt --
+ // the convention is the well-known /llms.txt path -- but advertising it in
+ //
costs nothing and is picked up by some crawlers and dev tools.
+ // Per-page markdown alternates are emitted by src/theme/DocItem/Layout.
+ {
+ tagName: "link",
+ attributes: {
+ rel: "alternate",
+ type: "text/plain",
+ href: "https://docs.zerops.io/llms.txt",
+ title: "llms.txt",
+ },
+ },
+ {
+ tagName: "link",
+ attributes: {
+ rel: "alternate",
+ type: "text/plain",
+ href: "https://docs.zerops.io/llms-full.txt",
+ title: "llms-full.txt",
+ },
+ },
],
plugins: [
'./src/plugins/markdown-source',
diff --git a/apps/docs/static/llms-full.txt b/apps/docs/static/llms-full.txt
index b6be83cc..907b0080 100644
--- a/apps/docs/static/llms-full.txt
+++ b/apps/docs/static/llms-full.txt
@@ -3256,54 +3256,62 @@ Manual top-ups give you direct control over your account funding. To add credits
## Automatic Top-ups
-:::caution Beta version
-Available only for customers who have previously made a manual top-up, have a saved payment method, and have provided billing information.
+Automatic top-up ensures your projects continue running without interruption by replenishing your credits when they run low.
+
+:::note Prerequisites
+Automatic top-ups are available once you have made at least one manual top-up, saved a payment method, and provided your [billing information](#billing-information). The saved card is charged off-session, so a valid payment method must stay on file.
:::
-Automatic top-up ensures your projects continue running without interruption by replenishing your credits when they run low.
+To turn them on, navigate to **Credit & Spend Overview** in the Organization section of the main menu, open the automatic top-up settings, and configure the three values described below.
### How Automatic Top-ups Work
-When enabled, Zerops monitors your credit balance and consumption rate to determine when and how much to top up, always respecting your configured limits.
+When enabled, Zerops periodically checks your balance and tops it up by a fixed amount whenever your credit drops below a threshold you choose, up to a limit you set for each calendar month.
Zerops initiates an automatic payment when:
-- Your remaining credits are sufficient for **less than 10 days** of operation at your **current consumption rate**
+- Your combined balance (credit + promo credit) drops **below your threshold**
- You have automatic top-ups enabled
-- Your [settings](#billing-information) allow for the payment amount
+- The top-up wouldn't exceed your **calendar-month limit**
:::note Important notes
-- The charge amount is estimated based on your actual usage patterns, not maximum possible amounts
-- Negative balances are included in the next auto top-up (within your maximum charge limit)
-- The system checks your balance immediately after enabling auto top-ups, potentially triggering an immediate payment
+- Each top-up charges the **fixed amount** you configured, regardless of how fast you're spending
+- Your balance is checked periodically (every few minutes), so a top-up can take a few minutes to appear after you drop below the threshold
+- The final automatic top-up of the month is reduced so the month's total lands exactly on your calendar-month limit; after the limit is reached, automatic top-ups pause until the next calendar month
+- To avoid repeatedly hitting your payment method (which can get a card flagged or blocked by the payment processor), top-ups are spaced out: after a **successful** top-up Zerops waits **1 hour** before the next one, and after a **failed** top-up it waits **1 day** before trying again
+- If a charge fails, Zerops notifies you so you can check the validity and available funds of your saved card. After **3 failed attempts in a row**, automatic top-up is turned off and you're notified by email; re-enable it once your payment method is working again
- Auto top-up limits don't affect manual payments — add any amount manually regardless of automatic settings
:::
### Configuration Options
-#### Period
-The timeframe for which the maximum configured auto-charge amount is checked. Can be set to either:
+#### Threshold
+When your combined balance (credit + promo credit) drops below this value, an automatic top-up is triggered.
-- **Weekly**: Maximum charge amount applies to a 7-day rolling window
-- **Monthly**: Maximum charge amount applies to a 30-day rolling window
+#### Top-up Amount
+The fixed amount charged to your saved card on each automatic top-up.
-#### Maximum Charge Amount
-The maximum amount that can be auto-charged in the given period. This serves as a safeguard against unexpected costs due to traffic spikes or other unforeseen circumstances.
+- Minimum: $10 (matches the minimum manual payment)
+- Maximum: $10,000 per top-up
-- Minimum setting: $10 (matches the minimum manual payment)
+#### Calendar-Month Limit
+The maximum total that can be automatically charged within a single calendar month (UTC). This safeguards against unexpected costs: once the limit is reached, automatic top-ups pause until the next calendar month.
+
+- Must be at least the top-up amount (so at least one top-up can go through each month)
#### Real-World Example
-**Scenario:** Application with $20 weekly operating costs and initial manual top-up of $100
+**Scenario:** Application with ~$50 weekly operating costs and an initial manual top-up of $100
**Your Settings:**
-- Period = Weekly
-- Maximum charge amount = $150
+- Threshold = $50
+- Top-up amount = $200
+- Calendar-month limit = $500
**Expected behavior:**
-- System adds approximately $20-30 when less than 10 days of credits remain (around day 35)
-- Payments continue in small increments aligned with your usage patterns
-- During traffic spikes, payments adjust to match higher consumption (up to $150 weekly limit)
-- Payment amounts return to normal when usage decreases
+- When your balance falls below $50, Zerops charges $200 to bring it back up
+- Each top-up is exactly $200, no matter how fast you're spending, until you approach the calendar-month limit
+- After two top-ups ($400), a full $200 would exceed the $500 limit, so the next top-up is reduced to $100, bringing the month's total to exactly $500
+- Automatic top-ups then pause until the limit resets at the start of the next calendar month (UTC)
## Billing Information
@@ -3410,7 +3418,9 @@ Services in Zerops require computing resources that are billed separately from y
:::note Daily Spending Control
-You can set daily spending limits in GUI for your project to control costs and avoid unexpected charges. This provides an alternative to configuring automatic resource scaling ranges while keeping your services running optimally.
+You can set a daily spending limit in the GUI for your project to keep an eye on costs and avoid unexpected charges. This provides an alternative to configuring automatic resource scaling ranges while keeping your services running optimally.
+
+Reaching the limit does not stop your project - your services keep running. When a project reaches its daily spending limit, Zerops sends you a warning notification (e-mail) so you can decide whether to raise the limit. The limit resets at midnight (UTC).
:::
## Additional Services
@@ -8490,7 +8500,7 @@ Zerops provides an automated, secure backup system for supported services. This
## Supported Services
-Zerops provides automated backup functionality for the following services. For specific backup format details and restore instructions, visit each service's documentation: [MariaDB](/mariadb/how-to/backup), [PostgreSQL](/postgresql/how-to/backup), [Qdrant](/qdrant/overview), [Elasticsearch](/elasticsearch/overview), [NATS](/nats/overview), [Meilisearch](/meilisearch/overview), and [Shared Storage](/shared-storage/how-to/backup).
+Zerops provides automated backup functionality for the following services. For specific backup format details and restore instructions, visit each service's documentation: [MariaDB](/mariadb/how-to/backup), [PostgreSQL](/postgresql/how-to/manage#backups), [Qdrant](/qdrant/overview), [Elasticsearch](/elasticsearch/overview), [NATS](/nats/overview), [Meilisearch](/meilisearch/overview), and [Shared Storage](/shared-storage/how-to/manage#backups).
## Managing Backups in the UI
@@ -9313,6 +9323,20 @@ Remember that only publicly accessible objects will be cached by the CDN. Privat
Zerops was built on the idea of **environment parity** — giving developers the full development lifecycle, from remote development to highly available production, with the observability and developer tools for maximum flexibility, and sensible defaults so the configs stay reasonable. Turns out that's **exactly what coding agents need** to produce and iterate on production-ready applications.
+
+
+
+
## What is it
An **[MCP server](/zcp/overview)** for your agents — with an optional remote cloud development environment container to run them in — that makes use of the flexibility and processes Zerops provides. There's no "Zerops system prompt" hogging your context window. The MCP server is a thin layer that makes **your agent a Zerops platform power user** — when needed — so it understands:
@@ -10885,8 +10909,8 @@ Usage:
Flags:
--archive-file-path string If set, zCLI creates a tar.gz archive with the application code in the required path relative
to the working directory. By default, no archive is created.
- --deploy-git-folder Sets a custom path to the zerops.yaml file relative to the working directory. By default zCLI
- looks for zerops.yaml in the working directory.
+ --deploy-git-folder If set, the .git folder is also uploaded with the deploy. By default, the .git folder
+ is ignored.
-h, --help the service deploy command.
--project-id string If you have access to more than one project, you must specify the project ID for which the
command is to be executed.
@@ -14673,6 +14697,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
@@ -14706,21 +14732,19 @@ 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 |
-| 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` |
+| 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.
----------------------------------------
@@ -14807,18 +14831,10 @@ 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
----------------------------------------
@@ -14871,16 +14887,16 @@ 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 [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}`)
----------------------------------------
@@ -14894,28 +14910,16 @@ zsc cdn purge /style.css$ # Purge exact file
| Need | Choice | Why |
|------|--------|-----|
-| **Any caching need** | **Valkey** (default) | Active development, full HA, Redis-compatible |
-| Legacy KeyDB apps | KeyDB | Only if migrating existing KeyDB deployment |
-| Legacy KeyDB apps | KeyDB | Only if migrating existing KeyDB deployment |
+| **Any caching need** | **Valkey** (default) | Active development, optional HA, Redis-compatible |
+| **Any caching need** | **Valkey** (default) | Active development, optional HA, Redis-compatible |
## 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
+- **HA vs single → the `type` variant:** `valkey:single@7.2` (default) / `valkey:ha@7.2`. Only `7.2` exists — the platform rejects `valkey@8` (`serviceStackTypeNotFound`).
+- **Scaling tier → the `profile` field:** `hobby` / `staging` *(default)* / `production`. Recommend `hobby` for dev (cheapest), `staging` for production; escalate to `production` only on a clear load signal. Omitting `profile` applies `staging`.
+- **Legacy form** (older YAML / existing services): `type: valkey@7.2` + `mode:` — `mode: NON_HA` ≡ `:single`, `mode: HA` ≡ `:ha`. Still accepted (remaps to the variant) but `mode` is deprecated; author new YAML as the variant + profile.
+- Connection: Valkey **requires auth** on Zerops — use `${cache_connectionString}` (full auth'd URL) or supply `${cache_password}` (e.g. `REDIS_PASSWORD: ${cache_password}`). There is no separate `${cache_user}` (default user + password); an unauthenticated `redis://${hostname}:6379` fails with `NOAUTH Authentication required`.
----------------------------------------
@@ -14923,42 +14927,64 @@ zsc cdn purge /style.css$ # Purge exact file
# Guides > Choose Database
-**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.
## 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 |
-| Analytics / OLAP | ClickHouse | Columnar storage, ReplicatedMergeTree, 4 protocol ports |
+| Analytics / OLAP | ClickHouse | Columnar storage, ReplicatedMergeTree |
+| Analytics / OLAP | ClickHouse | Columnar storage, ReplicatedMergeTree |
+
+## PostgreSQL — two import-YAML choices, both fixed for the service's life
+
+**1. HA vs single node → the `type` variant** (NOT a `mode:` field):
+
+- `postgresql:single@` — one node. Default for dev and most apps.
+- `postgresql:ha@` — 3-node cluster + proxies. Production-grade; resources start at the production tier.
+
+**2. Scaling tier → the `profile` field** (autoscaling envelope + PostgreSQL tuning preset):
+
+| Workload | `:single` profile | `:ha` profile |
+|---|---|---|
+| dev / prototype / learning — cheapest | **`oltp-hobby`** | — (HA has no hobby) |
+| staging / early-stage app | `oltp-staging` *(single default)* | `oltp-staging` |
+| business-critical transactional | `oltp-production` | `oltp-production` *(HA default)* |
+| high-throughput OLTP at scale | — | `oltp-enterprise` |
+| analytics / warehouse | `olap-production` | `olap-production` |
+| ingestion / write-heavy | `writeheavy-production` | `writeheavy-production` |
+| ingestion / write-heavy | `writeheavy-production` | `writeheavy-production` |
+
+**Recommended defaults — set `profile` explicitly; escalate only on a clear signal:** dev → `postgresql:single@` + `profile: oltp-hobby` (lowest cost); production → `profile: oltp-staging` (move to `oltp-production` / `oltp-enterprise` only when load clearly warrants it). Omitting `profile` applies the default — single → `oltp-staging`, **HA → `oltp-production` (dedicated CPU + high minima)** — which over-provisions HA.
-## PostgreSQL (Default Choice)
+```yaml
+services:
+ - hostname: db # dev — cheapest
+ type: postgresql:single@18
+ profile: oltp-hobby
+ - hostname: db # production HA — default tier (profile optional)
+ type: postgresql:ha@18
+ profile: oltp-production
+```
+
+- `profileOverrides` (individual params: `work_mem`, `max_wal_size`, `autovacuum_*`, …) is valid **only** with `profile: custom`.
+- `verticalAutoscaling` (CPU mode, min/max CPU/RAM/disk) tightens the envelope on top of any profile — no need for `custom` just to resize.
-- 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
+**Legacy form — you may still meet it** in older YAML, recipes, or existing services: `type: postgresql@` + a separate `mode:` field. The mapping is `mode: NON_HA` ≡ `:single`, `mode: HA` ≡ `:ha`. The platform still accepts the legacy form (it remaps to the variant) but `mode` is **deprecated and ignored by validation**. Author new YAML as `:single`/`:ha` + `profile`; treat a discovered `mode:` as its equivalent variant.
+
+- 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
+- Type variant: `mariadb:single@` / `mariadb:ha@`. No `profile` — scale with `verticalAutoscaling`.
+- 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
+- Type variant: `clickhouse:single@` / `clickhouse:ha@`. No `profile` — scale with `verticalAutoscaling`.
+- HA: replicated databases use a `Replicated(...)` engine `ON CLUSTER`; tables use a `Replicated*MergeTree` engine (without `ON CLUSTER`).
----------------------------------------
@@ -14979,74 +15005,29 @@ zsc cdn purge /style.css$ # Purge exact file
| Event sourcing / audit logs | Kafka | Indefinite topic retention, strong ordering |
| 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.
-
----------------------------------------
# Guides > Choose Runtime Base
-**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 |
| Pre-built Docker images | Docker | VM-based, bring your own image |
-| CGO / native libs | Ubuntu | Better glibc compatibility than Alpine's musl |
-| 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 ` (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
@@ -15058,66 +15039,42 @@ NATS exposes **two distinct messaging shapes**. Pick ONE per recipe and write ya
- 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
-
----------------------------------------
# Guides > Choose Search
-**Use Meilisearch** for simple full-text search. Use **Elasticsearch** for advanced queries or HA requirements. Use **Qdrant** for vector/AI search.
+Four managed search engines, chosen by workload. **Meilisearch** for simple full-text search (the default). **Elasticsearch** for advanced queries or HA full-text. **Typesense** for autocomplete with typo-tolerance (HA-capable). **Qdrant** for vector / AI similarity search.
## Decision Matrix
| Need | Choice | Why |
|------|--------|-----|
-| **Simple full-text search** | **Meilisearch** (default) | Instant setup, typo-tolerant, frontend-safe keys |
-| Advanced queries / HA | Elasticsearch | Cluster support, plugins, JVM tuning |
-| Autocomplete + typo-tolerance | Typesense | Raft HA, CORS built-in, fast |
-| Vector / AI similarity | Qdrant | gRPC + HTTP, automatic cluster replication |
-| Vector / AI similarity | Qdrant | gRPC + HTTP, automatic cluster replication |
+| Simple full-text search (default) | **Meilisearch** | Instant setup, typo-tolerant, frontend-safe keys; single-node only (no HA) |
+| Advanced queries / HA full-text | **Elasticsearch** | Multi-node cluster, plugins, JVM tuning |
+| Autocomplete + typo-tolerance, with HA | **Typesense** | 3-node Raft consensus, CORS built-in, fast |
+| Vector / AI similarity | **Qdrant** | HTTP + gRPC, automatic cluster replication; internal-only |
+| Vector / AI similarity | **Qdrant** | HTTP + gRPC, automatic cluster replication; internal-only |
## 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
----------------------------------------
@@ -15144,14 +15101,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: ${{ 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 ` instead
## GitLab Integration (Webhook)
@@ -15162,15 +15120,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=""` when you return to the ZCP develop flow
## GitLab CI
@@ -15212,7 +15168,7 @@ Any CI system with shell access can deploy via `zcli push`:
# Guides > Cloudflare
-Always use **Full (strict)** SSL mode in Cloudflare — "Flexible" causes redirect loops. Shared IPv4 with Cloudflare proxy is not recommended.
+Use **Full (strict)** SSL mode in Cloudflare for production (plain "Full" is acceptable for testing) — **never "Flexible"**, which causes redirect loops. Shared IPv4 with Cloudflare proxy is not recommended.
## DNS Configuration
@@ -15221,14 +15177,6 @@ Always use **Full (strict)** SSL mode in Cloudflare — "Flexible" causes redire
CNAME
```
-### With Cloudflare Proxy (orange cloud)
-| IP Type | Record | Proxy |
-|---------|--------|-------|
-| IPv6 only | `AAAA ` | Proxied |
-| Dedicated IPv4 | `A ` | Proxied |
-| Shared IPv4 | **Not recommended** | Reverse AAAA lookup issues |
-| Shared IPv4 | **Not recommended** | Reverse AAAA lookup issues |
-
### DNS-Only (gray cloud)
| IP Type | Records Required |
|---------|-----------------|
@@ -15244,12 +15192,6 @@ Method B: CNAME *.
ACME: CNAME _acme-challenge..zerops.zone
```
-## SSL/TLS Settings (Cloudflare Dashboard)
-- **Encryption mode: Full (strict)** — mandatory
-- **Never use "Flexible"** — causes infinite redirect loops
-- Enable "Always Use HTTPS"
-- WAF exception: Skip rule for `/.well-known/acme-challenge/` (ACME validation)
-
## Preparing a Service for Cloudflare
Any runtime service (nodejs, go, python, etc.) can be put behind Cloudflare. Steps:
@@ -15266,16 +15208,13 @@ Any runtime service (nodejs, go, python, etc.) can be put behind Cloudflare. Ste
3. **Configure Cloudflare DNS** to point to your Zerops project IP
4. **Set SSL mode to "Full (strict)"** in Cloudflare dashboard
-**Important**: The `zerops_subdomain enable` tool only works on deployed (ACTIVE) services. For new services, use `enableSubdomainAccess: true` in import YAML.
+The `enableSubdomainAccess: true` in step 1 is the portable mechanism — the first deploy (or GUI toggle) activates the L7 route; you do not need a separate enable step. (*In ZCP, `zerops_deploy` auto-enables it on first deploy; see the public-access guide for the eligible-mode list and the `serviceStackIsNotHttp` port-shape rejection in the Gotchas below.*)
Internal service-to-service communication must always use `http://` — never `https://`. SSL terminates at the Zerops L7 balancer.
## Gotchas
-1. **Flexible SSL = redirect loop**: Zerops forces HTTPS, Cloudflare Flexible sends HTTP → infinite redirect
-2. **Shared IPv4 + proxy is broken**: Reverse AAAA lookup doesn't work with Cloudflare proxy on shared IPv4
-3. **ACME challenge needs WAF exception**: Without it, Cloudflare blocks Let's Encrypt validation
-4. **Wildcard SSL on Cloudflare Free**: Free plan doesn't proxy wildcard subdomains — use DNS-only or upgrade
-5. **Subdomain on undeployed service**: `zerops_subdomain enable` returns "Service stack is not http or https" on READY_TO_DEPLOY services — deploy code first or use `enableSubdomainAccess` in import YAML
+1. **Wildcard SSL on Cloudflare Free**: Free plan doesn't proxy wildcard subdomains — use DNS-only or upgrade
+2. **"Service stack is not http or https"**: `zerops_subdomain enable` returns this when the service has no HTTP-shaped port (a worker, or a port without `httpSupport: true`) — it's about port shape, not deploy state. A READY_TO_DEPLOY service WITH `httpSupport: true` can be enabled; a deployed worker still cannot.
----------------------------------------
@@ -15364,38 +15303,9 @@ Default behavior (`temporaryShutdown: false`):
5. Old container processes terminated
6. Old containers deleted
-### temporaryShutdown Behavior
-
-| Setting | Behavior | Downtime |
-|---------|----------|----------|
-| `false` (default) | New containers start BEFORE old ones stop | **Zero downtime** |
-| `true` | Old containers stop BEFORE new ones start | **Temporary downtime** |
-| `true` | Old containers stop BEFORE new ones start | **Temporary downtime** |
-
-Use `temporaryShutdown: true` only when you cannot run two versions simultaneously (e.g., database migrations, singleton locks).
-
----
-
-## Readiness Check vs Health Check
-
-| Aspect | Readiness Check | Health Check |
-|--------|----------------|--------------|
-| When | **During deploy only** | **Continuously after deploy** |
-| Purpose | Gates traffic to new containers | Detects runtime failures |
-| Location | `deploy.readinessCheck` | `run.healthCheck` |
-| Failure action | Container marked failed after timeout, replaced | Container restarted |
-| Failure action | Container marked failed after timeout, replaced | Container restarted |
-
-### Readiness Check Mechanics
-
-1. Application starts via `start` command
-2. Readiness check runs (httpGet or exec)
-3. If **fails** -- wait `retryPeriod` seconds (default 5s), retry
-4. If **succeeds** -- container marked active, receives traffic
-5. If still failing after `failureTimeout` (default 300s / 5 min) -- container deleted, new one created
+### Deploy strategy & checks
-**httpGet**: succeeds on HTTP `2xx`, follows `3xx` redirects, 5-second per-request timeout
-**exec.command**: succeeds on exit code 0, 5-second per-command timeout
+`temporaryShutdown` (in the `deploy` block) controls cutover order: `false` (default) starts new containers before removing old ones (zero-downtime); `true` stops old before new (downtime, use only when two versions cannot coexist — exclusive DB migrations, singleton locks). The **readiness check** gates traffic to the new container during a deploy; the **health check** monitors the live app continuously. For check behavior, params, the httpGet/exec timeouts, and the production pattern, see `zerops_knowledge uri="zerops://guides/readiness-health-checks"`.
---
@@ -15405,7 +15315,7 @@ Typical pipeline events in chronological order:
1. **`stack.build` process RUNNING** -- build container created, pipeline started
2. **`stack.build` process FINISHED** -- build complete, artifact uploaded
-3. **`appVersion` build event ACTIVE** -- deploy started, containers launching
+3. **`appVersion` build event ACTIVE** -- the new version is deployed and running (this is the terminal success state, NOT "launching")
4. **Service status returns to RUNNING** -- all containers active, deploy complete
**Terminal states:**
@@ -15434,23 +15344,20 @@ Zerops keeps **10 most recent versions**. Older auto-deleted. Any archived versi
## Gotchas
-1. **Build and run are SEPARATE containers** -- build output does not automatically appear in runtime. You must specify `deployFiles`
-2. **initCommands run on EVERY container start** -- including restarts and horizontal scaling, not just deploys
-3. **initCommands failures do NOT cancel deploy** -- app starts regardless of init exit code
-4. **prepareCommands in build vs run** -- `build.prepareCommands` customizes build env, `run.prepareCommands` creates custom runtime image. Different containers, different purposes
-5. **deployFiles land in `/var/www`** -- tilde syntax (`dist/~`) extracts contents directly to `/var/www/` (strips directory). Without tilde, `dist` → `/var/www/dist/` (preserved). **CRITICAL**: `run.start` path must match — `dist/~` + `start: bun dist/index.js` BREAKS because the file is at `/var/www/index.js`, not `/var/www/dist/index.js`
+1. **initCommands run on EVERY container start** -- including restarts and horizontal scaling, not just deploys
+2. **initCommands failures DO fail the deploy** -- `run.initCommands` run during runtime-prepare, BEFORE the start command, and are deploy-gating: a non-zero exit aborts the deploy. The platform emits `RUN.INIT COMMANDS FINISHED WITH ERROR` in the runtime log and surfaces the failed command + exit code on the `stack.build` process (`commandExec` / "init command failed"). The new appVersion goes to `DEPLOY_FAILED` and is **never activated**; the start command never runs and the previously-active version keeps serving. **Diagnose via `appVersion.status` (`DEPLOY_FAILED`) + `activationDate` (`null`), NOT the service status** — the service stays `ACTIVE` on the old version, so service-status alone reads as "fine". Keep init commands idempotent and exit 0.
## SSHFS Mount and Deploy Interaction
When using SSHFS (`zerops_mount`) for dev workflows, deploy replaces the container. This has important consequences:
1. **After deploy, run container only has `deployFiles` content.** All other files (including zerops.yml if not in deployFiles) are gone. Use `deployFiles: [.]` for dev services to ensure zerops.yml and source files survive the deploy cycle.
-2. **SSHFS mount auto-reconnects after deploy.** No explicit remount is needed — the SSHFS reconnect mechanism handles the container replacement transparently. The mount only becomes truly stale during stop (container not running); after start it auto-reconnects again.
+2. **SSHFS mount auto-reconnects only while the service is running.** Usually no remount is needed, but if the mount goes stale after a deploy (stat/ls returns empty, writes hang), recover it explicitly with `zerops_mount action="mount"`. A stopped service has no live mount until it starts again.
3. **zerops.yml must be in deployFiles** for dev self-deploy lifecycle. Without it, subsequent deploys from the container fail because zerops.yml is missing.
**Two kinds of "mount" (disambiguation):**
- `zerops_mount` -- SSHFS tool, mounts service `/var/www` locally for development. This is a dev workflow tool.
-- Shared storage mount -- platform feature, attaches a shared-storage volume at `/mnt/{hostname}` via `mount:` in import.yml + zerops.yml `run.mount`. These are completely unrelated features.
+- Shared storage mount -- platform feature, attaches a shared-storage volume at `/mnt/{hostname}` via the service-level `mount:` parameter in import.yml. There is no `mount` field in zerops.yml (a `run.mount` is silently ignored). These are completely unrelated features.
----------------------------------------
@@ -15486,11 +15393,11 @@ Total order for the bare key (highest wins): **system/platform > yaml-baked `run
Build and runtime run in **separate containers**. Variables from one phase are not visible in the other unless explicitly referenced with prefixes:
-| Want to access | From | Use prefix |
-|---------------|------|-----------|
-| Runtime var `API_KEY` | Build container | `${RUNTIME_API_KEY}` |
-| Build var `BUILD_ID` | Runtime container | `${BUILD_BUILD_ID}` |
-| Build var `BUILD_ID` | Runtime container | `${BUILD_BUILD_ID}` |
+| Want to access | From | How |
+|---------------|------|-----|
+| Runtime var `API_KEY` | Build container | `${RUNTIME_API_KEY}` — runtime `run.envVariables` are known at build time, so the build can read them |
+| Build var `BUILD_ID` | Runtime container | **Not available.** The build container is destroyed after build and its vars are not carried into the runtime env store — `${BUILD_BUILD_ID}` reaches the runtime process as the literal string `${BUILD_BUILD_ID}`. Persist the value into a deployed file, or recompute it at runtime. |
+| Build var `BUILD_ID` | Runtime container | **Not available.** The build container is destroyed after build and its vars are not carried into the runtime env store — `${BUILD_BUILD_ID}` reaches the runtime process as the literal string `${BUILD_BUILD_ID}`. Persist the value into a deployed file, or recompute it at runtime. |
```yaml
zerops:
@@ -15516,14 +15423,9 @@ run:
CACHE_URL: ${cache_connectionString}
```
-```javascript
-// App reads the names you mapped above:
-const host = process.env.DB_HOST;
-```
-
- The reference **resolves at container start**, independent of isolation mode — the referenced var does not need to exist at definition time.
- An **unresolved ref stays literal** (`${db_hostname}` reaches the process verbatim) — no error, no blank. A wrong hostname/var on the right-hand side becomes a literal string and the app fails at connect time.
-- **Hostname transformation**: dashes become underscores. Service `my-db` variable `port` is `${my_db_port}`.
+- **Hostname charset**: service hostnames are lowercase alphanumeric only (`[a-z0-9]`) — the platform rejects dashes, underscores, and uppercase with `serviceStackNameInvalid`. So a ref is simply `${hostname_varname}` with the literal hostname (service `cache` → `${cache_port}`); there is no dash-to-underscore rewrite to reason about, because a dashed hostname cannot exist.
Only legacy `envIsolation=none` auto-injects every sibling's vars as bare `_KEY` OS env vars without a ref — see Isolation Modes. New projects are `service`; rely on explicit refs.
@@ -15619,7 +15521,7 @@ The frontend consumes `API_URL` via plain `${API_URL}` in `build.envVariables` (
- Defined via GUI, import.yml `envSecrets`, or `dotEnvSecrets`
- **Read is privilege-gated** -- masked in GUI; via API an admin/write token returns the value verbatim, a read-only token returns `REDACTED` (keyed on `sensitive=true`). In-container the value is plaintext (the app needs it). Project-level `sensitive=true` does NOT persist — only service-level is a true secret surface.
- Can be updated without redeploy, but the service **must be restarted** to pick it up.
-- Overridden by yaml-baked `run.envVariables` with the same key (yaml owns the key).
+- A yaml-baked `run.envVariables` key and a secret on the **same key cannot coexist** — the platform rejects the secret with `userDataDuplicateKey`. The yaml owns the key; edit the yaml and redeploy to change it.
### dotEnvSecrets
@@ -15668,16 +15570,7 @@ An env-store change (secret or project) propagates to the container in ~5–10s
## System-Generated Variables
-Zerops auto-generates variables per service (e.g., `hostname`, `PATH`, DB connection strings). Cannot be deleted. Some read-only (`hostname`), others editable (`PATH`). Reference them from another service with an explicit `${hostname_varname}`.
-
-## Common Mistakes
-
-- **DO NOT** expect a sibling's vars to appear automatically under default `service` isolation — reference them explicitly as `${hostname_varname}` in `run.envVariables` (the bare `_KEY` injected form is `none`-only legacy).
-- **DO NOT** re-reference a var under its SAME name -- self-shadow loop. Project vars auto-inherit (read directly); cross-service uses a DIFFERENT left-hand name (`DB_HOST: ${db_hostname}`).
-- **DO NOT** set a secret/service var on a key already in `run.envVariables` -- rejected (`userDataDuplicateKey`); the yaml owns the key, edit yaml + redeploy.
-- **DO NOT** assume secret values are unreadable -- API read is privilege-gated (admin verbatim, read-only `REDACTED`), not unconditionally write-only.
-- **DO NOT** forget restart after GUI/API env changes -- the running process won't see new values.
-- **DO NOT** expect `envReplace` to recurse subdirectories -- it does not.
+Zerops auto-generates variables per service (e.g., `hostname`, `PATH`, DB connection strings). Some are **hard-reserved** and rejected if you try to set them — `PATH` (uppercase) returns `userDataUseOfSystemKey` in any `envVariables` block. Overridable platform vars include `envIsolation` / `sshIsolation` / `zeropsSubdomainHost` (and the CDN URLs) — but never `PATH`. Reference any of them from another service with an explicit `${hostname_varname}`.
----------------------------------------
@@ -15687,40 +15580,125 @@ Zerops auto-generates variables per service (e.g., `hostname`, `PATH`, DB connec
Zerops uses nftables with restricted TCP ports 1-1024 (only 22, 53, 80, 123, 443, 587 allowed); UDP and ports 1025-65535 are unrestricted.
-## TCP Ports 1-1024 (Restricted)
-
-| Port | Protocol | Status |
-|------|----------|--------|
-| 22 | SSH | Allowed |
-| 25 | SMTP | **Blocked** (spam prevention) |
-| 53 | DNS | Allowed |
-| 80 | HTTP | Allowed |
-| 123 | NTP | Allowed |
-| 443 | HTTPS | Allowed |
-| 465 | SMTPS | **Blocked** (deprecated) |
-| 587 | SMTP/STARTTLS | Allowed |
-| All others | — | **Blocked** |
-| All others | — | **Blocked** |
-
-## UDP Ports
-No restrictions on any UDP port.
-
-## TCP Ports 1025-65535
-No restrictions.
-
-## Direct Port Access Firewall
-For services with direct port access enabled:
-- Configure **blacklist** or **whitelist** rules per port
-- Available on ports 10-65435
-- Protocols: TCP, UDP
-
## Port Modification
Contact `support@zerops.io` with Project ID + Organization ID to request changes to restricted ports.
+
+----------------------------------------
+
+# Guides > Local Development
+
+
+Develop locally with hot reload while connecting to Zerops managed services (DB, cache, storage) via VPN. ZCP generates `.env` with real credentials. Deploy to Zerops with `zerops_deploy` which uses `zcli push` under the hood.
+
+---
+
+## Setup
+
+### Prerequisites
+- **zcli** installed: `npm i -g @zerops/zcli` or [docs.zerops.io/references/cli](https://docs.zerops.io/references/cli)
+- **VPN**: WireGuard (installed by zcli automatically on first `zcli vpn up`)
+- **Project-scoped token**: Create in Zerops GUI → Settings → Access Tokens → Custom access per project
+
+### Configuration
+```json
+// .mcp.json (in project root)
+{
+ "mcpServers": {
+ "zcp": {
+ "command": "zcp",
+ "env": { "ZCP_API_KEY": "" }
+ }
+ }
+}
+```
+
+---
+
+## Workflow
+
+### 1. Connect to Zerops services
+```bash
+zcli vpn up
+```
+- All services accessible by hostname (e.g., `db`, `cache`)
+- One project at a time — switching disconnects the current
+- **Env vars NOT available via VPN** — use `.env` file instead
+
+### 2. Load credentials
+ZCP writes `.env` via `zerops_env action="generate-dotenv"` (it merges three input channels — project `envVariables`, zerops.yaml `run.envVariables`, and `.env.local` — into one resolved file):
+```
+db_hostname=db
+db_port=5432
+db_password=
+db_connectionString=postgresql://db:@db:5432
+```
+> Don't hand-edit `.env` directly — the next `generate-dotenv` refuses with a diff if it finds keys it didn't produce. Put manual overrides in `.env.local` (a no-touch input channel that survives regeneration), or pass `force=true`.
+
+### 3. Develop locally
+Start your dev server as usual — hot reload works against Zerops managed services over VPN.
+
+### 4. Deploy to Zerops
+```
+zerops_deploy targetService="appstage"
+```
+Uses `zcli push` under the hood. Blocks until build completes.
+
+---
+
+## zerops.yml for Local Mode
+
+The same `zerops.yml` works for both local push and container deploy:
+
+```yaml
+zerops:
+ - setup: appstage
+ build:
+ base: nodejs@22
+ buildCommands:
+ - npm ci
+ - npm run build
+ deployFiles: ./dist
+ run:
+ start: node dist/server.js
+ ports:
+ - port: 3000
+ httpSupport: true
+ envVariables:
+ DB_URL: ${db_connectionString}
+```
+
+`${hostname_varName}` references are resolved by Zerops at container runtime — they work regardless of push source (local or container).
+
+---
+
+## Connection Troubleshooting
+
+| Symptom | Diagnosis | Fix |
+|---------|-----------|-----|
+| `nc -zv db 5432` times out | VPN not connected | `zcli vpn up ` |
+| VPN connected, still timeout | Wrong project | `zcli vpn up ` |
+| Connected but auth fails | Stale .env | Regenerate: `zerops_env action="generate-dotenv"` |
+| Service unreachable | Service stopped | `zerops_manage action="start" serviceHostname="db"` |
+| Service unreachable | Service stopped | `zerops_manage action="start" serviceHostname="db"` |
+
+### Diagnostic sequence
+1. `zerops_discover service="db"` — is service RUNNING?
+2. `nc -zv db 5432 -w 3` — network reachable?
+3. Compare `.env` vs `zerops_env action="generate-dotenv" preview=true` (or `zerops_discover includeEnvValues=true` for stored values — `includeEnvs` returns key templates, not resolved values) — credentials current?
+
+---
+
+## Multi-Project
+
+Each project directory has its own `.mcp.json` + `.zcp/state/`. VPN is one per machine — switch manually.
+
+---
+
## Gotchas
-1. **Port 25 is permanently blocked**: Use port 587 with STARTTLS for email sending
-2. **Port 465 is blocked**: Legacy SMTPS — use 587 instead
-3. **Cannot self-service unblock**: Must contact Zerops support for port exceptions
+
+1. **`.env` contains secrets**: Add to `.gitignore` immediately — never commit
+2. **Object storage (S3)**: Uses HTTPS apiUrl — may work without VPN but not fully verified. Include VPN as fallback
----------------------------------------
@@ -15737,15 +15715,12 @@ Zerops captures stdout/stderr as logs; use syslog output format for severity fil
## Access Methods
-### GUI
-- Project detail → service → Logs section
-- Filter by severity, time range, container
-
### CLI
```bash
-zcli service log # Runtime logs
-zcli service log --showBuildLogs # Build logs
+zcli service log -S # Runtime logs (select via -S/--service-id, NOT a positional name)
+zcli service log -S --show-build-logs # Build logs
```
+Agents in this ecosystem read runtime logs via the `zerops_logs` MCP tool — it fetches **runtime logs only** (no build-log flag); build logs surface by auto-attachment on a deploy-failure response.
## Severity Filtering
Logs must output to **syslog format** for severity filtering to work. Plain stdout/stderr logs appear as "info" level.
@@ -15781,11 +15756,7 @@ Certificate paths:
- Custom certs: `ca-file("/etc/syslog-ng/user.crt")`
## Gotchas
-1. **Syslog format required**: Without syslog formatting, all logs appear as same severity — no filtering possible
-2. **Build logs separate**: Use `--showBuildLogs` flag in CLI — not shown by default
-3. **Source name must be `s_src`**: Using `s_sys` (common default) will not capture Zerops logs
-4. **UDP for Logstash**: Zerops forwards logs via UDP syslog — ensure Logstash listens on UDP
-5. **Custom certs path**: Place custom CA certs in `/etc/syslog-ng/user.crt`
+1. **UDP for Logstash**: Zerops forwards logs via UDP syslog — ensure Logstash listens on UDP
----------------------------------------
@@ -15809,12 +15780,13 @@ Zerops supports ELK (APM + logs) and Prometheus/Grafana stacks; expose `/metrics
| `logstash` | Log collection |
### APM Configuration
-```yaml
-envVariables:
- ELASTIC_APM_ACTIVE: "true"
- ELASTIC_APM_SERVICE_NAME: my-app
- ELASTIC_APM_SERVER_URL: https://apmserver.zerops.app
- ELASTIC_APM_SECRET_TOKEN:
+
+Set these on your app as service env vars (GUI or `run.envVariables`). Copy the real APM server URL from the `apmserver` service's subdomain in the GUI — it's a generated subdomain (`apmserver--..zerops.app`), **not** a fixed `apmserver.zerops.app` host:
+```
+ELASTIC_APM_ACTIVE=true
+ELASTIC_APM_SERVICE_NAME=my-app
+ELASTIC_APM_SERVER_URL=https://
+ELASTIC_APM_SECRET_TOKEN=
```
## Prometheus + Grafana Stack Services
@@ -15829,20 +15801,15 @@ envVariables:
### Custom Metrics
1. Expose HTTP `/metrics` endpoint in your app
-2. Set env var: `ZEROPS_PROMETHEUS_PORT=8080` (comma-separated for multiple ports)
+2. Set env var: `ZEROPS_PROMETHEUS_PORT=` (e.g. `9090`; comma-separated for multiple ports)
3. Prometheus auto-discovers and scrapes
## Built-in Metrics
- Service scaling & resource usage
-- PostgreSQL (with `pg_stat_statements` extension)
+- PostgreSQL (some metrics require the `pg_stat_statements` extension — superuser `CREATE EXTENSION` + restart)
- MariaDB
- Valkey
-## Gotchas
-1. **`ZEROPS_PROMETHEUS_PORT` is required**: Without it, Prometheus won't discover your custom metrics endpoint
-2. **APM server must be public**: Use Zerops subdomain to expose apmserver for trace collection
-3. **Cross-project needs forwarder**: Use `prometheuslight` service in source project to forward to global Prometheus
-
----------------------------------------
@@ -15855,14 +15822,6 @@ Zerops networking has two layers: a private VXLAN network per project (service-t
## Architecture Overview
-```
-Internet
- │
- ├─ HTTP/HTTPS ──→ L7 Balancer (SSL termination, nginx) ──→ container VXLAN IP:port
- │
- └─ Direct port ──→ L3/Core Balancer ──→ container VXLAN IP:port
-```
-
**Per-project infrastructure:**
- **Private VXLAN network** — isolated overlay network shared by all services
- **L7 HTTP Balancer** — 2 HA containers, auto-scales, domain routing + SSL
@@ -15899,7 +15858,10 @@ The L7 balancer is **nginx-based**, deployed as 2 HA containers per project. It
The balancer forwards client info via standard headers:
- **`X-Forwarded-For`** / **`X-Real-IP`** — original client IP
-- **`X-Forwarded-Proto`** — `https` (original protocol)
+- **`X-Forwarded-Proto`** — original protocol (`http` or `https`)
+- **`X-Forwarded-Host`** / **`X-Forwarded-Port`** — original host and port
+
+These headers are set by the balancer itself (client-sent values are overwritten), and the `Forwarded` and `Proxy` headers are stripped from incoming requests.
Your app receives plain HTTP but can inspect these headers for the real client info.
@@ -15944,16 +15906,7 @@ Work through these steps **in order**:
6. **Service status** — Is the service ACTIVE? (check `zerops_discover`)
7. **Timeout settings** — For slow responses, increase `send_timeout` (default 2s)
-**Common framework fixes:**
-```bash
-app.listen(3000, '0.0.0.0')
-
-flask run --host=0.0.0.0
-
-http.ListenAndServe(":8080", handler) // implicit 0.0.0.0
-
-server.address=0.0.0.0
-```
+**Fix:** bind the listen address to `0.0.0.0` (e.g. `app.listen(3000, '0.0.0.0')`), never `127.0.0.1`/`localhost`.
---
@@ -15985,12 +15938,7 @@ server.address=0.0.0.0
---
## Gotchas
-1. **Binding localhost = 502**: The L7 balancer connects via VXLAN IP, not localhost — always bind `0.0.0.0`
-2. **Internal HTTPS breaks things**: Service-to-service must use `http://` — the VXLAN network is already isolated
-3. **Subdomain 50MB cap**: zerops.app subdomains have a hard 50MB upload limit — use custom domain for larger files
-4. **send_timeout default is 2s**: Slow API responses may be cut off — increase for long-running endpoints
-5. **Cross-project networking impossible**: Each project is an isolated VXLAN — use public access to bridge projects
-6. **Shared IPv4 needs AAAA**: Missing AAAA record = silent routing failure on shared IPv4
+1. **Cross-project networking impossible**: Each project is an isolated VXLAN — to bridge projects, use public access (L7/public endpoint), not private hostnames
----------------------------------------
@@ -16039,67 +15987,9 @@ https://endpoint.com/bucket-name/object-key
https://bucket-name.endpoint.com/object-key
```
-**Every S3 client must be configured for path-style access.**
-
-## Framework Integration
-
-### PHP (Laravel — Flysystem)
-```php
-// config/filesystems.php
-'s3' => [
- 'driver' => 's3',
- 'endpoint' => env('S3_ENDPOINT'),
- 'use_path_style_endpoint' => true, // REQUIRED
- 'key' => env('S3_ACCESS_KEY'),
- 'secret' => env('S3_SECRET_KEY'),
- 'region' => env('S3_REGION', 'us-east-1'),
- 'bucket' => env('S3_BUCKET'),
-],
-```
-Package: `league/flysystem-aws-s3-v3`
-
-### Node.js (AWS SDK v3)
-```javascript
-
-const s3 = new S3Client({
- endpoint: process.env.S3_ENDPOINT,
- forcePathStyle: true, // REQUIRED
- credentials: {
- accessKeyId: process.env.S3_ACCESS_KEY,
- secretAccessKey: process.env.S3_SECRET_KEY,
- },
- region: process.env.S3_REGION || 'us-east-1',
-});
-```
-Package: `@aws-sdk/client-s3`
-
-### Python (boto3)
-```python
-import boto3
-s3 = boto3.client('s3',
- endpoint_url=os.environ['S3_ENDPOINT'],
- aws_access_key_id=os.environ['S3_ACCESS_KEY'],
- aws_secret_access_key=os.environ['S3_SECRET_KEY'],
- region_name='us-east-1',
- config=boto3.session.Config(s3={'addressing_style': 'path'}), # REQUIRED
-)
-```
-Package: `boto3`
-
-### Java (AWS SDK)
-```java
-S3Client s3 = S3Client.builder()
- .endpointOverride(URI.create(System.getenv("S3_ENDPOINT")))
- .serviceConfiguration(S3Configuration.builder()
- .pathStyleAccessEnabled(true) // REQUIRED
- .build())
- .credentialsProvider(StaticCredentialsProvider.create(
- AwsBasicCredentials.create(
- System.getenv("S3_ACCESS_KEY"),
- System.getenv("S3_SECRET_KEY"))))
- .region(Region.US_EAST_1)
- .build();
-```
+**Every S3 client must be configured for path-style access** (the SDK-specific
+flag: `forcePathStyle`/`use_path_style_endpoint`/`addressing_style: path`/
+`pathStyleAccessEnabled`). Framework wiring lives in the recipe for that stack.
## import.yaml Definition
@@ -16151,13 +16041,13 @@ Each service = one bucket (auto-named, immutable). Need multiple buckets? Create
# Guides > Php Tuning
-Override php.ini via `PHP_INI_*` env vars, FPM via `PHP_FPM_*`. Both require **restart** (not reload). Zerops defaults: upload/post = 1024M, FPM dynamic 20/2/1/3. Upload bottleneck is L7 balancer (50MB subdomain), not PHP.
+Override php.ini via `PHP_INI_*` env vars, FPM via `PHP_FPM_*`. **Applying a change depends on the channel**: values in `run.envVariables` are baked into the app version → changing one requires a **redeploy**; values set via `zerops_env`/GUI service env → changing one requires a **restart** (never reload — see Gotchas). Zerops defaults: upload/post = 1024M, FPM dynamic 20/2/1/3. Upload bottleneck is L7 balancer (50MB subdomain), not PHP.
## PHP Configuration (`PHP_INI_*`)
Override any php.ini directive via `PHP_INI_{directive}` env vars in `run.envVariables` or via `zerops_env` API.
-**Requires restart** to take effect. Reload writes config files (`/etc/php*/conf.d/overwrite.ini`) but FPM master does not re-read INI on reload.
+To apply a CHANGE: if the value lives in `run.envVariables` (baked into the app version), **redeploy**; if set via `zerops_env`/GUI service env, **restart**. A plain reload rewrites the config files (`/etc/php*/conf.d/overwrite.ini`) but the FPM master does not re-read INI on reload, so reload alone never applies the change.
### Zerops Platform Defaults
@@ -16192,7 +16082,7 @@ zerops:
## PHP-FPM (`PHP_FPM_*`)
-Configure FPM process management via `PHP_FPM_*` env vars. **Requires restart** — same as PHP_INI.
+Configure FPM process management via `PHP_FPM_*` env vars — same change semantics as `PHP_INI_*`: redeploy for `run.envVariables`, restart for `zerops_env`/GUI service env, never reload.
Config files are written to `/etc/php*/php-fpm.d/www.conf` by `zerops-zenv` at container startup.
@@ -16214,12 +16104,13 @@ Pre-forks a pool of workers. Good for consistent traffic.
High-traffic example:
```yaml
-envVariables:
- PHP_FPM_PM_MAX_CHILDREN: 50
- PHP_FPM_PM_START_SERVERS: 10
- PHP_FPM_PM_MIN_SPARE_SERVERS: 5
- PHP_FPM_PM_MAX_SPARE_SERVERS: 15
- PHP_FPM_PM_MAX_REQUESTS: 1000
+run:
+ envVariables:
+ PHP_FPM_PM_MAX_CHILDREN: 50
+ PHP_FPM_PM_START_SERVERS: 10
+ PHP_FPM_PM_MIN_SPARE_SERVERS: 5
+ PHP_FPM_PM_MAX_SPARE_SERVERS: 15
+ PHP_FPM_PM_MAX_REQUESTS: 1000
```
### Ondemand Mode
@@ -16227,11 +16118,12 @@ envVariables:
Spawns workers only when requests arrive. Saves memory for low-traffic sites.
```yaml
-envVariables:
- PHP_FPM_PM: ondemand
- PHP_FPM_PM_MAX_CHILDREN: 20
- PHP_FPM_PM_PROCESS_IDLE_TIMEOUT: 60s
- PHP_FPM_PM_MAX_REQUESTS: 500
+run:
+ envVariables:
+ PHP_FPM_PM: ondemand
+ PHP_FPM_PM_MAX_CHILDREN: 20
+ PHP_FPM_PM_PROCESS_IDLE_TIMEOUT: 60s
+ PHP_FPM_PM_MAX_REQUESTS: 500
```
Available parameters for ondemand:
@@ -16279,7 +16171,7 @@ run:
## Gotchas
-- **Reload does NOT apply changes** -- `PHP_INI_*` and `PHP_FPM_*` both require restart. Zerops reload rewrites config files via `zerops-zenv` but does not signal FPM to re-read them.
+- **Reload never applies the change** -- a value in `run.envVariables` needs a **redeploy** (it's baked into the app version); a value in `zerops_env`/GUI service env needs a **restart**. Reload rewrites config files via `zerops-zenv` but does not signal FPM to re-read them.
- **Upload fails at 50MB on subdomain** -- this is the L7 balancer limit, not PHP. Use a custom domain for larger uploads.
- **`post_max_size` must be >= `upload_max_filesize`** -- PHP silently drops the POST body if it exceeds `post_max_size`, even if the file itself is under `upload_max_filesize`.
@@ -16289,18 +16181,21 @@ run:
# Guides > Production Checklist
-Before going to production: (1) databases to HA mode, (2) minContainers: 2 on app services, (3) replace Mailpit with real SMTP, (4) remove Adminer, (5) use Object Storage for uploads, (6) use Redis/Valkey for sessions.
+Before going to production: (1) databases to HA (the `:ha` type variant), (2) minContainers: 2 on app services, (3) replace Mailpit with real SMTP, (4) remove Adminer, (5) use Object Storage for uploads, (6) use Valkey for sessions.
## Database
| Item | Dev | Production |
|------|-----|------------|
-| Mode | `NON_HA` | `HA` (must recreate) |
+| Deployment variant | `:single` (e.g. `postgresql:single@18`) | `:ha` (e.g. `postgresql:ha@18`) — must recreate |
+| Scaling profile | `oltp-hobby` | `oltp-staging` (escalate to `oltp-production` only on clear load) |
| Backups | Optional | Enabled |
| Connection | Single primary | Primary + read replicas |
| Connection | Single primary | Primary + read replicas |
-**HA is immutable** — cannot switch after creation. Delete and recreate with `mode: HA`.
+**HA is immutable** — cannot switch after creation. Delete and recreate with the `:ha` variant.
+
+**Legacy form** you may still see in older YAML or existing services: `type: postgresql@18` + a separate `mode:` field — `mode: NON_HA` ≡ `:single`, `mode: HA` ≡ `:ha`. Still accepted (remapped to the variant) but `mode` is deprecated and ignored by validation; author new YAML in the variant form.
## Application Services
@@ -16322,16 +16217,26 @@ Before going to production: (1) databases to HA mode, (2) minContainers: 2 on ap
## Dev Services to Remove
### Mailpit → Production SMTP
-```yaml
-- hostname: mailpit
- type: go@1
- buildFromGit: https://github.com/zeropsio/recipe-mailpit
-envVariables:
- SMTP_HOST: smtp.sendgrid.net
- SMTP_PORT: "587"
-envSecrets:
- SMTP_PASSWORD: your-production-key
+Mailpit (the dev mail catcher) is defined as:
+```yaml
+services:
+ - hostname: mailpit
+ type: alpine@3.20
+ buildFromGit: https://github.com/zeropsio/recipe-mailpit
+```
+For production, point your app at a real provider — non-secret settings in the app's `run.envVariables` (zerops.yaml), the key in `envSecrets` (import.yaml). They are different files; a bare top-level `envVariables:` block is schema-invalid.
+```yaml
+run:
+ envVariables:
+ SMTP_HOST: smtp.sendgrid.net
+ SMTP_PORT: "587"
+```
+```yaml
+services:
+ - hostname: app
+ envSecrets:
+ SMTP_PASSWORD: your-production-key
```
### Adminer → Remove or Restrict
@@ -16362,7 +16267,7 @@ Remove entirely or disable `enableSubdomainAccess`. Use VPN + pgAdmin/DBeaver lo
| Use case | Solution |
|----------|----------|
-| PHP sessions | Redis/Valkey |
+| PHP sessions | Valkey |
| Laravel sessions | Redis driver |
| Django sessions | Redis backend |
| Express sessions | Redis store |
@@ -16370,8 +16275,7 @@ Remove entirely or disable `enableSubdomainAccess`. Use VPN + pgAdmin/DBeaver lo
```yaml
- hostname: cache
- type: valkey@7.2
- mode: NON_HA # HA for production
+ type: valkey:single@7.2 # valkey:ha@7.2 for production
```
## Framework-Specific Production Settings
@@ -16416,31 +16320,13 @@ Remove entirely or disable `enableSubdomainAccess`. Use VPN + pgAdmin/DBeaver lo
| CPU mode | `cpuMode: DEDICATED` for consistent performance under load |
| Environment separation | Separate projects for dev/staging/prod |
| Stateless design | Sessions in Valkey, uploads in Object Storage — no local state |
-| Database mode | `mode: HA` for all managed services (immutable — plan before creation) |
-| Min containers | `minContainers: 2` on all app services for zero-downtime deploys |
-| Min containers | `minContainers: 2` on all app services for zero-downtime deploys |
+| Database HA | the `:ha` type variant for all HA-capable managed services (immutable — plan before creation) |
+| Min containers | `minContainers: 2+` on app services for throughput + crash-tolerance (rolling deploys are already zero-downtime at any count via the default `temporaryShutdown: false` — don't conflate the two) |
+| Min containers | `minContainers: 2+` on app services for throughput + crash-tolerance (rolling deploys are already zero-downtime at any count via the default `temporaryShutdown: false` — don't conflate the two) |
## Health Check Pattern
-Combined readiness + runtime health check for production services:
-
-```yaml
-zerops:
- - setup: app
- deploy:
- readinessCheck:
- httpGet:
- port: 3000
- path: /health
- run:
- healthCheck:
- httpGet:
- port: 3000
- path: /health
- start: node server.js
-```
-
-Readiness check gates traffic during deploy. Health check runs continuously — unhealthy containers are restarted after 5-minute retry window.
+Production services should pair a `deploy.readinessCheck` (gates traffic during deploy) with a `run.healthCheck` (continuous — the LB routes around an unhealthy container). The combined pattern, params, and behavior are in `zerops_knowledge uri="zerops://guides/readiness-health-checks"`.
## Gotchas
1. **HA is immutable**: Must delete and recreate service to switch modes
@@ -16465,8 +16351,8 @@ Zerops offers three public access methods: zerops.app subdomains (dev only, 50MB
- Max upload: **50 MB**
- **Not for production** — use for development/testing only
- Auto-provisioned SSL
-- Pre-configure via import YAML: `enableSubdomainAccess: true` (works for all runtime/web types)
-- **Activate routing:** `zerops_deploy` **auto-enables** the subdomain on the first deploy for eligible service modes (dev/stage/simple/standard/local-stage) and waits HTTP-ready — the deploy response carries `subdomainAccessEnabled: true` and the URL. Use `zerops_subdomain enable` only as an explicit recovery/ops command if auto-enable was skipped (a worker / non-HTTP service, or launch-production which deliberately opts out in favor of a custom domain). Import's `enableSubdomainAccess: true` pre-configures intent; deploy activates the L7 balancer. Re-deploys do NOT deactivate it. Use `zerops_discover` to check current status and get the URL (`subdomainEnabled` + `subdomainUrl` fields).
+- **Enable it:** set `enableSubdomainAccess: true` in the import YAML (works for all runtime/web types) to pre-configure intent; the first deploy (or the GUI toggle) activates the L7 subdomain route, and re-deploys never deactivate it.
+- *In ZCP:* `zerops_deploy` auto-enables the subdomain on the first deploy for eligible service modes (dev/stage/simple/standard/local-stage) and waits HTTP-ready — the deploy response carries `subdomainAccessEnabled: true` and the URL. `zerops_subdomain enable` is the explicit recovery/ops command if auto-enable was skipped (a worker / non-HTTP service, or launch-production which deliberately opts out in favor of a custom domain). `zerops_discover` shows current status (`subdomainEnabled` + `subdomainUrl`).
- **Port-specific subdomains**: If HTTP ports are defined in zerops.yml, each port gets its own subdomain: `{hostname}-{subdomainHost_prefix}-{port}.{subdomainHost_rest}`. Example: hostname `appdev`, subdomainHost `1df2.prg1.zerops.app`, port 3000 → actual URL `https://appdev-1df2-3000.prg1.zerops.app`. Port 80 omits the port suffix: `https://appdev-1df2.prg1.zerops.app`
- **Internal network fallback**: Every service is accessible internally via `http://{hostname}:{port}` (e.g., `http://appdev:3000`). Use this to verify the app is running when subdomain access is uncertain — `curl http://appdev:3000/health` from the ZCP container or any other service in the project
- Works for: nodejs, static, nginx, go, python, php, java, rust, dotnet, and all other runtime types
@@ -16474,15 +16360,8 @@ Zerops offers three public access methods: zerops.app subdomains (dev only, 50MB
### 2. Custom Domains (Production)
- Per-project HTTPS balancer (2 containers, HA)
- Round-robin load balancing + health checks
-- Full upload limit: 512 MB
-- Requires IP address assignment:
-
-| IP Type | Cost | Protocol | Notes |
-|---------|------|----------|-------|
-| Shared IPv4 | Free | HTTP/HTTPS only | Limited connections, shorter timeouts |
-| Dedicated IPv4 | $3/30 days | All protocols | Non-refundable, auto-renews |
-| IPv6 | Free | All protocols | Dedicated per project |
-| IPv6 | Free | All protocols | Dedicated per project |
+- Upload limit: 512 MB default (`client_max_body_size`, configurable up to 2048m on a custom domain) — not a hard cap
+- Requires IP address assignment
### 3. Direct Port Access
- Available for: Runtime services, PostgreSQL
@@ -16490,17 +16369,101 @@ Zerops offers three public access methods: zerops.app subdomains (dev only, 50MB
- Protocols: TCP, UDP
- Configurable firewall: blacklist or whitelist per port
-## DNS Setup (Custom Domain)
-Point your domain to the project's IP:
-- `A` record → Dedicated IPv4
-- `AAAA` record → IPv6
-- Shared IPv4: Requires **both A and AAAA** records (AAAA needed for SNI routing)
-
## Gotchas
-1. **Shared IPv4 needs AAAA record**: Without AAAA, SNI routing fails — always add both A and AAAA
-2. **zerops.app 50MB limit**: File uploads over 50MB fail on subdomains — use custom domain
-3. **Dedicated IPv4 is non-refundable**: $3/30 days, auto-renews — cannot get refund if removed early
-4. **Ports 80/443 reserved**: Your app cannot bind to these — Zerops uses them for SSL termination
+1. **Dedicated IPv4 is non-refundable**: $3/30 days, auto-renews — the fee isn't refunded if removed early, but the address can be reused in another project until the subscription ends
+
+
+----------------------------------------
+
+# Guides > Readiness Health Checks
+
+
+Two distinct mechanisms, often confused. **Readiness check** (`deploy.readinessCheck`) runs ONLY during a deploy — it gates when the new container starts receiving traffic; if it never passes, the deploy fails and the old version keeps serving. **Health check** (`run.healthCheck`) runs CONTINUOUSLY on the live app — it disconnects an unhealthy container from the load balancer, restarts it, and reconnects it on recovery. Both support `httpGet` or `exec` (mutually exclusive within one block). The field shape lives in the zerops.yml schema; this guide owns the behavior.
+
+---
+
+## The distinction (the #1 confusion)
+
+| | Readiness check | Health check |
+|---|---|---|
+| Location | `deploy.readinessCheck` | `run.healthCheck` |
+| When it runs | **During a deploy only** | **Continuously, after startup** |
+| Purpose | Gate traffic to a NEW container | Detect runtime failure of a LIVE container |
+| On failure | Deploy fails; new appVersion not activated; old version keeps serving | Container removed from LB → restarted → reconnected on recovery |
+| On failure | Deploy fails; new appVersion not activated; old version keeps serving | Container removed from LB → restarted → reconnected on recovery |
+
+A readiness check makes a deploy wait for the app to actually answer before cutting traffic over. A health check keeps a degraded container out of rotation while it's live. Use both on production services.
+
+## Readiness check (`deploy.readinessCheck`)
+
+Checks the **new** container at `localhost`. Until it passes, traffic stays on the old container.
+
+```yaml
+deploy:
+ readinessCheck:
+ httpGet: { port: 3000, path: /health }
+ failureTimeout: 60 # seconds until the container is marked failed
+ retryPeriod: 10 # seconds between attempts
+```
+
+Mechanics: `start` runs → readiness check runs → on fail, wait `retryPeriod` and retry → on success, the container is marked active and receives traffic → if still failing after `failureTimeout`, the container is deleted and the deploy fails (the previous appVersion stays active). Set `failureTimeout`/`retryPeriod` explicitly — there is no fixed schema default to rely on.
+
+## Health check (`run.healthCheck`)
+
+Runs on every container continuously after startup.
+
+```yaml
+run:
+ healthCheck:
+ httpGet: { port: 3000, path: /health }
+ failureTimeout: 30 # consecutive-failure seconds before restart (reset by a success)
+ disconnectTimeout: 30 # seconds before a failing container is pulled from the LB
+ recoveryTimeout: 30 # seconds of success before a restarted container takes traffic again
+ execPeriod: 10 # seconds between attempts
+```
+
+**Failure sequence**: repeated failures → `disconnectTimeout` removes the container from the load balancer → `failureTimeout` triggers a restart → `recoveryTimeout` gates traffic reconnection once it's healthy again.
+
+## httpGet vs exec (both checks)
+
+- **`httpGet`** — GET to `localhost:{port}{path}`, triggered **inside** the container. Success = HTTP `2xx` (follows `3xx` redirects), 5-second per-request timeout. `host` sets a custom Host header; `scheme: https` only if the app demands TLS internally (default is plain HTTP — the L7 balancer terminates SSL upstream).
+- **`exec`** — a local shell command, success = exit `0`, 5-second per-command timeout. Has access to all env vars. Use a YAML `|` block for multi-step scripts.
+
+**DO NOT** put both `httpGet` and `exec` in the same check block — they are mutually exclusive.
+
+## temporaryShutdown (deploy container ordering)
+
+Readiness gating only buys zero-downtime when the old container stays up during cutover — that is `temporaryShutdown` (in the `deploy` block):
+
+| Value | Behavior | Downtime |
+|---|---|---|
+| `false` (default) | New containers start and pass readiness BEFORE old ones are removed | None (zero-downtime rolling deploy) |
+| `true` | Old containers stop BEFORE new ones start | Yes |
+| `true` | Old containers stop BEFORE new ones start | Yes |
+
+Use `true` only when you cannot run two versions simultaneously (exclusive DB-migration access, singleton locks). Rolling cutover is zero-downtime at any `minContainers` value — don't conflate replica count with the deploy strategy.
+
+## Dev/stage placement
+
+In dev+stage pairs, `healthCheck` and `readinessCheck` belong ONLY on the **stage** entry. Dynamic-runtime dev services run `start: zsc noop --silent` (a no-op keepalive that idles the container while the agent drives the real dev server's lifecycle) — adding a `healthCheck` to a dev service causes unwanted container restarts during iteration.
+
+## Production pattern
+
+Combine both on a production service so deploys wait for readiness and the LB routes around runtime failures:
+
+```yaml
+zerops:
+ - setup: app
+ deploy:
+ readinessCheck:
+ httpGet: { port: 3000, path: /health }
+ run:
+ healthCheck:
+ httpGet: { port: 3000, path: /health }
+ start: node server.js
+```
+
+Without health checks, the load balancer cannot route around an unhealthy container — it keeps sending traffic to a degraded instance.
----------------------------------------
@@ -16508,7 +16471,7 @@ Point your domain to the project's IP:
# Guides > Scaling
-Zerops autoscales vertically (CPU/RAM/disk) and horizontally (container count). Runtimes support both. Managed services (DB, cache, shared-storage) support vertical only with fixed container count (NON_HA=1, HA=3). Object-storage and Docker have no autoscaling. Extends grammar.md section 9 with mechanics, thresholds, YAML syntax, and common mistakes.
+Zerops autoscales vertically (CPU/RAM/disk) and horizontally (container count). Runtimes support both. Managed services (DB, cache, shared-storage) support vertical only with fixed container count (the `:single` type variant = 1 node, `:ha` = 3). Object-storage and Docker have no autoscaling. Extends grammar.md section 9 with mechanics, thresholds, YAML syntax, and common mistakes.
## When to Scale Which Way
@@ -16526,12 +16489,12 @@ Zerops autoscales vertically (CPU/RAM/disk) and horizontally (container count).
|---|---|---|---|
| **Runtime** (Node.js, Go, PHP, Python, Java, etc.) | Yes | Yes (1-10 containers) | Full autoscaling |
| **Linux containers** (Alpine, Ubuntu) | Yes | Yes (1-10 containers) | Same as runtimes |
-| **Managed DB** (PostgreSQL, MariaDB) | Yes | No (fixed: NON_HA=1, HA=3) | Mode immutable after creation |
-| **Managed cache** (KeyDB/Valkey) | Yes | No (fixed: NON_HA=1, HA=3) | Mode immutable after creation |
-| **Shared storage** | No (automatic, not configurable) | No (fixed: NON_HA=1, HA=3) | DO NOT set verticalAutoscaling in import.yml |
+| **Managed DB** (PostgreSQL, MariaDB) | Yes | No (fixed: `:single`=1, `:ha`=3) | Variant immutable after creation |
+| **Managed cache** (Valkey) | Yes | No (fixed: `:single`=1, `:ha`=3) | Variant immutable after creation |
+| **Shared storage** | Yes (cpu/ram/disk configurable) | No (fixed: `:single`=1, `:ha`=3) | Accepts verticalAutoscaling in import.yml |
| **Object storage** | No | No | Fixed size at creation, no verticalAutoscaling |
-| **Docker** | No (manual, triggers VM restart) | Yes (VM count changeable, triggers restart) | No autoscaling at all |
-| **Docker** | No (manual, triggers VM restart) | Yes (VM count changeable, triggers restart) | No autoscaling at all |
+| **Docker** | No (manual, triggers VM restart) | Manual only (change VM count, triggers restart) | No automatic autoscaling |
+| **Docker** | No (manual, triggers VM restart) | Manual only (change VM count, triggers restart) | No automatic autoscaling |
## Vertical Autoscaling
@@ -16622,27 +16585,23 @@ Applies to **runtimes and Linux containers only**. New containers are added when
### Managed Services (DB, Cache, Shared Storage)
-Container count is **fixed by deployment mode**, set at creation, **immutable**:
+Container count is **fixed by the deployment variant in the type** (`postgresql:single@18` / `postgresql:ha@18`), set at creation, **immutable**:
-| Mode | Containers | Use case |
+| Variant | Containers | Use case |
|---|---|---|
-| `NON_HA` | 1 | Development, non-critical |
-| `HA` | 3 (on separate physical machines) | Production, automatic failover |
-| `HA` | 3 (on separate physical machines) | Production, automatic failover |
+| `:single` | 1 | Development, non-critical |
+| `:ha` | 3 (on separate physical machines) | Production, automatic failover |
+| `:ha` | 3 (on separate physical machines) | Production, automatic failover |
+
+**Legacy form** you may still see in older YAML: a separate `mode:` field — `mode: NON_HA` ≡ `:single`, `mode: HA` ≡ `:ha`. Still accepted (remapped to the variant) but deprecated and ignored by validation.
HA recovery: failed container is disconnected, new one created on different hardware, data synchronized from healthy copies, failed container removed.
PostgreSQL HA exposes read replica port **5433** for distributing SELECT queries.
-## Configuring Thresholds via zerops_scale
-
-Threshold parameters can be set via the `zerops_scale` MCP tool, not just import.yml:
-
-```
-zerops_scale serviceHostname="api" minFreeRamGB=0.5 minFreeRamPercent=5 minFreeCpuCores=0.2
-```
+## Autoscaling Thresholds
-All four threshold parameters (`minFreeRamGB`, `minFreeRamPercent`, `minFreeCpuCores`, `minFreeCpuPercent`) are optional and can be combined with any other scaling parameters in a single call.
+The dual-threshold trigger controls WHEN vertical scaling fires. All four fields (`minFreeRamGB`, `minFreeRamPercent`, `minFreeCpuCores`, `minFreeCpuPercent`) are optional and live in the `verticalAutoscaling` block (see import.yml Syntax below). (*In ZCP they can also be set live: `zerops_scale serviceHostname="api" minFreeRamGB=0.5 minFreeRamPercent=5 minFreeCpuCores=0.2`, combinable with any other scaling parameter in one call.*)
## Docker Services
- Run in **VMs**, not containers. **No autoscaling** -- resources fixed at creation
@@ -16672,8 +16631,7 @@ services:
# Managed DB (vertical only, no container settings)
- hostname: db
- type: postgresql@16
- mode: HA
+ type: postgresql:ha@16
verticalAutoscaling:
cpuMode: DEDICATED
minCpu: 1
@@ -16686,28 +16644,30 @@ services:
## Strategy Presets
-**Development** — SHARED CPU, min resources, 1 container. Cost-effective for dev/staging:
-```
-zerops_scale serviceHostname="api" cpuMode="SHARED" minCpu=1 maxCpu=2 minRam=0.25 maxRam=1 minContainers=1 maxContainers=1
+**Development** — SHARED CPU, min resources, single container (cost-effective for dev/staging):
+```yaml
+minContainers: 1
+maxContainers: 1
+verticalAutoscaling: { cpuMode: SHARED, minCpu: 1, maxCpu: 2, minRam: 0.25, maxRam: 1 }
```
**Production** — DEDICATED CPU, higher minimums, multiple containers for HA:
-```
-zerops_scale serviceHostname="api" cpuMode="DEDICATED" minCpu=2 maxCpu=8 minRam=2 maxRam=8 minContainers=2 maxContainers=6
+```yaml
+minContainers: 2
+maxContainers: 6
+verticalAutoscaling: { cpuMode: DEDICATED, minCpu: 2, maxCpu: 8, minRam: 2, maxRam: 8 }
```
-**Burst workloads** — Wide autoscaling range, SHARED CPU:
-```
-zerops_scale serviceHostname="worker" cpuMode="SHARED" minCpu=1 maxCpu=8 minRam=1 maxRam=16 minContainers=1 maxContainers=10
+**Burst workloads** — wide autoscaling range, SHARED CPU:
+```yaml
+minContainers: 1
+maxContainers: 10
+verticalAutoscaling: { cpuMode: SHARED, minCpu: 1, maxCpu: 8, minRam: 1, maxRam: 16 }
```
-## Common Mistakes
-
-**DO NOT** add `verticalAutoscaling` to **object-storage** or **shared-storage** services in import.yml -- causes import failure. Object storage has a fixed `objectStorageSize` only. Shared storage is managed automatically.
+(*In ZCP, the same presets apply live via `zerops_scale serviceHostname=... cpuMode=... minCpu=... ...`.*)
-**DO NOT** set `minContainers` or `maxContainers` for managed services (DB, cache, shared-storage) -- container count is fixed by `mode` (NON_HA=1, HA=3). Setting these causes import failure.
-
-**DO NOT** use `DEDICATED` CPU for low-traffic or dev services -- wastes resources. Use `SHARED` and switch to `DEDICATED` only when consistent performance matters.
+## Common Mistakes
**DO NOT** set `minFreeRamGB: 0` and `minFreeRamPercent: 0` simultaneously -- the API rejects this with "Invalid custom autoscaling value". Always keep at least the default absolute threshold (0.0625 GB).
@@ -16716,6 +16676,56 @@ zerops_scale serviceHostname="worker" cpuMode="SHARED" minCpu=1 maxCpu=8 minRam=
**DO NOT** assume horizontal scaling works automatically -- your application must be stateless. File-based sessions, local uploads, and in-memory state break with multiple containers.
+----------------------------------------
+
+# Guides > Shared Storage Integration
+
+
+Shared storage is a managed SeaweedFS volume mounted as a POSIX filesystem at `/mnt/` into one or more runtime services — for files that must be shared *between containers/services* (shared config, plugin directories, a common working set). It is mounted via the import.yaml `mount:` field; there is **no `zerops.yaml` mount**. For high-write workloads or user uploads, prefer Object Storage (S3) instead — shared storage is POSIX/NFS-style and not built for high-throughput churn.
+
+## Mounting — import.yaml `mount:` is the only config-file mechanism
+
+Declare the storage service, then list it under the runtime's service-level `mount:`. This auto-connects the storage at provision — import alone is sufficient, no second step:
+
+```yaml
+services:
+ - hostname: storage
+ type: shared-storage
+ - hostname: app
+ type: nodejs@22
+ buildFromGit: https://github.com/myorg/myapp # mount: requires buildFromGit
+ mount:
+ - storage # list one or more shared-storage hostnames
+```
+
+After deploy, the runtime has `/mnt/storage` (SeaweedFS FUSE, writable). Multiple volumes can be mounted to one service (`/mnt/files1`, `/mnt/files2`, …).
+
+**There is NO `zerops.yaml` mount field.** A `mount:` under `run:` is silently stripped by the platform — it even passes yaml validation (validation-passing ≠ honored), but produces no mount and no connection. Mounting is import.yaml-only (or `connect-storage`, below).
+
+## Connecting a storage to a runtime that missed the import mount
+
+A runtime that was READY_TO_DEPLOY at import time (e.g. a stage service created but not yet deployed) does NOT pick up the import `mount:`. Once it's ACTIVE, connect explicitly:
+
+```
+zerops_manage action="connect-storage" serviceHostname="app" storageHostname="storage"
+```
+
+This registers the connection, but the FUSE mount materializes **only on the next fresh deploy (new container creation)** — a plain restart does NOT bring it up. Redeploy the service after connecting.
+
+## Constraints & behavior
+
+- **Mount path**: always `/mnt/`. Runtime containers only — NOT available during build or `run.prepareCommands` phases.
+- **Mounting overwrites** any existing content in the mount directory.
+- **Capacity**: max 60 GB total (raise via support request); file size is unbounded within the 60 GB. `verticalAutoscaling` floors: RAM 0.5 GB, disk 5 GB.
+- **HA**: 1:1 replication with auto-failover; during a master failover the mount is briefly unavailable (~30s).
+- **POSIX**: standard filesystem ops (with minor permission-setting limits). Filesystem operations are logged to runtime logs tagged `zerops-mount-`. `df` can report misleading numbers — use the Zerops GUI for accurate storage metrics.
+- **No env vars**: shared storage exposes no connection variables — it's a filesystem, not a networked service.
+
+## Shared storage vs object storage
+
+Use **shared storage** when you need a POSIX filesystem shared across services (shared config, plugin/extension directories, a common scratch area). Use **object storage** (S3/MinIO) for user uploads, media, and any high-throughput or write-heavy file operations — and for anything that must survive independent of any single service. Don't reach for shared storage as a generic uploads bucket.
+
+
----------------------------------------
# Guides > Smtp
@@ -16744,19 +16754,31 @@ Only port **587** (STARTTLS) is allowed for outbound email — ports 25 and 465
| Amazon SES | `email-smtp.{region}.amazonaws.com` | 587 | Access key | Secret key |
## Configuration Example
+
+Non-secret SMTP settings belong in `run.envVariables` (zerops.yaml); the password is a secret in `envSecrets` (import.yaml, service level). These live in **different files** — a bare top-level `envVariables:`/`envSecrets:` block is rejected (`envVariables` is valid only under `build`/`run`).
+
```yaml
-envVariables:
- SMTP_HOST: smtp.sendgrid.net
- SMTP_PORT: "587"
- SMTP_USER: apikey
-envSecrets:
- SMTP_PASSWORD:
+zerops:
+ - setup: app
+ run:
+ envVariables:
+ SMTP_HOST: smtp.sendgrid.net
+ SMTP_PORT: "587"
+ SMTP_USER: apikey
```
+```yaml
+services:
+ - hostname: app
+ type: nodejs@22
+ envSecrets:
+ SMTP_PASSWORD:
+```
+
+A change to `envSecrets` requires a **service restart** to take effect.
+
## Gotchas
-1. **Port 25 is permanently blocked**: Cannot be unblocked — use 587 with STARTTLS
-2. **Port 465 is also blocked**: Legacy SMTPS is deprecated — use 587
-3. **Gmail needs App Password**: Regular Gmail passwords won't work — generate an App Password in Google Account settings
+- **Gmail SMTP**: a regular Gmail password fails auth — generate an App Password in Google Account settings and use it as `SMTP_PASSWORD`.
----------------------------------------
@@ -16830,7 +16852,7 @@ Zerops VPN uses WireGuard via `zcli vpn up ` — connects to one pro
## Commands
```bash
zcli vpn up # Connect
-zcli vpn up --auto-disconnect # Auto-disconnect on terminal close
+zcli vpn up --auto-disconnect # First disconnect an already-active VPN, then connect
zcli vpn up --mtu 1350 # Custom MTU (default 1420)
zcli vpn down # Disconnect
```
@@ -16853,16 +16875,11 @@ zcli vpn down # Disconnect
|---------|----------|
| Interface already exists | `zcli vpn down` then `zcli vpn up` |
| Hostname not resolving | Try `db.zerops` suffix. On Windows, add `zerops` to DNS suffix list. Note: `dig`/`nslookup` bypass system resolver — use `nc -zv db 5432` to test |
-| WSL2 not working | Enable systemd in `/etc/wsl.conf` under `[boot]` |
+| WSL2 not working | Set `systemd=true` in `/etc/wsl.conf` under `[boot]`, then `wsl --shutdown` |
| Conflicting VPN | Use `--mtu 1350` |
| Ubuntu 25.* issues | Install AppArmor utilities |
| Ubuntu 25.* issues | Install AppArmor utilities |
-## Gotchas
-1. **No env vars via VPN**: Must read env vars from GUI or API — VPN only provides network access
-2. **One project at a time**: Cannot connect to multiple projects simultaneously
-3. **Hostname resolution**: Both `hostname` and `hostname.zerops` work (VPN sets up DNS search domain). Use plain hostname for simplicity. If resolution fails on Windows, add `zerops` to DNS suffix list in Advanced TCP/IP Settings.
-
----------------------------------------
@@ -16873,56 +16890,9 @@ Behavioral semantics for advanced zerops.yml features: health/readiness checks,
---
-## Health Check Behavior
-
-Health checks run **continuously** on every container after startup. Two types (mutually exclusive):
-
-- **`httpGet`**: GET to `localhost:{port}{path}`. Success = 2xx. Runs **inside** the container. Use `host` for custom Host header, `scheme: https` only if app requires TLS.
-- **`exec`**: Shell command, success = exit 0. Has access to all env vars. Use YAML `|` for multi-command scripts.
+## Health / Readiness Checks & temporaryShutdown
-| Parameter | Purpose |
-|-----------|---------|
-| `failureTimeout` | Seconds of consecutive failures before container restart |
-| `disconnectTimeout` | Seconds before failing container is removed from load balancer |
-| `recoveryTimeout` | Seconds of success before restarted container receives traffic again |
-| `execPeriod` | Interval in seconds between check attempts |
-| `execPeriod` | Interval in seconds between check attempts |
-
-**Failure sequence**: repeated failures -> `disconnectTimeout` removes from LB -> `failureTimeout` triggers restart -> `recoveryTimeout` gates traffic reconnection.
-
-**DO NOT** configure both `httpGet` and `exec` in the same block.
-
----
-
-## Readiness Check Behavior
-
-Runs **only during deployments** to gate traffic switch to a new container.
-
-```yaml
-deploy:
- readinessCheck:
- httpGet: { port: 3000, path: /health }
- failureTimeout: 60
- retryPeriod: 10
-```
-
-**How it works**: Checks the **new** container at `localhost`. Until it passes, traffic stays on the old container. After `failureTimeout`, deploy fails and the old container remains active.
-
-**DO NOT** confuse with healthCheck -- readiness gates a deploy; healthCheck monitors continuously after.
-
-> **Dev/stage distinction**: In dev+stage pairs, healthCheck and readinessCheck belong ONLY on the stage entry. Dev services use `start: zsc noop --silent` — the agent controls server lifecycle via SSH. Adding healthCheck to dev causes unwanted container restarts during iteration.
-
----
-
-## temporaryShutdown
-
-| Value | Behavior | Downtime |
-|-------|----------|----------|
-| `false` (default) | New containers start first, old removed after readiness | None (zero-downtime) |
-| `true` | All old containers stop, then new ones start | Yes |
-| `true` | All old containers stop, then new ones start | Yes |
-
-Use `true` when: exclusive DB migration access needed, or brief downtime acceptable. Use `false` for: production web services, APIs, user-facing apps.
+Health checks (`run.healthCheck`, continuous), readiness checks (`deploy.readinessCheck`, deploy-time traffic gate), the httpGet/exec shape + params + failure sequence, the dev/stage placement rule (dev uses `start: zsc noop --silent`, no healthCheck), and `temporaryShutdown` deploy ordering are all owned by `zerops_knowledge uri="zerops://guides/readiness-health-checks"`.
---
@@ -16937,7 +16907,7 @@ run:
allContainers: false
```
-Parameters: `command` (required), `timing` (required, 5-field cron: `min hour dom mon dow`), `workingDir` (default `/var/www`), `allContainers` (`false` = one container, `true` = all containers).
+Parameters: `command` (required), `timing` (required, 5-field cron: `min hour dom mon dow`), `allContainers` (**required** by the schema — `false` = one container, `true` = all containers), `workingDir` (optional, default `/var/www`).
Cron runs inside the runtime container with full env var access. When `allContainers: false`, Zerops picks **one** container (good for DB jobs). Use `true` for cache clearing or log rotation everywhere. Minimum granularity is 1 minute.
@@ -16958,7 +16928,7 @@ run:
- litestream restore -if-replica-exists -if-db-not-exists $DB_NAME
```
-Each entry: `command` (required), `name` (required), `workingDir` (optional), `initCommands` (optional, per-process init). **DO NOT** use both `start` and `startCommands`.
+Each entry: `command` (**required** — the only field the schema requires), `name` (optional, distinguishes processes in logs), `workingDir` (optional), `initCommands` (optional, per-process init). **DO NOT** use both `start` and `startCommands`.
---
@@ -17047,7 +17017,7 @@ Configuration is **merged at the section level** -- child values override parent
Available runtimes and versions are listed in **Service Stacks (live)** -- injected by `zerops_knowledge` and workflow responses. Some key rules:
- PHP: build `php@X`, run `php-nginx@X` or `php-apache@X` (different bases)
-- Deno, Gleam: REQUIRES `os: ubuntu` (not available on Alpine)
+- Deno: REQUIRES `os: ubuntu` (no Alpine build exists). Gleam runs on both Alpine and Ubuntu.
- Static sites: build `nodejs@latest`, run `static`
- `@latest` = newest stable version
@@ -17130,6 +17100,7 @@ export const runtimes = [
{ name: "Bun", link: "/bun/overview", icon: },
{ name: "Elixir", link: "/elixir/overview", icon: },
{ name: "Gleam", link: "/gleam/overview", icon: },
+ { name: "Ruby", link: "/ruby/overview", icon: },
{ name: "Nginx", link: "/nginx/overview", icon: },
{ name: "Static", link: "/static/overview", icon: },
]
@@ -26062,119 +26033,30 @@ Have you build something that others might find useful? Don't hesitate to share
- [Zerops VPN](/references/networking/vpn) — Connect to your services easily with Zerops VPN.
-----------------------------------------
-
-# Postgresql > Faq
-
-
- **Question: How do I properly use PostgreSQL in HA mode?**
-
-In High Availability (HA) mode, PostgreSQL runs on multiple containers with a primary node and replicas. To get the most out of this setup:
-
- - Use port `5432` for all write operations (INSERT, UPDATE, DELETE) — this always routes to the primary node
- - Use port `5433` for read operations (SELECT) — this distributes queries across all replicas, improving performance
-
- The read replica port (`5433`) is only available in HA mode. If you're running PostgreSQL in single container (NON_HA) mode, only port `5432` is available.
-
- See [Connection Parameters](/postgresql/how-to/connect#connection-parameters) for all available ports and environment variables.
-
- **Question: Why is my connection to PostgreSQL from third-party software failing?**
-
-*One possible cause:*
-
- The connection string in Zerops always starts with `postgresql://`. While the official PostgreSQL documentation
- states that both `postgresql://` and `postgres://` URIs are valid, some software requires the shorter `postgres://`
- version.
-
- To resolve this, create your own environment variable with the correct URI. For example, if your PostgreSQL service is named `db`, use the following format:
-
- ```
- postgres://${db_user}:${db_password}@${db_hostname}:${db_port}
- ```
-
-
-----------------------------------------
-
-# Postgresql > How To > Backup
-
-
-Zerops provides automated data backup for PostgreSQL services with full encryption and flexible management options.
-
-For general backup information including configuration, scheduling, and management options, see the [Zerops Backups](/features/backup) documentation, which covers:
-- Backup scheduling and retention policies
-- Tagging system and storage quotas
-- Manual backup creation and CLI tools
-- Security and encryption details
-
-This page focuses on PostgreSQL-specific backup details.
-
-## PostgreSQL Backup Format
-
-PostgreSQL backups are created using `pg_dump` and stored in `.zip` format:
-
-- **Format**: `.zip` (containing per-schema `.dump` files)
-- **Tooling**: `pg_dump`
-- **Compression**: Custom format (`-Fc`), schema files named `schemaName.dump`
-- **Storage**: Encrypted and stored in isolated object storage
-
-## Restoring PostgreSQL Backups
-
-To restore a PostgreSQL backup:
-
-1. **Download** the backup file (`.zip`) from the Zerops UI
-2. **Extract** the zip file to access the individual schema dump files
-3. **Prepare** your target environment (clean existing data or use a new instance)
-4. **Restore** using PostgreSQL native tools. Follow the [official PostgreSQL backup documentation](https://www.postgresql.org/docs/current/backup-dump.html) for detailed restore procedures, or use web-based management tools like phpMyAdmin or Adminer as described in [PostgreSQL Management](/postgresql/how-to/manage).
-
-For assistance with the restoration process, contact Zerops support.
-
-## High Availability
-
-For PostgreSQL services running in High Availability mode:
-- Backups are created on a randomly selected healthy node
-- Other nodes remain operational during the backup process
-- Manual backups typically run on the primary node
-
-## Best Practices
-
-- Always create a manual backup with a protected tag before database migrations or major schema changes
-- Test your restore process periodically in a non-production environment
-- Monitor your backup storage usage in the Project Overview
-- Use descriptive tags like `pre-migration-v2` for important snapshots
-- Consider the order of schema restoration if you have dependencies between schemas
-
-For additional best practices and troubleshooting, refer to the [main backup documentation](/features/backup).
-
----------------------------------------
# Postgresql > How To > Connect
-This guide covers how to connect to your PostgreSQL database in Zerops, both from services within the same project and from outside the Zerops environment.
-
-## Connection Options Overview
+This guide covers everything about reaching your PostgreSQL database in Zerops: connection details, ports and TLS, connection pooling, connecting from other services or from your workstation, and the database tools you can use.
-Zerops provides several ways to connect to PostgreSQL:
+## Connection options at a glance
-1. **Internal connections** - Between services in the same Zerops project (via private network)
-2. **Remote connections**:
- - **VPN access** - From your local machine via Zerops VPN
- - **Direct IP access** - Enables external applications to connect using TLS encryption by opening public ports on IPv6 (available by default) or IPv4 (requires add-on activation if not already enabled)
-
-## Connection Details
+- **Internal**: between services in the same project, over the private network.
+- **Remote**: from outside Zerops, either through the [VPN](#connect-via-zerops-vpn) (your machine joins the private network) or through [public IP access](#connect-via-public-ip) (TLS through pgBouncer).
-You'll find PostgreSQL connection details in the service detail page under the **Peek access details** button (shows hostname, port, user, password, and connection string).
+## Connection details
-The full list of connection-related environment variables is available in the service detail under **Environment variables**.
+Find your connection details in the service detail page under **Peek access details** (hostname, port, user, password, connection string). The full list of connection-related environment variables lives under **Environment variables** in the same service detail.
-### Connection Parameters
+### Connection parameters
Parameter
Internal
-
External (TLS)
+
Public IP (TLS)
Env Variable
@@ -26199,7 +26081,7 @@ The full list of connection-related environment variables is available in the se
User
-
Identical to hostname
+
`db` (default)
Same as internal
`user`
@@ -26223,216 +26105,268 @@ The full list of connection-related environment variables is available in the se
Database name
-
db
+
`db` (default)
Same as internal
`dbName`
-:::tip
-If you're running PostgreSQL in High Availability (HA) mode, configure your application to route read queries to port **5433**. This distributes the load across all replicas, reducing pressure on the primary node and improving overall throughput.
-:::
-
:::warning
-Zerops creates a system user named `zps` with full privileges for maintenance purposes. Do not delete, change the password, or remove privileges from this user, as it will disrupt Zerops' ability to maintain the database cluster.
+Zerops creates a system user named `zps` with full privileges for maintenance. Do not delete it, change its password, or remove its privileges, as doing so disrupts Zerops' ability to maintain the database cluster.
:::
-:::info
-For more information about default PostgreSQL setup, users, and databases, see [Manage PostgreSQL Users and Databases](/postgresql/how-to/manage).
-:::
+## Connection ports and TLS
+
+PostgreSQL on Zerops exposes three ports, each for a different purpose:
+
+
-## Connect from Services in the Same Project
+- **Ports `5432` and `5433` do not support TLS.** Connect with `sslmode=disable`. Inside a project these ports are reachable only on the private network, and over the [VPN](#connect-via-zerops-vpn) the tunnel already encrypts the traffic, so requesting TLS on them fails the handshake.
+- **Port `6432` (pgBouncer) requires TLS.** Connect with at least `sslmode=require`; the connection is always encrypted. pgBouncer presents a certificate signed by the [Zerops CA](/references/networking/zerops-ca) but does not force the client to validate it, so `sslmode=require` is enough. If you want to verify the server's identity, use `sslmode=verify-full` together with the Zerops CA. This holds whether you reach `6432` from inside the project or over a public IP.
-All services within a Zerops project share a dedicated private network. There are two ways to implement connections between services in the same project:
+## Connection pooling with pgBouncer
-### Method 1: Direct Connection Parameters
+Port `6432` puts [pgBouncer](https://www.pgbouncer.org/) in front of PostgreSQL and multiplexes many client connections onto a small pool of server connections. This is valuable for applications that open many short-lived connections (serverless functions, PHP-FPM, or anything with high connection churn), and **we recommend connecting through `6432` rather than `5432` for these workloads, including for internal service-to-service connections.**
-You can directly use the connection parameters from Peek Access Details:
+A few things to know:
-```
-host = database1
-port = 5432
-user = database1
-password = ********** (find under Peek Access Details)
-```
+- **Transaction pooling.** A server connection is returned to the pool after each transaction, not when the client disconnects. Prepared statements are supported (cached per server connection), but session-scoped features that span transactions (session-level `SET`, advisory locks held across statements, `LISTEN`/`NOTIFY`) won't behave as expected. Use a direct `5432` connection for those.
+- **TLS is required** on `6432` (see [above](#connection-ports-and-tls)), even for internal connections.
+- **HA mode.** pgBouncer pools connections to the primary (writes). Read routing across replicas on port `5433` is separate and is not pooled.
-For read operations in HA mode, use port `5433` instead of `5432` with the same credentials.
+## Connect from services in the same project
-### Method 2: Environment Variables (Recommended)
+All services in a project share a private network, so other services reach PostgreSQL directly by its hostname. There are two ways to wire it up.
-For better maintainability, Zerops creates environment variables for each PostgreSQL service that you can use in your application configuration. List of service environment variables is available in Zerops GUI. Go to a PostgreSQL service detail and choose **Environment variables**.
+### Direct connection parameters
-To use variables from one service in another, prefix the variable name with the service hostname and underscore - to access the `connectionString` variable of `postgresql1`, use `postgresql1_connectionString`.
+Use the parameters from **Peek access details**:
-For read-only connections (HA mode only), use the `connectionStringReplicas` variable instead.
+```ini
+host = db # your PostgreSQL service hostname
+port = 5432 # 5433 for read-only replicas in HA mode
+user = db # default user
+password = **********
+```
+
+### Environment variables (recommended)
+
+Zerops generates connection environment variables for every PostgreSQL service. To use one service's variables from another, prefix the variable name with the service hostname and an underscore. For example, to read the `connectionString` of service `db`, reference `db_connectionString`.
-For more details on how to use environment variables, and instructions for adding your own custom variables, see the [Environment Variables](/features/env-variables) documentation.
+For read-only connections (HA mode only), use `connectionStringReplicas` instead.
+
+See the [Environment Variables](/features/env-variables) documentation for details and for adding your own variables.
:::caution Important notes
-- When changing passwords, update both the database user password and the environment variable separately - they don't automatically synchronize.
-- While both `postgresql://` and `postgres://` URI formats are valid, Zerops uses the `postgresql://` format. If your software requires `postgres://`, create a custom environment variable with this format.
-- Do not use SSL/TLS protocols for internal connections. Security is assured by the project's private network.
+- When changing a password, update both the database user and the environment variable, since they don't synchronize automatically.
+- Zerops uses the `postgresql://` URI scheme. If your software requires the shorter `postgres://`, create a custom environment variable with that format.
+- Internal connections on `5432`/`5433` don't use SSL/TLS. Security comes from the private network. For TLS-encrypted [connection pooling](#connection-pooling-with-pgbouncer), connect to pgBouncer on `6432`.
:::
-## Connect Remotely
+## Connect remotely
-Zerops offers two methods for connecting to your PostgreSQL database from outside the Zerops environment:
+There are two ways to reach PostgreSQL from outside Zerops: the VPN (recommended for development) and public IP access (for external applications).
-### Method 1: Connect via Zerops VPN
+### Connect via Zerops VPN
-You can securely connect to PostgreSQL from your local workstation via Zerops VPN:
+The VPN puts your workstation on the project's private network, so you connect exactly as an internal service would.
1. [Install & set up zCLI](/references/cli)
2. [Start the Zerops VPN](/references/networking/vpn#start-vpn)
-3. Use the connection details from Access Details in the PostgreSQL service detail in Zerops GUI
+3. Use the connection details from **Peek access details** in the service detail
4. When finished, [stop the Zerops VPN](/references/networking/vpn#stop-vpn)
:::warning Important notes
-* Do not use SSL/TLS protocols when connecting over VPN. Security is provided by the VPN tunnel.
-* If your connection over VPN doesn't work, try adding `.zerops` suffix to the service hostname (e.g., `database1.zerops`). For additional help, check the [VPN troubleshooting page](/references/networking/vpn#troubleshooting).
+* Connect to `5432`/`5433` without SSL/TLS over the VPN, since the tunnel already encrypts the traffic.
+* Environment variables are **not** available over the VPN; copy the access details from the GUI.
+* If a connection doesn't work, try the `.zerops` suffix on the hostname (e.g. `db.zerops`). See the [VPN troubleshooting page](/references/networking/vpn#troubleshooting).
:::
-### Method 2: Connect via Direct IP Access
-
-Direct IP Access uses [pgBouncer](https://www.pgbouncer.org/) for connection pooling and TLS termination.
+### Connect via public IP
-Internally, port `5432` is available without SSL (and port `5433` for reads in HA mode). Externally, connections are secured with TLS through pgBouncer (port `6432`) before being routed to your PostgreSQL service. The read replica port is not available for external connections.
+Public access always goes through pgBouncer on port `6432` over TLS (see [Connection ports and TLS](#connection-ports-and-tls)), which also pools your connections. The read-only replica port (`5433`) is **not** exposed publicly. Route reads through your application logic instead.
:::tip Trusting the TLS certificate
-The TLS certificate served on port `6432` is signed by the Zerops Certificate Authority. To verify it from outside Zerops, download and trust the [Zerops CA](/references/networking/zerops-ca) — e.g. `psql "... sslmode=verify-full sslrootcert=./zerops-ca.pem"`.
+The certificate on `6432` is signed by the Zerops Certificate Authority. To verify it from outside Zerops, download and trust the [Zerops CA](/references/networking/zerops-ca), for example `psql "... sslmode=verify-full sslrootcert=./zerops-ca.pem"`.
:::
-#### Enable external access
+To enable public access:
-1. Navigate to your PostgreSQL service in the Zerops GUI and choose the **Public Access through IP Addresses** section
-2. Choose either IPv6 (available by default) or IPv4 (requires the [unique IPv4](/references/networking/public-access#ipv4-configuration) add-on)
-3. Open one or more ports and point them to your PostgreSQL service (the system will direct them through pgBouncer)
- - Choose any port from 10-65435 (except 80 and 443)
- - Select destination service and internal port
- - Each public port can be mapped to any internal service port
- - Multiple public ports can point to the same internal port if needed
- - Port configurations can be set independently for IPv4 and IPv6
-4. Optionally enable firewall protection for additional security
-5. Click the **Publish X IP access change(s)** button to apply your settings
+1. Open your PostgreSQL service in the GUI and go to **Public Access through IP Addresses**
+2. Choose IPv6 (available by default) or IPv4 (requires the [unique IPv4](/references/networking/public-access#ipv4-configuration) add-on)
+3. Open one or more ports pointing to your service (routed through pgBouncer):
+ - Any port from 10–65435 (except 80 and 443)
+ - Each public port can map to any internal port; multiple public ports can share one internal port
+ - IPv4 and IPv6 can be configured independently
+4. Optionally enable firewall protection
+5. Click **Publish X IP access change(s)** to apply
-For database management tools and how to manage users and databases, see [Manage PostgreSQL Users and Databases](/postgresql/how-to/manage).
+## Database management tools
-----------------------------------------
+You can use any PostgreSQL-compatible tool to administer your database. For a zero-setup option, Zerops provides a ready-to-use recipe for [Adminer](https://www.adminer.org), a lightweight, full-featured web-based tool that supports PostgreSQL.
-# Postgresql > How To > Control
+### Install Adminer
+
+In the GUI, open your project, select **Import services**, and paste:
+
+```yaml title="zerops-import.yaml"
+services:
+ - hostname: adminer
+ type: php-apache@8.5
+ maxContainers: 1
+ enableSubdomainAccess: true
+ buildFromGit: https://github.com/zeropsio/recipe-adminer
+```
+Then [start the VPN](/references/networking/vpn) and open `http://adminer` in your browser (try `http://adminer.zerops` if it doesn't resolve).
+:::caution
+Do not use `https` when reaching management tools over the VPN.
+:::
-----------------------------------------
+### Desktop tools and psql
-# Postgresql > How To > Create
+Popular desktop clients (pgAdmin, DBeaver, DataGrip, or any PostgreSQL-compatible tool) work with Zerops over the VPN:
+1. [Start the Zerops VPN](/references/networking/vpn) to open an encrypted tunnel to your project
+2. Copy the [connection details](#connection-details) from the GUI (environment variables aren't available over the VPN)
+3. Connect without SSL/TLS on `5432`/`5433`, since the tunnel already encrypts the connection
-## Create PostgreSQL using Zerops GUI
+For the `psql` command-line client:
-First, set up a project in Zerops GUI. Then go to the project dashboard page and choose **Add new service** in the left menu in the **Services** block. Then add a new PostgreSQL service:
+```sh
+psql -h [hostname] -U [user] -d [database_name]
+```
-[Video: /vids/services/postgres.webm](/vids/services/postgres.webm)
+`psql` has no password flag, so it prompts you. To pass the password non-interactively, use `PGPASSWORD`:
-### Choose PostgreSQL version
+```sh
+PGPASSWORD=[password] psql -h [hostname] -U [user] -d [database_name]
+```
-Following PostgreSQL versions are currently supported:
+:::tip
+Try `[hostname].zerops` instead of `[hostname]` if you hit connection issues over the VPN.
+:::
-### Set a hostname
+To move data in and out with these tools, see [Export & import data](/postgresql/how-to/manage#export--import-data).
-Enter a unique service identifier like `postgresql`, `sql`, `db` etc.
-#### Limitations:
+----------------------------------------
-- Duplicate services with the same name within the same project are not allowed
-- Maximum 25 characters
-- Must contain only lowercase ASCII letters (a-z) or numbers (0-9)
+# Postgresql > How To > Control
-:::caution
-The hostname is fixed after the service is created and cannot be changed later.
-:::
-### Configure auto scaling
-Zerops automatically scales PostgreSQL services based on actual database usage. Configure the scaling parameters to match your database needs and control costs.
+----------------------------------------
-**CPU Mode**: Choose between shared (cost-effective) or dedicated (consistent performance).
+# Postgresql > How To > Create
-**Resource Limits**: Set minimum and maximum resources for CPU, RAM, and disk to control costs and ensure performance.
-**Deployment Mode**: Choose the reliability configuration for your PostgreSQL service:
+Create a PostgreSQL service in the [GUI](#create-in-the-gui), or describe it in YAML and [import](#import-with-yaml) it through the GUI or zCLI. Both paths configure the same things; the YAML route is repeatable and versionable.
-- **Highly Available**: Multiple containers with redundancy across different physical machines. Recommended for production environments.
-- **Single Container**: One container suitable for development and non-critical environments.
+## Create in the GUI
-:::warning
-Deployment mode cannot be changed after service creation.
-:::
+Go to your project dashboard and choose **Add new service** in the **Services** block:
-:::tip Learn More
-For detailed scaling configuration, deployment mode details, and troubleshooting, see:
-- [How Zerops scales PostgreSQL](/postgresql/how-to/scale) - Database-specific scaling guide
-- [Automatic Scaling and High Availability](/features/scaling) - Complete technical details
-:::
+[Video: /vids/services/postgres.webm](/vids/services/postgres.webm)
-## Create PostgreSQL using zCLI
+The wizard asks for four things:
-zCLI is the Zerops command-line tool. To create a new PostgreSQL service via the command line, follow these steps:
+### Version
-1. [Install & setup zCLI](/references/cli)
-2. [Create a project description file](#create-a-project-description-file)
-3. Create a project and a PostgreSQL service
+### Hostname
-### Create a project description file
+A unique service identifier, like `db`, `sql`, or `postgresql`. Maximum 25 characters, lowercase ASCII letters (a-z) and numbers (0-9) only, unique within the project.
-Zerops uses a YAML format file to describe the project infrastructure.
+### Deployment mode
-#### Basic example
+**Highly Available** (a 3-node cluster, recommended for production) or **Single container** (lower cost, no redundancy). See [Deployment modes](/postgresql/overview#deployment-modes) for the comparison.
-Create a directory `my-project`. Create a `description.yaml` file inside the directory with the following content:
+### Scaling profile and resources
-```yaml
-# Basic project data
-project:
- # project name
- name: my-project
-# array of project services
+Pick a [scaling profile](/postgresql/how-to/scale#scaling-profiles) matched to your workload (OLTP, OLAP, or write-heavy). The profile sets the autoscaling defaults and tunes the PostgreSQL configuration; on top of it you can set the CPU mode (shared or dedicated) and minimum/maximum limits for CPU, RAM, and disk. See [Scale & profiles](/postgresql/how-to/scale) for details.
+
+:::caution
+The **hostname** and **deployment mode** are fixed once the service is created. The scaling **profile** and resource limits can be changed at any time.
+:::
+
+## Import with YAML
+
+Zerops uses a YAML file to describe services declaratively. You can paste it in the GUI (**Import services** in the left menu of your project) or import it with the [zCLI](/references/cli).
+
+### Add a service to an existing project
+
+```yaml title="zerops-import.yaml"
services:
- - # service name
- hostname: postgresql1
- # service type and version number in postgresql@{version} format
- type: postgresql@12
- # mode of operation "HA"/"NON_HA"
- mode: NON_HA
+ - hostname: db
+ # postgresql:{single|ha}@{version}
+ type: postgresql:single@18
+ # optional: autoscaling profile, see Scale & profiles
+ profile: oltp-staging
```
-The YAML file describes your future project infrastructure. The project will contain one PostgreSQL service in the single container mode with default [auto scaling](/postgresql/how-to/scale) configuration. The hostname will be set to `postgresql1`.
+With zCLI:
-#### Full example
+```sh
+zcli project service-import zerops-import.yaml
+```
-Create a directory `my-project`. Create a `description.yaml` file inside the directory with the following content:
+The command is interactive: it lists your projects and lets you pick which one to import into. You can also pass the project ID directly with `-P`:
-```yaml
-# Basic project data
+```sh
+Usage:
+ zcli project service-import importYamlPath [flags]
+
+Flags:
+ -h, --help Help for the project service import command.
+ -P, --project-id string If you have access to more than one project, you must specify the project ID for which the
+ command will be executed.
+```
+
+### Create a new project
+
+A project import additionally contains the `project:` section:
+
+```yaml title="zerops-import.yaml"
project:
- # project name
name: my-project
- # optional: project description
+ # optional: project description and tags
description: A project with a PostgreSQL database
- # optional: project tags
tags:
- DEMO
- ZEROPS
-# array of project services
+
services:
- - # first service hostname
- hostname: postgresql1
- # service type and version number in postgresql@{version} format
- type: postgresql@12
- # mode of operation "HA"/"NON_HA"
- mode: HA
- # optional: vertical auto-scaling customization
+ - hostname: db
+ type: postgresql:ha@18
+ profile: oltp-production
+ # optional: override the profile's autoscaling defaults
verticalAutoscaling:
cpuMode: DEDICATED
minCpu: 2
@@ -26444,384 +26378,143 @@ services:
startCpuCoreCount: 3
minFreeRamGB: 0.5
minFreeRamPercent: 20
- - # second service hostname
- hostname: postgresql2
- # service type and version number in postgresql@{version} format
- type: postgresql@12
- # mode of operation "HA"/"non_HA"
- mode: NON_HA
+ - hostname: dbstaging
+ type: postgresql:single@18
+ profile: oltp-staging
```
-The YAML file describes your future project infrastructure. The project will contain two PostgreSQL services.
+```sh
+zcli project project-import zerops-import.yaml
+```
-The hostname of the first service will be set to `postgresql1`. The [high availability](/features/scaling#highly-available-ha-mode) mode will be chosen and the custom [auto scaling configuration](/postgresql/how-to/scale) will be set.
+The project name comes from the YAML, so the command doesn't take one. If you are a member of more than one organization (called *client* in the GUI), pass the ID with `--org-id`; you'll find it under the client name on the project dashboard.
-The hostname of the second service will be set to `postgresql2`. The [single container](/features/scaling#single-container-mode) mode will be chosen and the default [auto scaling configuration](/postgresql/how-to/scale) will be set.
+```sh
+Usage:
+ zcli project project-import importYamlPath [flags]
-#### Description of description.yaml parameters
+Flags:
+ -h, --help Help for the project import command.
+ --org-id string If you have access to more than one organization, you must specify the org ID for which the
+ project will be created.
+ --working-dir string Sets a custom working directory. The default working directory is the current directory. (default "./")
+```
-The `project:` section is required. Only one project can be defined.
+:::note
+The maximum size of an import YAML is 100 kB. A project import can mix PostgreSQL with [any other service type](/references/import-yaml/type-list).
+:::
+
+### Project parameters
+
+The `project:` section is required for a project import. Only one project can be defined.
-
Parameter
-
Description
-
Limitations
+
Parameter
+
Description
-
name
+
name
The name of the new project. Duplicates are allowed.
-
-
description
-
Optional. Description of the new project.
-
Maximum 255 characters.
+
description
+
Optional. Description of the new project. Maximum 255 characters.
-
tags
-
Optional. One or more string tags. Tags do not have a functional meaning, they only provide better orientation in projects.
-
+
tags
+
Optional. One or more string tags. Tags have no functional meaning, they only provide better orientation in projects.
-At least one service in the `services:` section is required. You can create a project with multiple services. The example above contains only PostgreSQL services but you can create a `description.yaml` with [different types] of services.
+### Service parameters
+
+At least one service in the `services:` section is required.
-
Parameter
+
Parameter
Description
+
hostname
- hostname
-
-
- The unique service identifier.
-
- The hostname of the new database will be set to the `hostname` value.
-
- Limitations:
-
-- duplicate services with the same name in the same project are
- forbidden
-
- - maximum 25 characters
-
-- must contain only lowercase ASCII letters (a-z) or numbers (0-9)
-
-
-
-
- type
-
-
- Specifies the service type and version.
-
- See what [PostgreSQL service types](/references/import-yaml/type-list#database-services) are currently supported.
+ The unique service identifier; becomes the database hostname on the project's private network. Maximum 25 characters, lowercase ASCII letters (a-z) and numbers (0-9) only, unique within the project. Fixed after creation.
-
-
- mode
-
-
- Defines the operation mode of the PostgreSQL service.
-
- HA
-
- Creates a PostgreSQL cluster with 3 database containers and 2 free
- database proxies. This mode is suited for production.
-
- Zerops always keeps the 3 database containers on different physical
- machines. All your data is stored redundantly in 3 identical copies. In
- case of a failure of a container or the underlying physical machine,
- Zerops automatically disconnects the failed container from the cluster,
- creates a new container and syncs all data from the remaining 2 copies.
- Finally, the broken container is automatically deleted.
-
- In HA mode, a dedicated read replica port (5433) is available, allowing you to route read queries to replicas for better performance. See [Connection Parameters](/postgresql/how-to/connect#connection-parameters) for details.
-
- NON_HA
-
- Zerops will create a PostgreSQL database installed in a single
- container. Useful for non-essential data or dev environments.
-
- Your data is stored only in a single container. If the container or the
- the underlying physical machine fails, your data since the last backup are
- lost. Zerops doesn't provide any automatic repairs of a single node
- PostgreSQL services.
-
-
+
type
- verticalAutoscaling
-
-
- Optional. Defines [custom vertical auto-scaling parameters](/postgresql/how-to/scale#configure-scaling).
-
- All verticalAutoscaling attributes are optional. Not specified
- attributes will be set to their default values.
+ The service type, deployment mode, and version in postgresql:{single|ha}@{version} format. The :single/:ha part selects the [deployment mode](/postgresql/overview#deployment-modes) and is fixed after creation; the standalone mode field is deprecated. See the [list of supported types](/references/import-yaml/type-list#database-services).
+
profile
- - cpuMode
-
-
- Optional. Accepts `SHARED`, `DEDICATED` values. Default is `SHARED`
+ Optional. The [autoscaling profile](/postgresql/how-to/scale#scaling-profiles): sets the autoscaling envelope and tunes the PostgreSQL configuration for your workload. Defaults to oltp-staging (single) or oltp-production (HA). Can be changed at any time in the GUI.
+
profileOverrides
- - minCpu/maxCpu
-
-
- Optional. Set the minCpu or maxCpu in CPU cores (integer).
+ Optional. Only valid with profile: custom; overrides individual PostgreSQL configuration values. See [Custom profile](/postgresql/how-to/scale#custom-profile) for the overridable keys and their units.
+
verticalAutoscaling
- - minRam/maxRam
-
-
- Optional. Set the minRam or maxRam in GB (float).
-
-
-
-
- - minDisk/maxDisk
-
-
- Optional. Set the minDisk or maxDisk in GB (float).
+ Optional. Overrides the profile's autoscaling defaults. All attributes are optional: cpuMode (SHARED/DEDICATED), minCpu/maxCpu (cores), minRam/maxRam (GB), minDisk/maxDisk (GB), startCpuCoreCount, minFreeRamGB, minFreeRamPercent. See [Configure scaling](/postgresql/how-to/scale#configure-scaling).
-:::caution
-The PostgreSQL service **hostname** and **mode** are fixed after the service is created. They can't be changed later.
-:::
-
-### Create a project based on the description.yaml
-
-When you have your `description.yaml` ready, use the `zcli project project-import` command to create a new project and the service infrastructure.
-
-```sh
-Usage:
- zcli project project-import importYamlPath [flags]
-
-Flags:
- -h, --help Help for the project import command.
- --org-id string If you have access to more than one organization, you must specify the org ID for which the
- project will be created.
- --working-dir string Sets a custom working directory. The default working directory is the current directory. (default "./")
-```
-
-Zerops will create a project and one or more services based on the `description.yaml` content.
-
-The maximum size of the `description.yaml` file is 100 kB.
-
-You don't specify the project name in the `zcli project project-import` command, because the project name is defined in the `description.yaml`.
-
-If you have access to more than one client, you must specify the client ID for which the project is to be created. The `clientID` is located in the Zerops GUI under the client name on the project dashboard page.
-
-### Add PostgreSQL service to an existing project
-
-#### Example
-
-Create a directory `my-project` if it doesn't exist. Create an `import.yaml` file inside the `my-project` directory with following content:
-
-```bash
-# array of project services
-services:
- -
- # service name
- hostname: postgresql1
- # service type and version number in postgresql@{version} format
- type: postgresql@12
- # mode of operation "HA"/"NON_HA"
- mode: NON_HA
-```
-
-The YAML file describes the list of one or more services that you want to add to your existing project. In the example above, one PostgreSQL service in the [single container](/features/scaling#single-container-mode) with default [auto scaling](/postgresql/how-to/scale) configuration will be added to your project. The hostname of the new service will be set to `postgresql1`.
-
-The content of the `services:` section of `import.yaml` is identical to the [project description file](#create-a-project-description-file). The `import.yaml` never contains the `project:` section because the project already exists.
-
-When your `import.yaml` is ready, use the `zcli project service-import` command to add one or more services to your existing Zerops project.
-
-```sh
-Usage:
- zcli project service-import importYamlPath [flags]
-
-Flags:
- -h, --help Help for the project service import command.
- -P, --project-id string If you have access to more than one project, you must specify the project ID for which the
- command will be executed.
-```
-
-zCLI commands are interactive, when you press enter after `zcli project service-import importYamlPath`, you will be given a list of your projects to choose from.
-
-The maximum size of the `import.yaml` file is 100 kB.
-
-----------------------------------------
-
-# Postgresql > How To > Export Import Data
-
-
-## Use Adminer or phpMyAdmin to export or import data
-* [Adminer ↗](https://www.adminer.org) - an open source full-featured database management tool written in PHP
-* [phpMyAdmin ↗](https://www.phpmyadmin.net) - a free software tool written in PHP, intended to handle the administration of PostgreSQL over the Web
-
-1. [Install the tools to Zerops](/postgresql/how-to/manage#installing-management-tools)
-2. Use their standard export or import functions
-
-## Use a database management tool on your workstation to export or import data
-
-Do you already use a database management tool that supports PostgreSQL on your workstation? Connect it securely to PostgreSQL from your local workspace via Zerops VPN.
-
-Zerops VPN client is included into zCLI, the Zerops command-line tool. To start the VPN connection, read [how to connect to PostgreSQL remotely](/postgresql/how-to/connect#connect-remotely).
-:::caution
-Do not use SSL/TLS protocols when connecting to PostgreSQL over VPN. Zerops PostgreSQL is not configured to support these protocols. The security is assured by the VPN.
-:::
-
-Once the connection to PostgreSQL is established, use the standard export or import functions of your favourite management tool.
-
-## Use psql CLI to export or import data
-
-If you are using the [psql ↗](https://www.postgresql.org/docs/current/app-psql.html) command-line client to manage your PostgreSQL on your local workspace, you can connect it securely to PostgreSQL via Zerops VPN.
-
-Zerops VPN client is included into zCLI, the Zerops command-line tool. To start the VPN connection, read [how to connect to PostgreSQL remotely](/postgresql/how-to/connect#connect-remotely).
-
-Once the VPN session is established, you have the secured connection to the project's private network in Zerops. You can access all project services locally by using their hostname. The only difference is that no [environment variables](/postgresql/how-to/connect#method-2-environment-variables-recommended) are available when connected through VPN. To connect to PostgreSQL in Zerops you have to copy the [access details](/postgresql/how-to/connect#connection-details) manually from Zerops GUI.
-
-Use [psql ↗](https://www.postgresql.org/docs/current/app-psql.html) command to connect to PostgreSQL in Zerops:
-
-```sh
-psql -h [hostname] -U [user] -p [password] -d [database_name]
-```
-
-:::caution
-Do not use SSL/TLS protocols when connecting to PostgreSQL over VPN. Zerops PostgreSQL is not configured to support these protocols. The security is assured by the VPN.
-:::
-
-To export your database data and structure, use the [pg_dump ↗](https://www.postgresql.org/docs/current/backup-dump.html) command.
-
-```sh
-pg_dump [database_name] > dumpfilename.sql
-```
-
-To import your database data and structure, use the `mysql` command.
-
-```sh
-mysql [database_name] < dumpfilename.sql
-```
-
----------------------------------------
# Postgresql > How To > Manage
-This guide covers how to manage your PostgreSQL databases in Zerops, including default setup, database management tools, plugins, and best practices.
-
-## Default Database and User
-
-Zerops creates a default database and user automatically when a new PostgreSQL service is [created](/postgresql/how-to/create).
+This guide covers the default database setup, installing extensions, backups, and moving data in and out. For connecting and management tools, see [Connect](/postgresql/how-to/connect).
-### Database
+## Default database and user
-- **Name**: Identical to the service hostname
-- **Encoding**: `utf8mb4`
+Zerops creates a default database and user automatically when a PostgreSQL service is [created](/postgresql/how-to/create).
-### DB User
+- **Database name**: `db`
+- **User**: `db`
+- **Password**: randomly generated
+- **Encoding**: `UTF8` (locale `C.UTF-8`)
-- **Username**: Identical to the service hostname
-- **Password**: Generated randomly
+Both names are fixed; they don't follow the service hostname. Besides the `db` user, Zerops also creates a `postgres` superuser for you (see [Extensions](#extensions)).
:::info
-For connection methods and environment variables, see the [Connect to PostgreSQL in Zerops](/postgresql/how-to/connect) page.
-:::
-
-:::caution Important notes
-- When changing passwords, update both the database user password and the environment variable separately - they don't automatically synchronize.
-- While both `postgresql://` and `postgres://` URI formats are valid, Zerops uses the `postgresql://` format. If your software requires `postgres://`, create a custom environment variable with this format.
-- Do not use SSL/TLS protocols for internal connections. Security is assured by the project's private network.
-:::
-
-## Database Management Tools
-
-You can use any PostgreSQL management tool of your choice to administer your databases in Zerops. For convenience, Zerops provides ready-to-use recipes for two popular web-based database management tools:
-
-* [Adminer](https://www.adminer.org) - a lightweight database management tool by Jakub Vrána
-* [phpMyAdmin](https://www.phpmyadmin.net) - a popular free database administration tool that works with both MySQL and PostgreSQL databases
-
-### Installing Management Tools
-
-You can install these tools with a simple one-click import in Zerops:
-
-1. In Zerops GUI, open your project and select **Import services** from the left menu
-2. Copy and paste one of the following YAML configurations:
-
-### Accessing Management Tools
-
-After installation, you can access these tools via VPN:
-
-1. [Start the Zerops VPN](/references/networking/vpn)
-2. Type `http://adminer` or `http://phpmyadmin` in your browser
-
-:::tip
-Try `http://adminer.zerops` or `http://phpmyadmin.zerops` if you encounter any connection issues.
-:::
-
-:::caution
-Do not use https when connecting to management tools via VPN.
+For connection methods, ports, and environment variables, see [Connect to PostgreSQL](/postgresql/how-to/connect).
:::
-## Database Tools on Your Workstation
+## Extensions
-You can use various database management tools from your local workstation to connect to your PostgreSQL database in Zerops:
+### List available extensions
-1. **Establish a secure tunnel** using the [Zerops VPN](/references/networking/vpn) to create an encrypted connection to your Zerops project
-2. **Obtain the [connection details](/postgresql/how-to/connect#connection-details)** from Zerops GUI
- - Environment variables are not available through VPN connections
-3. Connect with your **preferred database tool**
- - Do not use SSL/TLS (security is provided by the VPN)
- - **Desktop Database Tools** - popular GUI tools like pgAdmin, DBeaver, DataGrip, or any other PostgreSQL-compatible client will work with Zerops
- - **Command Line with psql** - connect using the standard PostgreSQL command-line client with the credential obtained above:
- ```sh
- psql -h [hostname] -U [user] -d [database_name]
- ```
-
-:::tip
- Try `{hostname}.zerops` instead of just `{hostname}` if you encounter any connection issues.
-:::
-
-## How to install and manage PostgreSQL plugins
-
-### Viewing available plugins
-You can list all available PostgreSQL plugins by running the following query *(superuser privileges not required)*:
+Any user can list the extensions available to install (superuser not required):
```sql
SELECT * FROM pg_available_extensions ORDER BY name;
```
-### Installing plugins (requires superuser)
-
-1. **Connect with superuser credentials**:
- - Use the `superUser` (user `postgres`) and `superUserPassword` environment variables from your PostgreSQL service
+### Install an extension (requires superuser)
-2. **Switch to your service database**:
- When logging in as the superuser, you're initially in the `postgres` database, not your service database.
+1. **Connect as the superuser.** Use the `superUser` (user `postgres`) and `superUserPassword` environment variables from your service.
+2. **Switch to your service database.** As the superuser you start in the `postgres` database, not your service database.
+3. **Create the extensions** you need:
-3. **Install required extensions**:
```sql
CREATE EXTENSION pg_stat_statements;
CREATE EXTENSION vector;
@@ -26829,16 +26522,14 @@ SELECT * FROM pg_available_extensions ORDER BY name;
```
:::warning
-Currently, it is not possible to add new plugins that are not already listed in `pg_available_extensions`.
+You can only install extensions already listed in `pg_available_extensions`; new extensions cannot be added.
:::
-When working with text search functionality, you'll need to reference the correct `stop`, `dict`, and `affix` files when creating dictionaries in your database. These files are essential for proper text search configuration.
-
-Zerops PostgreSQL includes the following dictionary files:
+When configuring text search dictionaries, reference the correct `stop`, `dict`, and `affix` files. Zerops PostgreSQL ships the following:
#### Available dictionary files
-**Stop word files** - used to remove common words that don't add significant meaning:
+**Stop word files**, used to remove common words that don't add significant meaning:
```
czech.stop
danish.stop
@@ -26859,7 +26550,7 @@ spanish.stop
swedish.stop
turkish.stop
```
-**Dictionary and affix files** - used for stemming and word normalization:
+**Dictionary and affix files**, used for stemming and word normalization:
```
cs_CZ.affix
cs_CZ.dict
@@ -26875,7 +26566,76 @@ sk_SK.dict
unaccent.rules
```
-For more information on text search dictionaries, refer to the [PostgreSQL documentation](https://www.postgresql.org/docs/16/textsearch-dictionaries.html).
+See the [PostgreSQL text search documentation](https://www.postgresql.org/docs/current/textsearch-dictionaries.html) for more.
+
+## Backups
+
+Zerops automatically backs up PostgreSQL services with full encryption. For scheduling, retention, tagging, storage quotas, and manual backups (and the CLI tools), see [Zerops Backups](/features/backup). This section covers the PostgreSQL specifics.
+
+### Backup format
+
+- **Format**: `.zip` containing per-schema `.dump` files
+- **Tooling**: `pg_dump`, custom format (`-Fc`), files named `schemaName.dump`
+- **Storage**: encrypted, in isolated object storage
+
+### Restore a backup
+
+1. **Download** the backup `.zip` from the GUI
+2. **Extract** it to get the per-schema `.dump` files
+3. **Prepare** the target (clean existing data, or use a fresh instance)
+4. **Restore** each schema with `pg_restore`. The dumps are custom format (`-Fc`), so plain `psql` can't read them:
+
+ ```sh
+ pg_restore -d [database_name] schemaName.dump
+ ```
+
+ See the [official PostgreSQL backup documentation](https://www.postgresql.org/docs/current/backup-dump.html) for restore options. To use a web UI like [Adminer](/postgresql/how-to/connect#database-management-tools), first convert a dump to plain SQL with `pg_restore -f schema.sql schemaName.dump` and import that SQL file.
+
+### High availability
+
+For HA services:
+- Backups run on a randomly selected healthy node
+- Other nodes stay operational during the backup
+- Manual backups typically run on the primary node
+
+### Best practices
+
+- Take a manual backup with a protected tag before migrations or major schema changes
+- Test your restore process periodically in a non-production environment
+- Monitor backup storage usage in the Project Overview
+- Use descriptive tags like `pre-migration-v2`
+- Mind schema dependency order when restoring
+
+## Export & import data
+
+To move data in and out, first connect to your database, either over the [VPN](/postgresql/how-to/connect#connect-via-zerops-vpn) for a local tool or via [Adminer](/postgresql/how-to/connect#database-management-tools) in the browser.
+
+### With Adminer or a desktop tool
+
+Once connected, use the tool's built-in export/import functions. See [Database management tools](/postgresql/how-to/connect#database-management-tools) and [Desktop tools and psql](/postgresql/how-to/connect#desktop-tools-and-psql).
+
+### With the command line
+
+Export data and schema with [pg_dump](https://www.postgresql.org/docs/current/app-pgdump.html):
+
+```sh
+pg_dump [database_name] > dump.sql
+```
+
+Import a plain SQL dump with `psql`:
+
+```sh
+psql [database_name] < dump.sql
+```
+
+For a custom-format dump (`pg_dump -Fc`, the format Zerops backups use), use [pg_restore](https://www.postgresql.org/docs/current/app-pgrestore.html):
+
+```sh
+pg_restore -d [database_name] dump.dump
+```
+
+See [Desktop tools and psql](/postgresql/how-to/connect#desktop-tools-and-psql) for how to reach the database and pass the password with `PGPASSWORD`.
+
----------------------------------------
@@ -26892,6 +26652,189 @@ For complete scaling details across all services, see [Automatic Scaling and Hig
PostgreSQL services use **vertical scaling** to adjust CPU, RAM, and disk resources within containers based on usage patterns. Unlike runtime services, PostgreSQL does not use horizontal scaling (adding/removing containers). Instead, PostgreSQL services use deployment modes for high availability.
+:::danger Scaling can briefly interrupt the service
+When scaling changes the service's resources, Zerops regenerates the PostgreSQL configuration and applies it with an automatic **reload**. If the new values require it, the service is **restarted** instead: rolling through the cluster in HA mode, a short outage in single mode.
+
+A restart is only needed when the granted RAM crosses a memory step: `256 MiB`, `512 MiB`, `1 GiB`, `2 GiB`, `4 GiB`, then multiples of `8 GiB`. Scaling within a step reloads only; to rule out restarts entirely, keep `minRam` and `maxRam` within one step.
+:::
+
+## Scaling profiles
+
+A **scaling profile** is the starting point for a PostgreSQL service. Each profile sets two things at once:
+
+1. **The autoscaling envelope**: the default minimum/maximum CPU, RAM, and disk, plus the free-resource headroom that controls how eagerly the autoscaler reacts.
+2. **The PostgreSQL configuration**: memory, WAL, planner, autovacuum, and replication settings tuned for a specific workload shape.
+
+A profile name combines a **workload type** with a **tier**, e.g. `oltp-production`.
+
+### Workload types
+
+
+
+
+
Type
+
Tuned for
+
Notes
+
+
+
+
+
OLTP
+
Transactional workloads like web apps, APIs, order processing, and auth. Short transactions and point lookups.
+
The default and most general-purpose type. Synchronous replication in HA.
+
+
+
OLAP
+
Analytical workloads like reporting, dashboards, and large aggregations.
+
Larger sort/hash memory, aggressive query parallelism, higher-resolution planner statistics. Asynchronous replication in HA (synchronous commits would throttle bulk loads).
+
+
+
WriteHeavy
+
High-volume ingestion like IoT telemetry, event logging, and metrics.
+
Commit batching, WAL compression, and aggressive autovacuum to keep up with write volume. Synchronous replication in HA.
+
+
+
+
+### Available profiles
+
+The tier part of the name sets the size of the autoscaling envelope (and, in HA, the replication topology). Which profiles you can pick depends on the deployment mode:
+
+
+
+
+
Profile
+
Mode
+
Use it for
+
+
+
+
+
oltp-hobby
+
Single
+
Side projects, prototypes, learning. Runs hot with minimal headroom to keep costs low.
+
+
+
oltp-staging
+
Single / HA
+
Staging, internal tools, early-stage apps. Moderate headroom. Default for single.
+
+
+
oltp-production
+
Single / HA
+
Business-critical transactional workloads. Generous headroom for traffic spikes. Default for HA.
+
+
+
oltp-enterprise
+
HA only
+
High-throughput OLTP at scale. Highest connection limits and the most aggressive headroom.
+
+
+
olap-production
+
Single / HA
+
Analytical / warehouse workloads.
+
+
+
writeheavy-production
+
Single / HA
+
Ingestion pipelines and write-heavy workloads.
+
+
+
custom
+
Single / HA
+
OLTP-based profile that lets you override individual PostgreSQL settings. See [Custom profile](#custom-profile).
+
+
+
+
+:::note
+In **HA** mode the OLTP and WriteHeavy profiles run with two synchronous standbys, so an acknowledged `COMMIT` survives a node failure. OLAP runs with asynchronous standbys, trading a small potential window of recent writes for ingest speed.
+:::
+
+### Setting a profile
+
+Set the profile when you create the service in the GUI, or with the `profile` field in your [import YAML](/postgresql/how-to/create#import-with-yaml):
+
+```yaml title="zerops-import.yaml"
+services:
+ - hostname: db
+ type: postgresql:ha@18
+ profile: oltp-production
+```
+
+If you don't set one, the default is used (`oltp-staging` for single, `oltp-production` for HA). **The profile can be changed at any time** in the GUI.
+
+### Overriding the autoscaling envelope
+
+The resource limits a profile sets are defaults. You can override any of them (CPU mode, min/max CPU/RAM/disk, and the free-resource thresholds) **without switching to the custom profile**, using the [`verticalAutoscaling`](/postgresql/how-to/create#service-parameters) block in your import YAML or the **Automatic scaling configuration** in the GUI. These overrides apply on top of any profile.
+
+### Custom profile
+
+The `custom` profile uses the **OLTP** tuning as its base and additionally lets you override individual PostgreSQL configuration values through `profileOverrides`:
+
+```yaml title="zerops-import.yaml"
+services:
+ - hostname: db
+ type: postgresql:single@18
+ profile: custom
+ profileOverrides:
+ random_page_cost: 1.1
+ default_statistics_target: 200
+ autovacuum_max_workers: 5
+ work_mem: 67108864 # bytes (64 MiB)
+ autovacuum_naptime: 120000000000 # nanoseconds (2 minutes)
+```
+
+Settings that Zerops derives from the container's resources or that are required for the managed cluster to operate (`shared_buffers`, `max_connections`, JIT on/off, the pooler limits, and the replication/Patroni settings) are managed automatically and **cannot** be overridden.
+
+Refer to the [PostgreSQL configuration documentation](https://www.postgresql.org/docs/current/runtime-config.html) for the meaning and valid range of each parameter, but **mind the units**: override values are plain numbers, with memory/disk sizes given in **bytes** and durations in **nanoseconds**. This differs from PostgreSQL's own conventions (kB, milliseconds, seconds). The table below shows which unit each key takes.
+
+#### Overridable configuration keys
+
+
+
+
+
Key
+
Type / unit
+
Description
+
+
+
+
work_mem
bytes
Memory per sort/hash operation before spilling to disk
+
hash_mem_multiplier
integer
Multiplier applied to work_mem for hash-based operations
+
maintenance_work_mem
bytes
Memory for maintenance operations (VACUUM, CREATE INDEX, REINDEX)
+
autovacuum_work_mem
bytes
Memory used by each autovacuum worker
+
temp_buffers
bytes
Per-session memory for accessing temporary tables
+
temp_file_limit
bytes
Maximum total disk space a session may use for temporary files
+
effective_cache_size
bytes
Planner's assumption of total cache available (shared_buffers + OS cache)
+
effective_io_concurrency
integer
Estimated number of concurrent disk I/O operations the storage can handle
+
maintenance_io_concurrency
integer
Concurrent disk I/O operations for maintenance (VACUUM, prefetch)
+
random_page_cost
number
Planner's estimated cost of a non-sequential page fetch
+
default_statistics_target
integer
Default number of samples used by ANALYZE for column statistics
+
jit_above_cost
number
Query plan cost above which JIT compilation is considered
+
max_worker_processes
integer
Maximum number of background worker processes
+
max_parallel_workers
integer
Maximum parallel workers that can be active at one time cluster-wide
+
max_parallel_workers_per_gather
integer
Maximum parallel workers a single Gather node can start
+
max_parallel_maintenance_workers
integer
Maximum parallel workers for maintenance (CREATE INDEX, VACUUM)
+
wal_compression
string
Compression algorithm for full-page WAL images (off/pglz/lz4/zstd)
+
wal_buffers
bytes
Shared memory used to buffer WAL data not yet written to disk
+
wal_writer_delay
nanoseconds
Interval at which the WAL writer flushes WAL to disk
+
wal_writer_flush_after
bytes
WAL bytes written before the WAL writer triggers a flush
+
commit_delay
nanoseconds
Artificial delay before a WAL flush during commit to batch concurrent commits
+
commit_siblings
integer
Minimum number of concurrent open transactions required for commit_delay to apply
+
max_wal_size
bytes
Soft upper limit on WAL size that triggers a checkpoint
+
min_wal_size
bytes
Minimum WAL size kept for future reuse before recycling segments
+
autovacuum_max_workers
integer
Maximum autovacuum worker processes running concurrently
+
autovacuum_naptime
nanoseconds
Delay between autovacuum runs on any given database
+
autovacuum_vacuum_scale_factor
number
Fraction of table size added to the autovacuum threshold
+
autovacuum_analyze_scale_factor
number
Fraction of table size added to the auto-analyze threshold
+
autovacuum_vacuum_cost_delay
nanoseconds
Cost-based delay inserted by autovacuum between I/O operations
+
autovacuum_vacuum_cost_limit
integer
Accumulated vacuum cost at which an autovacuum worker sleeps
+
vacuum_cost_delay
nanoseconds
Cost-based delay inserted by manual VACUUM between I/O operations (0 disables)
+
idle_in_transaction_session_timeout
nanoseconds
Terminate sessions that stay idle in a transaction longer than this
+
+
+
## Configure scaling
You can configure scaling settings:
@@ -26910,20 +26853,13 @@ You can configure scaling settings:
- **Raise the minimum** when you need guaranteed baseline performance
- **Set minimum = maximum** to disable automatic scaling for that specific resource
-**Deployment mode**: Choose the reliability configuration for your PostgreSQL service:
+**Deployment mode** (single container vs. highly available) is chosen when the service is created and cannot be changed later. See [Deployment modes](/postgresql/overview#deployment-modes).
-- **Single Container**: One container with vertical scaling only. Suitable for development environments.
-- **Highly Available**: Multiple containers with built-in redundancy. Recommended for production environments.
+### High availability
-:::warning
-Deployment mode cannot be changed after service creation.
-:::
-
-:::tip HA Mode
-In HA mode, a dedicated read replica port (`5433`) is available for routing read queries to replicas. This improves performance by distributing load across all database containers. See [Connection Parameters](/postgresql/how-to/connect#connection-parameters) for details.
-:::
+In HA mode, Zerops runs a 3-node cluster across separate physical machines, fronted by two database proxy containers (free of charge) that route traffic. When a container fails, Zerops automatically replaces it on a different machine and synchronizes data from the healthy copies.
-When a container fails in HA mode, Zerops automatically replaces it with a new container on a different physical machine and synchronizes data from healthy copies.
+A dedicated **read-replica port (`5433`)** lets you route read-only queries to the replicas, taking load off the primary and improving throughput. See [Connection parameters](/postgresql/how-to/connect#connection-parameters) and [Connection ports and TLS](/postgresql/how-to/connect#connection-ports-and-tls).
### Advanced settings
@@ -26949,7 +26885,7 @@ Navigate to your PostgreSQL service and select **Service containers & Overview**
## Technical details
-Zerops monitors database usage and automatically adjusts resources based on predefined thresholds and timing parameters. The scaling behavior follows the same principles as other services in the platform.
+Zerops monitors database usage and automatically adjusts resources based on predefined thresholds and timing parameters. The scaling behavior follows the same principles as other services on the platform.
For complete technical specifications including:
- Resource monitoring intervals and thresholds
@@ -26987,39 +26923,82 @@ See [Resource Scaling Behavior](/features/scaling#resource-scaling-behavior) in
# Postgresql > Overview
-[PostgreSQL ↗](https://www.postgresql.org/) is a powerful, open source object-relational database system with over 35 years of active development that has earned it a strong reputation for reliability, feature robustness, and performance.
+[PostgreSQL ↗](https://www.postgresql.org/) is a powerful, open-source object-relational database with a 35-year track record for reliability, robustness, and performance. On Zerops it runs as a **fully managed service**: automated encrypted backups, vertical autoscaling, optional high availability with automatic failover, and a configuration tuned to your workload, with no server to administer.
-## Feature Highlights
+## Quick start
-- [Create PostgreSQL service](/postgresql/how-to/create) — Start with creating a PostgreSQL service using GUI or zCLI.
-- [Import config file](/postgresql/how-to/create#full-example) — Use an example config file to import your own app.
+Add a PostgreSQL service to your project with a `zerops-import.yaml`:
-### Connect to PostgreSQL service
+```yaml title="zerops-import.yaml"
+services:
+ - hostname: db
+ # type is postgresql:{mode}@{version}
+ type: postgresql:single@18
+ # workload-tuned autoscaling profile
+ profile: oltp-staging
+```
-- [Manage users and databases](/postgresql/how-to/manage)
-- [Connect from the same project](/postgresql/how-to/connect#connect-from-services-in-the-same-project)
-- [Connect remotely](/postgresql/how-to/connect#connect-remotely)
+Import it with the zCLI:
-### Others
+```bash
+zcli project service-import zerops-import.yaml
+```
-- [Scale postgresql service](/postgresql/how-to/scale)
-- [Export and import data](/postgresql/how-to/export-import-data)
+The moment the container boots you have a working database. See [Create & import](/postgresql/how-to/create) for the GUI flow and the full configuration reference.
-## When in doubt, reach out
+## What you get out of the box
-Don't know how to start or got stuck during the process? You might not be the first one, visit the FAQ section to find out.
+- **A default database and user**, both named `db` (encoding `UTF8`, locale `C.UTF-8`), with a randomly generated password.
+- **Connection details and environment variables**, including hostname, ports, and ready-made connection strings. See [Connect](/postgresql/how-to/connect).
+- **Workload-tuned configuration** via the [scaling profile](/postgresql/how-to/scale#scaling-profiles) you choose (OLTP, OLAP, or write-heavy).
+- **Automated, encrypted backups** stored in isolated object storage. See [Manage & data](/postgresql/how-to/manage#backups).
+- **Vertical autoscaling** that grows and shrinks CPU, RAM, and disk with real usage.
-In case you haven't found an answer (and also if you have), we and our community are looking forward to hearing from you on Discord.
+### Supported versions
-Have you build something that others might find useful? Don't hesitate to share your knowledge!
+## Deployment modes
-- [FAQ](/postgresql/faq) — Most common questions in one place.
-- [Discord](https://discord.com/invite/WDvCZ54) — Join our core team and Zerops community on Discord. Ask questions and share your tips with other members.
+Choose the reliability model when you create the service. It is set through the service `type` and is **fixed for the life of the service**.
-## Popular Guides
+
+
+
+
Mode
+
type
+
What it is
+
+
+
+
+
Highly Available
+
postgresql:ha@N
+
A 3-node cluster spread across separate physical machines, with data stored redundantly, automatic failover, and a read-replica port. Recommended for production.
+
+
+
Single container
+
postgresql:single@N
+
One container, so lower cost but no redundancy. Data since the last backup is lost if it fails. Good for development and non-critical workloads.
+
+
+
-- [zCLI](/references/cli) — Get even more out of Zerops with the zCLI command line tool.
-- [Zerops VPN](/references/networking/vpn) — Connect to your services easily with Zerops VPN.
+:::tip
+In HA mode, route read-only queries to the replica port (`5433`) to take load off the primary. See [Scale & profiles](/postgresql/how-to/scale#high-availability) and [Connection ports and TLS](/postgresql/how-to/connect#connection-ports-and-tls).
+:::
+
+## Next steps
+
+- [Create & import](/postgresql/how-to/create) — Create a service in the GUI or with zerops-import.yaml.
+- [Connect](/postgresql/how-to/connect) — Ports, TLS, pooling, env vars, VPN, and management tools.
+- [Scale & profiles](/postgresql/how-to/scale) — Autoscaling, workload profiles, and high availability.
+- [Manage & data](/postgresql/how-to/manage) — Users, extensions, backups, and export/import.
+
+## Need help?
+
+Stuck, or want to share what you built? Our core team and community are on Discord.
+
+- [Discord](https://discord.com/invite/WDvCZ54) — Join the Zerops community on Discord. Ask questions and share your tips.
+- [zCLI](/references/cli) — Get more out of Zerops with the command-line tool.
----------------------------------------
@@ -28514,98 +28493,32 @@ For advanced configurations or custom requirements:
----------------------------------------
-# Quickstart
-
-
-With this guide, we'll go from zero to a live app with a managed database, a public URL, and auto-deploy on every git push. Takes about 5 minutes.
-
-Here's what we're building together: a page with a button that says **"I followed the Zerops quickstart"**. Every developer who finishes this guide and clicks it gets counted. It's a real app: Express backend, PostgreSQL database, static frontend, all running on **Zerops**.
-
-**See it live before you build it:** [app-25be-3000.prg1.zerops.app](https://app-25be-3000.prg1.zerops.app/)
-[Video: /vids/quickstart-app.webm](/vids/quickstart-app.webm)
-
-:::info Two ways to use this guide
-**Just want to see how Zerops works?** Follow along as-is. The feedback app covers everything: frontend, backend, managed database, private networking, auto-deploy.
-
-**Already have an app?** Pick a recipe matching your stack in Step 1 instead of Node.js. The steps are identical. Then in Step 5, replace the feedback app code with your own. The `zerops.yml` structure stays the same.
-:::
-
-:::tip Before we start
-Sign up at [app.zerops.io](https://app.zerops.io). You get $15 in promo credits on signup, no credit card needed. Verify your account with a $10 payment and get an additional $50, bringing your total to $65 in credits. A simple app with a database costs roughly $3-5/month, so credits go a long way.
-:::
-
----
-
-## 1. Deploy the recipe
-
-Go to [app.zerops.io/recipes](https://app.zerops.io/recipes). No account needed, the recipes page works even when you're not logged in.
-
-For this guide, find and click **Node.js**. You'll see **Node.js Hello World**, a Node.js app with Express connected to a PostgreSQL database.
-
-Hit **Deploy nodejs-hello-world-small-prod**. If you're not signed in yet, Zerops creates your account and kicks off the deploy in the same step.
-
-:::info What's a recipe?
-A recipe is a working app with infrastructure already configured: managed database, environment variables, `zerops.yaml`, everything connected. It's a correct starting point, not a finished product. We'll swap the code out in Step 5.
-:::
-
-:::note Want a different stack?
-Everything in this guide works the same way for Next.js, Python, Go, Laravel, and more. Just pick the recipe that matches your stack instead. The steps are identical.
-:::
-
----
-
-## 2. Watch it build
-
-After hitting Deploy, you'll land on the dashboard. On the right side you'll see a pipeline already running.
-
-It goes through these steps in real time:
-
-1. Initializing build container
-2. Running build commands from `zerops.yaml`
-3. Creating app version and upgrading service
-4. Done
-
-:::tip Good to know
-The build container is **temporary and free**. Zerops spins it up, runs your build, saves the output, and deletes it. You're only billed for the running app.
-:::
-
-Once the pipeline finishes, your app is live.
+# Quickstart > Quickstart Going Further
---
+title: Deploy Your First App
+description: Swap the recipe for your own code, deploy the feedback app, and connect GitHub for auto-deploy.
+sidebar_label: Deploy Your First App
+slug: /quickstart/deploy-your-first-app
-## 3. See what got created
-
-Click into your new project. You'll see:
-
-- **app**: your Node.js service, already running
-- **db**: a managed PostgreSQL database, already running
-- **Project core**: load balancer, firewall, logger, all managed by Zerops
-
-All of these are already talking to each other on a private network. Nothing to configure.
-
-:::tip Coming from Vercel and Supabase?
-You're probably used to copying a long database URL and pasting it as an environment variable. On Zerops, services talk to each other by hostname. `DB_HOST` is just `db`, not a URL. You still set credentials in `zerops.yaml`, Zerops fills in the values. No connection strings to manage.
-:::
-
+custom_edit_url: null
---
-## 4. Open your live app
+You've got a live app running on Zerops. Now let's make it actually yours.
-The Zerops subdomain is already enabled when you deploy from a recipe. Click into your **app** service and open the URL. It looks something like `app-2437-3000.prg1.zerops.app`.
+This page walks you through deploying the feedback app we showed you at the top of the quickstart - a Node.js app with a PostgreSQL database, auto-deploy on every git push, and a wall of everyone who's made it through. If you'd rather skip straight to your own code, there's a note at the bottom for that.
-The default recipe app will be there. Next step, we replace it with the feedback app.
+### Deploy the feedback app
-When you're ready for production, point a custom domain at your app. Zerops handles the SSL certificate automatically.
-
----
+Start from the recipe template. Click the link below, pick a name for your repo, and hit **Create repository**:
-## 5. Swap in the feedback app
+**[github.com/new?template_name=recipe-nodejs&template_owner=zeropsio](https://github.com/new?template_name=recipe-nodejs&template_owner=zeropsio)**
-Clone the recipe repo and open it in your editor:
+Then clone your new repo and install dependencies:
```bash
-git clone https://github.com/zeropsio/recipe-nodejs
-cd recipe-nodejs
+git clone https://github.com/your-username/your-repo.git
+cd your-repo
npm install
```
@@ -28629,6 +28542,7 @@ export const connectDB = async () => {
await client.query(`
CREATE TABLE IF NOT EXISTS clicks (
id SERIAL PRIMARY KEY,
+ seed INTEGER NOT NULL,
clicked_at TIMESTAMPTZ DEFAULT NOW()
)
`);
@@ -28647,17 +28561,24 @@ app.use(express.static(path.join(__dirname, '../public')));
app.get('/count', async (_, res) => {
const client = await connectDB();
- const result = await client.query('SELECT COUNT(*) FROM clicks');
+ const result = await client.query(
+ 'SELECT seed FROM clicks ORDER BY id ASC LIMIT 20'
+ );
+ const countResult = await client.query('SELECT COUNT(*) FROM clicks');
await client.end();
- res.json({ count: parseInt(result.rows[0].count) });
+ res.json({
+ count: parseInt(countResult.rows[0].count),
+ seeds: result.rows.map((r) => r.seed),
+ });
});
app.post('/click', async (_, res) => {
const client = await connectDB();
- await client.query('INSERT INTO clicks DEFAULT VALUES');
- const result = await client.query('SELECT COUNT(*) FROM clicks');
+ const seed = Math.floor(Math.random() * 1000000);
+ await client.query('INSERT INTO clicks (seed) VALUES ($1)', [seed]);
+ const countResult = await client.query('SELECT COUNT(*) FROM clicks');
await client.end();
- res.json({ count: parseInt(result.rows[0].count) });
+ res.json({ count: parseInt(countResult.rows[0].count), seed });
});
app.get('/status', (_, res) => {
@@ -28669,6 +28590,8 @@ export default app;
**Create a `public/` folder** at the repo root and add `public/index.html`:
+
+
The `zerops.yml` already exists in the repo. Update `deployFiles` to include the `public` folder:
```yaml
@@ -28921,9 +28852,11 @@ zerops:
envVariables:
NODE_ENV: production
DB_NAME: db
- DB_HOST: db
- DB_USER: db
- DB_PASS: ${db_password}
+ DB_HOST: ${db_hostname}
+ DB_USER: ${db_user}
+ DB_PASSWORD: ${db_password}
+ # or use the full connection string:
+ # DB_CONNECTION_STRING: ${db_connectionString}
start: npm run start:prod
healthCheck:
httpGet:
@@ -28931,22 +28864,23 @@ zerops:
path: /status
```
-Before pushing, point the repo at your own GitHub remote:
+:::tip How Zerops env variables work
+Zerops automatically generates credentials for every managed service. The variable names are derived from the service hostname — so if your database service is named `db`, the variables are `${db_hostname}`, `${db_user}`, `${db_password}`, and `${db_connectionString}`. If you named it `postgres` instead, they'd be `${postgres_hostname}`, `${postgres_password}`, and so on.
+:::
+
+Push to your repo and connect GitHub in the next section.
```bash
-git remote set-url origin https://github.com/your-username/your-repo.git
-git push -u origin main
+git add .
+git commit -m "add feedback app"
+git push
```
-Then move on to Step 6 to connect it to Zerops.
-
:::note Want to build something else instead?
-Skip the feedback app. Clone the repo, keep the `zerops.yml` as-is, and replace `src/app.ts` with your own routes. The DB connection, build pipeline, and env variables all stay the same.
+Skip the feedback app. Pick the recipe matching your stack from [app.zerops.io/recipes](https://app.zerops.io/recipes), add a `zerops.yaml` to your repo root copying the structure from the recipe, and adjust `buildCommands`, `deployFiles`, and `start` for your stack. The database env variables (`${db_hostname}`, `${db_user}`, `${db_password}`) stay the same regardless of what you're building.
:::
----
-
-## 6. Connect GitHub and auto-deploy
+### Connect GitHub and auto-deploy
1. Click into your **app** service
2. Scroll down to **Pipelines & CI/CD settings**
@@ -28959,25 +28893,13 @@ That's it. Every push to main now builds and deploys automatically. Zero downtim
You can also trigger deploys manually with the Zerops CLI: `zcli push`.
----
+### Add yourself to the list
-## You're live 🎉
+Deployed the feedback app? Open your live app URL and click **"I followed the Zerops quickstart"**. You'll show up alongside everyone else who's made it through.
-Open your app URL. You should see the feedback button app: a big button, a live click count pulled from the database, all running on Zerops.
+Check out everyone who's already made it: [app-25be-3000.prg1.zerops.app](https://app-25be-3000.prg1.zerops.app/)
-Here's what's running:
-
-- A real app on production infrastructure
-- A managed PostgreSQL database with no setup, no connection strings, no SDK
-- Auto-deploy on every git push with zero downtime
-- All services on a private network, secure by default
-- Full Linux containers you can SSH into and install anything on
-
-None of the infrastructure needed manual configuration.
-
----
-
-## If something breaks
+### If something breaks
Got a 502 or an app crash on startup? Start here.
@@ -28986,12 +28908,10 @@ Got a 502 or an app crash on startup? Start here.
Two things come up most often on a first deploy:
:::tip Debug locally with VPN
-Install zcli first (see [CLI reference](/references/cli)), then run `zcli vpn up [your-project-id]` and your laptop joins the project's private network. You can connect to `db:5432` directly from your local machine using TablePlus, psql, or any database client. Disable SSL when connecting over VPN, security is handled by the tunnel itself. If `db` doesn't resolve, try `db.zerops` instead.
+Install zcli first (see [CLI reference](/references/cli)), then run `zcli vpn up [your-project-id]` and your machine joins the project's private network. You can connect to `db:5432` directly from your local machine using TablePlus, psql, or any database client. You can disable SSL when connecting over VPN - the tunnel itself handles security either way. If `db` doesn't resolve, try `db.zerops` instead.
:::
----
-
-## What's next
+### What's next
- **[SSH into your container](/references/networking/ssh)**: `zcli service shell [service-name]` for full Linux access
- **[Custom domain](/references/networking/public-access)**: add your domain, SSL is automatic
@@ -29004,6 +28924,102 @@ Jump into the [Zerops Discord](https://docs.zerops.io/discord). The community is
:::
+----------------------------------------
+
+# Quickstart > Quickstart
+
+
+In the next 5 minutes, we'll go from zero to a live app: a managed database, a public URL, and auto-deploy on every git push, all set up for you.
+
+Here's what we're building together: a page with a button that says **"I followed the Zerops quickstart"**. Every developer who finishes this guide and clicks it gets added to the wall. It's a real app, Express backend, PostgreSQL database, static frontend, running entirely on **Zerops**.
+
+Curious what you'll end up with? Here it is, live: [app-25be-3000.prg1.zerops.app](https://app-25be-3000.prg1.zerops.app/)
+
+[Video: /vids/quickstart-app.webm](/vids/quickstart-app.webm)
+
+:::tip Before we start
+Sign up at [app.zerops.io](https://app.zerops.io). You get $15 in promo credits on signup, no credit card needed. Verify your account with a $10 payment and get an additional $50, bringing your total to $65 in credits. A simple app with a database costs roughly $3-5/month, so credits go a long way.
+:::
+
+---
+
+## 1. Deploy the recipe
+
+Go to [app.zerops.io/recipes](https://app.zerops.io/recipes). No account needed, the recipes page works even when you're not logged in.
+
+For this guide, find and click **Node.js**. You'll see **Node.js Hello World**, a Node.js app with Express connected to a PostgreSQL database.
+
+Hit **Deploy nodejs-hello-world-small-prod**. If you're not signed in yet, Zerops creates your account and kicks off the deploy in the same step.
+
+:::info What's a recipe?
+A recipe is a working app with infrastructure already configured: managed database, environment variables, `zerops.yaml`, everything connected. It's a correct starting point, not a finished product.
+:::
+
+---
+
+## 2. Watch it build
+
+After hitting Deploy, you'll land on the dashboard. On the right side you'll see a pipeline already running.
+
+It goes through these steps in real time:
+
+1. Initializing build container
+2. Running build commands from `zerops.yaml`
+3. Creating app version and upgrading service
+4. Done
+
+:::tip Good to know
+The build container is **temporary and free**. Zerops spins it up, runs your build, saves the output, and deletes it. You're only billed for the running app.
+:::
+
+Once the pipeline finishes, your app is live.
+
+---
+
+## 3. See what got created
+
+Click into your new project. You'll see:
+
+- **app**: your Node.js service, already running
+- **db**: a managed PostgreSQL database, already running
+- **Project core**: load balancer, firewall, logger, all managed by Zerops
+
+All of these are already talking to each other on a private network. Nothing to configure.
+
+---
+
+## 4. Open your live app
+
+The Zerops subdomain is already enabled when you deploy from a recipe. Click into your **app** service and open the URL.
+
+The default recipe app will be there.
+
+When you're ready for production, point a custom domain at your app. Zerops handles the SSL certificate automatically.
+
+---
+
+## You're live 🎉
+
+Here's what's running:
+
+- A real app on production infrastructure
+- A managed PostgreSQL database, ready to use
+- All services on a private network, secure by default
+- Full Linux containers for your app — SSH in anytime, install anything
+
+None of the infrastructure needed manual configuration.
+
+:::note Stuck?
+Jump into the [Zerops Discord](https://docs.zerops.io/discord). The community is active and the team is there.
+:::
+
+---
+
+Your app is live and the infrastructure is running.
+
+**Next up:** Swap the recipe for the feedback app we showed you, connect GitHub for auto-deploy, and add yourself to the wall. [Deploy your first app →](/quickstart/deploy-your-first-app)
+
+
----------------------------------------
# References > Api
@@ -29155,7 +29171,7 @@ Execute following command in PowerShell:
irm https://zerops.io/zcli/install.ps1 | iex
```
-zCLI will be installed inside `C:\Program Files\` or `C:\Program Files (x86)\`
+zCLI will be installed inside `C:\Users\\.zerops\zcli.exe`.
### Using Package Managers
@@ -29304,6 +29320,8 @@ As an alternative to direct integration, you can use GitHub Actions to manage yo
Create a new file at `.github/workflows/deploy.yaml` in your repository:
+ Basic example:
+
```yaml
name: Deploy to Zerops
@@ -29318,7 +29336,32 @@ As an alternative to direct integration, you can use GitHub Actions to manage yo
steps:
- name: Checkout code
- uses: actions/checkout@v3
+ uses: actions/checkout@v7
+
+ - name: Deploy with Zerops
+ uses: zeropsio/actions@main
+ with:
+ access-token: ${{ secrets.ZEROPS_TOKEN }}
+ service-id: your-service-id
+ ```
+
+ Example with tag matching:
+
+ ```yaml
+ name: Deploy to Zerops on Tag
+
+ on:
+ push:
+ tags:
+ - 'v[0-9]+\.[0-9]+\.[0-9]+' # Example: v1.0.0, v2.3.4
+
+ jobs:
+ deploy:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v7
- name: Deploy with Zerops
uses: zeropsio/actions@main
@@ -29350,6 +29393,38 @@ Keep your access token secure and never commit it directly to your repository. T
For more information about GitHub Actions, refer to the [official GitHub Actions documentation](https://docs.github.com/en/actions).
+---
+
+## Advanced: Full Lifecycle Automation
+
+:::tip Reference repository
+A complete, working example of this pattern is available at [zerops-showcase-deploy](https://github.com/zeropsio/zerops-showcase-deploy), a deployment orchestrator built entirely on GitHub Actions and the Zerops API, no Zerops CLI required. Clone it as a starting point for your own multi-environment pipeline.
+:::
+
+The workflow above deploys a single service on push to one branch. This reference repo extends that into a full lifecycle: every feature branch gets its own isolated environment, every PR gets a preview, and stage and prod stay permanent with their data intact across deploys.
+
+### What it sets up
+
+| You do this | Pipeline creates | Lifetime |
+| --------------------------- | -------------------------------------------------- | --------------------------- |
+| Push to a feature branch | A full isolated environment | Until the branch is deleted |
+| Open/update a PR to `stage` | A full isolated preview environment | Until the PR is closed |
+| Merge a PR into `stage` | Redeploys app code to the shared stage environment | Permanent, data preserved |
+| Push/merge to `main` | Redeploys app code to production | Permanent, data preserved |
+| Push/merge to `main` | Redeploys app code to production | Permanent, data preserved |
+
+Permanent environments keep their databases, caches, queues, and storage across every deploy, only the application code gets updated. Ephemeral environments are created from scratch and torn down automatically when no longer needed, so every developer gets their own isolated copy of the full stack without manual cleanup.
+
+PR previews require a `stage` branch as the pull request target; the repo's setup guide covers creating it.
+
+### How it differs from the basic workflow above
+
+The basic GitHub Actions setup deploys one service to one Zerops project. This pattern manages entire **projects** programmatically instead: it checks whether a project for a given branch or PR already exists, creates one from an import template if not, and deploys to it. The key safeguard is that it never deletes or recreates infrastructure on permanent environments, only ephemeral ones get torn down. This is a reference implementation, not a Zerops-managed service, so you own the repo, the GitHub Actions minutes, and the Zerops API calls it makes on your behalf.
+
+:::tip Full setup guide
+Required secrets, customizing the project template for your own services, and the complete workflow reference are all covered in the [repo's README](https://github.com/zeropsio/zerops-showcase-deploy#readme).
+:::
+
----------------------------------------
@@ -29400,8 +29475,6 @@ Zerops requires full access to configure webhooks and download your code. Your s
4. **Finalize Setup**
- Confirm your settings to complete the integration
--->
-
---
### Skip the automatic pipeline once
@@ -31072,10 +31145,10 @@ Versions listed on the same line are aliases of the same underlying version.
@@ -31304,7 +31377,7 @@ services:
override: false
# REQUIRED. Name of your other service
- hostname: teststorage1
- type: shared-storage
+ type: shared-storage:ha
...
```
@@ -31498,7 +31571,7 @@ services:
# A second, simpler service example
- hostname: teststorage1
- type: shared-storage
+ type: shared-storage:ha
```
This example includes all possible configuration options for Zerops services. Not all options are required or applicable to every service type. The example shows two services in the same YAML file: a fully configured Node.js API service and a simpler static frontend service.
@@ -31576,6 +31649,23 @@ Defines the operation mode of the service.
string (URL)
A URL of a Github or Gitlab repository used for a one-time build of your service.
+ The repository must be publicly accessible — a private repository fails at clone
+ time (the build ends FAILED with no build log). Use the plain repository URL
+ without a trailing `.git` suffix, which fails the same way.
+
+
+
+
startWithoutCode
+
boolean
+
+ Default: `false`
+
+ Set `true` to start a runtime service's containers without waiting for a first
+ deploy. Useful when the code arrives later (a subsequent pipeline run), or when
+ you want a live container to [SSH](/references/networking/ssh) into and play
+ around in — once your setup steps work, version them into
+ [`run.prepareCommands`](/zerops-yaml/specification#preparecommands--1) so they
+ persist across container replacements.
@@ -31855,6 +31945,8 @@ services:
The `mount:` parameter allows you to mount a shared storage (which should be created inside the project) to the service.
+The `buildFromGit` requirement exists because an import without it only creates an empty service shell with nothing deployed yet, so there is nothing to mount the storage into. With `buildFromGit`, the import also builds and deploys the repository, and the mount is applied to the deployed service. If you import a service without `buildFromGit`, deploy your application first and then connect the shared storage [in the GUI](/shared-storage/how-to/connect).
+
### Service Nginx Configuration
The nginx configuration defines the nginx settings for the service.
@@ -33455,6 +33547,49 @@ Configure security-related options:
**Best Practice:** Keep `server_tokens` disabled to avoid revealing server information.
+## Forwarded Headers
+
+The balancer sets the following headers on every request proxied to your application. They are always set by the balancer itself, overwriting any values sent by the client, so your application can safely trust them:
+
+
+
+
+
Header
+
Value
+
+
+
+
+
Host
+
Host requested by the client
+
+
+
X-Real-IP
+
Client IP address
+
+
+
X-Forwarded-For
+
Client IP address appended to any existing chain
+
+
+
X-Forwarded-Proto
+
Protocol the client used (`http` or `https`)
+
+
+
X-Forwarded-Host
+
Host requested by the client
+
+
+
X-Forwarded-Port
+
Port the client connected to
+
+
+
+
+:::note Security
+The `Forwarded` (RFC 7239) and `Proxy` headers are stripped from incoming requests and never reach your application. Configure your framework to read the `X-Forwarded-*` headers above instead.
+:::
+
## Advanced Routing Features
The L7 HTTP Balancer supports sophisticated routing beyond basic domain mapping.
@@ -34020,6 +34155,12 @@ Before getting started, ensure you have:
- [zCLI](/references/cli) (serves as the WireGuard client)
- A Zerops project with at least one service
+:::info Windows prerequisite
+zCLI VPN relies on WireGuard, which requires the **Virtual Machine Platform**
+feature to be enabled on Windows 11. Open `optionalfeatures.exe`, check
+**Virtual Machine Platform**, then reboot.
+:::
+
## Usage
You can interact with services within your project and even establish SSH connection to your services after connecting to project through VPN.
@@ -34197,7 +34338,7 @@ openssl s_client -connect :6380 -verify_return_error How To > Build Pipeline
+# Ruby > How To > Build Pipeline
-Zerops provides a customizable build and runtime environment for your Rust application.
+Zerops provides a customizable build and runtime environment for your Ruby application.
## Add zerops.yaml to your repository
@@ -35554,54 +35695,65 @@ zerops:
# ==== how to build your application ====
build:
# REQUIRED. Set the base technology for the build environment:
- base: rust@latest
+ base: ubuntu/ruby@4.0
- # OPTIONAL. Set the operating system for the build environment.
- # os: ubuntu
-
- # OPTIONAL. Customize the build environment by installing additional packages
+ # OPTIONAL. Customise the build environment by installing additional packages
# or tools to the base build environment.
# prepareCommands:
- # - sudo apt-get something
- # - curl something else
+ # - sudo apt-get update
+ # - sudo apt-get install -y some-package
+
+ # OPTIONAL. Set the env variables for the build environment.
+ # BUNDLE_PATH makes Bundler install gems into ./vendor/bundle
+ # so they can be deployed together with your application.
+ envVariables:
+ BUNDLE_PATH: vendor/bundle
# OPTIONAL. Build your application
buildCommands:
- - cargo b --release
+ - bundle install
# REQUIRED. Select which files / folders to deploy after
# the build has successfully finished
deployFiles:
- - target/release/~app
+ - ./vendor
+ - ./Gemfile
+ - ./Gemfile.lock
+ - ./config.ru
+ - ./src
# OPTIONAL. Which files / folders you want to cache for the next build.
# Next builds will be faster when the cache is used.
- # cache: file.txt
+ cache: vendor
# ==== how to run your application ====
run:
# OPTIONAL. Sets the base technology for the runtime environment:
- base: rust@latest
+ base: ubuntu/ruby@4.0
# OPTIONAL. Sets the internal port(s) your app listens on:
ports:
# port number
- port: 8080
- # OPTIONAL. Customize the runtime Rust environment by installing additional
- # dependencies to the base Rust runtime environment.
+ # OPTIONAL. Customise the runtime Ruby environment by installing additional
+ # dependencies to the base Ruby runtime environment.
# prepareCommands:
- # - sudo apt-get something
- # - curl something else
+ # - sudo apt-get update
+ # - sudo apt-get install -y some-package
# OPTIONAL. Run one or more commands each time a new runtime container
# is started or restarted. These commands are triggered before
- # your Rust application is started.
+ # your Ruby application is started.
# initCommands:
# - rm -rf ./cache
- # REQUIRED. Your Rust application start command
- start: ./app
+ # OPTIONAL. Set the env variables for the runtime environment.
+ envVariables:
+ BUNDLE_PATH: vendor/bundle
+
+ # REQUIRED. Your Ruby application start command
+ start: bundle exec puma -p 8080 -b tcp://0.0.0.0
```
The top-level element is always `zerops`.
@@ -35640,13 +35792,13 @@ Each service configuration contains at least the `run` section. Optional `build`
_REQUIRED._ Sets the base technology for the build environment.
-Following options are available for Rust builds:
+Following options are available for Ruby builds:
-- `rust@1`, `rust@latest`, `rust@stable`
-- `rust@1.86`
-- `rust@1.80`
-- `rust@1.78`
-- `rust@nightly`
+- `alpine/ruby@4.0`, `ubuntu/ruby@4.0`, `alpine/ruby@latest`, `ubuntu/ruby@latest`
+- `alpine/ruby@3.4`, `ubuntu/ruby@3.4`
+- `alpine/ruby@3.3`, `ubuntu/ruby@3.3`
+
+The base value always includes the operating system prefix (`ubuntu/` or `alpine/`). The `@latest` tag is an alias that points to Ruby 4.0.
```yaml
zerops:
@@ -35655,13 +35807,14 @@ zerops:
# ==== how to build your application ====
build:
# REQUIRED. Sets the base technology for the build environment:
- base: rust@latest
+ base: ubuntu/ruby@4.0
...
```
- The base build environment contains {data.alpine.default}, the selected
- major version of Rust, [Zerops command line tool](/references/cli), `npm` , `yarn`, `git` and `npx` tools.
+ The base build environment contains {data.ubuntu.default} or {data.alpine.default} (depending on the chosen base prefix), the selected
+ major version of Ruby, [Zerops command line tool](/references/cli), `gem`, `bundler` and `git` tools. A full native-extension
+ toolchain (gcc, make, libpq-dev, libyaml-dev, libffi-dev, etc.) is included, so gems with C extensions build out of the box.
:::info
@@ -35678,15 +35831,15 @@ zerops:
build:
# REQUIRED. Sets the base technology for the build environment:
base:
- - rust@latest
+ - ubuntu/ruby@4.0
prepareCommands:
- - zsc add go@latest
+ - zsc add nodejs@latest
...
```
See the full list of supported [build base environments](/zerops-yaml/base-list#runtime-services).
-To customize your build environment use the [prepareCommands](#preparecommands) attribute.
+To customise your build environment use the [prepareCommands](#preparecommands) attribute.
:::note
Modifying the base technology will invalidate your build cache. See our [Build Cache Documentation](/features/build-cache) for more details about cache invalidation.
@@ -35694,14 +35847,10 @@ Modifying the base technology will invalidate your build cache. See our [Build C
### os
-_OPTIONAL._ Sets the operating system for the build environment.
-
-Following options are available:
+The operating system for the build environment is selected as part of the [base](#base) value. There is no separate `os` attribute for Ruby, choose one of:
-- `alpine`
-- `ubuntu`
-
-Default value is `alpine`.
+- `ubuntu/ruby@4.0`
+- `alpine/ruby@4.0`
We are currently using following os version:
@@ -35709,23 +35858,23 @@ We are currently using following os version:
- {data.ubuntu.default}
:::caution
-The os version is fixed and cannot be customized.
+The os version is fixed and cannot be customised.
:::
:::note
-Modifying the OS will invalidate your build cache. See our [Build Cache Documentation](/features/build-cache) for more details about cache invalidation.
+Changing the OS prefix of your base will invalidate your build cache. See our [Build Cache Documentation](/features/build-cache) for details about cache behavior.
:::
### prepareCommands
-_OPTIONAL._ Customizes the build environment by installing additional dependencies or tools to the base build environment.
+_OPTIONAL._ Customises the build environment by installing additional dependencies or tools to the base build environment.
The base build environment contains:
-- {data.alpine.default}
-- selected version of Rust defined in the [base](#base) attribute
+- {data.ubuntu.default} or {data.alpine.default} (depending on the chosen base prefix)
+- selected version of Ruby defined in the [base](#base) attribute
- [Zerops command line tool](/references/cli)
-- `npm`, `yarn`, `git` and `npx` tools
+- `gem`, `bundler` and `git` tools
To install additional packages or tools add one or more prepare commands:
@@ -35736,15 +35885,18 @@ zerops:
# ==== how to build your application ====
build:
# REQUIRED. Set the base technology for the build environment:
- base: rust@latest
+ base: ubuntu/ruby@4.0
- # OPTIONAL. Customize the build environment by installing additional packages
+ # OPTIONAL. Customise the build environment by installing additional packages
# or tools to the base build environment.
prepareCommands:
- - cargo b --release
+ - sudo apt-get update
+ - sudo apt-get install -y some-package
...
```
+On the `alpine/ruby@4.0` base use `sudo apk add --no-cache some-package` instead.
+
When the first build is triggered, Zerops will
1. create a build container
@@ -35759,7 +35911,7 @@ These commands are skipped when using cached environment. Modifying `prepareComm
#### Command exit code
-If any command fails, it returns an exit code other than 0 and the build is canceled. Read the [build log](/rust/how-to/logs#build-log) to troubleshoot the error. If the command ends successfully, it returns the exit code 0 and Zerops triggers the following command. When all prepare commands are finished, your custom build environment is ready for the build phase.
+If any command fails, it returns an exit code other than 0 and the build is canceled. Read the [build log](/ruby/how-to/logs#build-log) to troubleshoot the error. If the command ends successfully, it returns the exit code 0 and Zerops triggers the following command. When all prepare commands are finished, your custom build environment is ready for the build phase.
#### Single or separated shell instances
@@ -35776,16 +35928,22 @@ zerops:
# ==== how to build your application ====
build:
# REQUIRED. Set the base technology for the build environment:
- base: rust@latest
+ base: ubuntu/ruby@4.0
+
+ # OPTIONAL. Set the env variables for the build environment:
+ envVariables:
+ BUNDLE_PATH: vendor/bundle
# OPTIONAL. Build your application
buildCommands:
- - cargo b --release
+ - bundle install
...
```
Build commands are optional. Zerops triggers each command in the defined order in a dedicated build container, running from the `/build/source` directory.
+With `BUNDLE_PATH` set to `vendor/bundle`, Bundler installs your gems into the `vendor` folder inside your project, so they can be deployed together with your application and used at runtime via `bundle exec`.
+
Before the build commands are triggered the build container contains:
1. base environment defined by the [base](#base) attribute
@@ -35799,7 +35957,8 @@ Use following syntax to run all commands in the same environment context. For ex
```yaml
buildCommands:
- |
- cargo b --release
+ bundle install
+ bundle exec rake assets:precompile
```
#### Run build commands as a separate shell instances
@@ -35808,16 +35967,17 @@ When the following syntax is used, each command is triggered in a separate envir
```yaml
buildCommands:
- - cargo b --release
+ - bundle install
+ - bundle exec rake assets:precompile
```
#### Command exit code
-If any command fails, it returns an exit code other than 0 and the build is canceled. Read the [build log](/rust/how-to/logs#build-log) to troubleshoot the error. If the error log doesn't contain any specific error message, try to run your build with the --verbose option.
+If any command fails, it returns an exit code other than 0 and the build is canceled. Read the [build log](/ruby/how-to/logs#build-log) to troubleshoot the error. If the error log doesn't contain any specific error message, try to run your build with the --verbose option.
```yaml
buildCommands:
- - cargo b --release
+ - bundle install --verbose
```
If the command ends successfully, it returns the exit code 0 and Zerops triggers the following command. When all `buildCommands` are finished, the application build is completed and ready for the deploy phase.
@@ -35830,7 +35990,11 @@ _REQUIRED._ Selects which files or folders will be deployed after the build has
# REQUIRED. Select which files / folders to deploy after
# the build has successfully finished
deployFiles:
- - target/release/~app
+ - ./vendor
+ - ./Gemfile
+ - ./Gemfile.lock
+ - ./config.ru
+ - ./src
```
Determines files or folders produced by your build, which should be deployed to your runtime service containers.
@@ -35845,7 +36009,8 @@ Deploys a folder, and a file from the project root directory:
```yaml
deployFiles:
- - target/release/~app
+ - vendor
+ - Gemfile
```
Deploys the whole content of the build container:
@@ -35883,10 +36048,10 @@ Deploys all folders that are located in any path that begins with `/path/` and e
```yaml
deployFiles: ./path/~/to/
```
+
:::note Example
By default, `./src/assets/fonts` deploys to `/var/www/src/assets/fonts`, keeping the full path. Adding `~`, like `./src/assets/~fonts`, shortens it to `/var/www/fonts`
:::
-
#### .deployignore
Add a `.deployignore` file to the root of your project to specify which files and folders Zerops should ignore during deploy. The syntax follows the same pattern format as [`.gitignore`](https://git-scm.com/docs/gitignore#_pattern_format).
@@ -35929,10 +36094,10 @@ _OPTIONAL._ Defines which files or folders will be cached for the next build.
```yaml
# OPTIONAL. Which files / folders you want to cache for the next build.
# Next builds will be faster when the cache is used.
-cache: file.txt
+cache: vendor
```
-The cache attribute helps optimize build times by preserving specified files between builds.
+The cache attribute helps optimize build times by preserving specified files between builds. Caching the `vendor` folder means the next `bundle install` only fetches gems that changed in your `Gemfile.lock`.
The cache attribute supports the [~ wildcard character](#how-to-use-a-wildcard-in-the-path).
@@ -35950,34 +36115,29 @@ zerops:
- setup: app
# ==== how to build your application ====
build:
- base: rust@latest
+ base: ubuntu/ruby@4.0
…
# OPTIONAL. Defines the env variables for the build environment:
envVariables:
- RUST_ENV: production
- DB_NAME: db
- DB_HOST: db
- DB_USER: db
- DB_PASS: ${db_password}
+ BUNDLE_PATH: vendor/bundle
+ BUNDLE_WITHOUT: development
```
-Read more about [environment variables](/rust/how-to/env-variables) in Zerops.
+Read more about [environment variables](/ruby/how-to/env-variables) in Zerops.
## Runtime configuration
### base
_OPTIONAL._ Sets the base technology for the runtime environment.
-If you don't specify the `run.base` attribute, Zerops keeps the current Rust version for your runtime.
+If you don't specify the `run.base` attribute, Zerops keeps the current Ruby version for your runtime.
-Following options are available for Rust builds:
+Following options are available for Ruby runtimes:
-- `rust@1`, `rust@latest`, `rust@stable`
-- `rust@1.86`
-- `rust@1.80`
-- `rust@1.78`
-- `rust@nightly`
+- `alpine/ruby@4.0`, `ubuntu/ruby@4.0`, `alpine/ruby@latest`, `ubuntu/ruby@latest`
+- `alpine/ruby@3.4`, `ubuntu/ruby@3.4`
+- `alpine/ruby@3.3`, `ubuntu/ruby@3.3`
```yaml
zerops:
@@ -35986,24 +36146,24 @@ zerops:
# ==== how to build your application ====
build:
# REQUIRED. Sets the base technology for the build environment:
- base: rust@latest
+ base: ubuntu/ruby@4.0
...
# ==== how to run your application ====
run:
# OPTIONAL. Sets the base technology for the runtime environment:
- base: rust@latest
+ base: ubuntu/ruby@4.0
...
```
- The base runtime environment contains {data.alpine.default}, the
- selected major version of Rust, Zerops command line tool, npm, yarn, git and
- npx tools.
+ The base runtime environment contains {data.ubuntu.default} or {data.alpine.default} (depending on the chosen base prefix), the
+ selected major version of Ruby, Zerops command line tool, `gem`, `bundler` and `git` tools. A full native-extension toolchain
+ (gcc, make, libpq-dev, libyaml-dev, libffi-dev, etc.) is included, so gems with C extensions work out of the box.
:::info
-You can change the base environment when you need to. Just simply modify the zerops.yaml in your repository.
+You can change the base environment when you need to. Just simply modify the `zerops.yaml` in your repository.
:::
If you need to install more technologies to the runtime environment, set multiple values as a yaml array. For example:
@@ -36015,33 +36175,29 @@ zerops:
# ==== how to build your application ====
build:
# REQUIRED. Sets the base technology for the build environment:
- base: rust@latest
+ base: ubuntu/ruby@4.0
...
# ==== how to run your application ====
run:
# OPTIONAL. Sets the base technology for the runtime environment:
base:
- - rust@latest
+ - ubuntu/ruby@4.0
prepareCommands:
- - zsc add go@latest
+ - zsc add nodejs@latest
...
```
See the full list of supported [run base environments](/zerops-yaml/base-list).
-To customize your build environment use the `prepareCommands` attribute.
+To customise your build environment use the `prepareCommands` attribute.
### os
-_OPTIONAL._ Sets the operating system for the runtime environment.
+The operating system for the runtime environment is selected as part of the [base](#base-1) value. There is no separate `os` attribute for Ruby, choose one of:
-Following options are available:
-
-- `alpine`
-- `ubuntu`
-
-Default value is `alpine`.
+- `ubuntu/ruby@4.0`
+- `alpine/ruby@4.0`
We are currently using following os version:
@@ -36058,41 +36214,25 @@ _OPTIONAL._ Specifies one or more internal ports on which your application will
Projects in Zerops represent a group of one or more services. Services can be of different types (runtime services, databases, message brokers, object storage, etc.). All services of the same project share a **dedicated private network**. To connect to a service within the same project, just use the service hostname and its internal port.
-For example, to connect to a Rust service with hostname = "app" and port = 8080 from another service of the same project, simply use `app:8080`. Read more about [how to access a Rust service](/references/networking/internal-access#basic-service-communication).
+For example, to connect to a Ruby service with hostname = "app" and port = 8080 from another service of the same project, simply use `app:8080`. Read more about [how to access a Ruby service](/references/networking/internal-access#basic-service-communication).
Each port has following attributes:
-
-
-
-
Parameter
-
Description
-
-
-
-
-
port
-
Defines the port number. You can set any port number between 10 and 65435. Ports outside this interval are reserved for internal Zerops systems.
-
-
-
protocol
-
Optional. Defines the protocol. Allowed values are TCP or UDP. Default value is TCP.
-
-
-
httpSupport
-
Optional.httpSupport = true is the default setting for TCP protocol. Set httpSupport = false if a web server isn't running on the port. Zerops uses this information for the configuration of [public access](/features/access). httpSupport = true is available only in combination with the TCP protocol.
-
-
-
+| parameter | description |
+| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| port | Defines the port number. You can set any port number between _10_ and _65435_. Ports outside this interval are reserved for internal Zerops systems. |
+| protocol | **Optional.** Defines the protocol. Allowed values are `TCP` or `UDP`. Default value is `TCP`. |
+| httpSupport | **Optional.** `httpSupport = true` is the default setting for TCP protocol. Set `httpSupport = false` if a web server isn't running on the port. Zerops uses this information for the configuration of [public access](/features/access). `httpSupport = true` is available only in combination with the TCP protocol. |
+| httpSupport | **Optional.** `httpSupport = true` is the default setting for TCP protocol. Set `httpSupport = false` if a web server isn't running on the port. Zerops uses this information for the configuration of [public access](/features/access). `httpSupport = true` is available only in combination with the TCP protocol. |
### prepareCommands
-_OPTIONAL._ Customises the Rust runtime environment by installing additional dependencies or tools to the runtime base environment.
+_OPTIONAL._ Customises the Ruby runtime environment by installing additional dependencies or tools to the runtime base environment.
- The base Rust environment contains {data.alpine.default}, the selected
- major version of Rust, [Zerops command line tool](/references/cli) and `npm` , `yarn`, `git` and `npx` tools. To install additional packages or tools add one or
- more prepare commands:
+ The base Ruby environment contains {data.ubuntu.default} or {data.alpine.default} (depending on the chosen base prefix), the selected
+ major version of Ruby, [Zerops command line tool](/references/cli) and `gem`, `bundler` and `git` tools. To install
+ additional packages or tools add one or more prepare commands:
```yaml
@@ -36106,13 +36246,15 @@ zerops:
# ==== how to run your application ====
run:
# OPTIONAL. Customise the runtime environment by installing additional packages
- # or tools to the base Rust runtime environment.
+ # or tools to the base Ruby runtime environment.
prepareCommands:
- - sudo apt-get something
- - curl something else
+ - sudo apt-get update
+ - sudo apt-get install -y some-package
...
```
+On the `alpine/ruby@4.0` base use `sudo apk add --no-cache some-package` instead.
+
When the first deploy with a defined prepare attribute is triggered, Zerops will
1. create a prepare runtime container
@@ -36125,7 +36267,7 @@ When the first deploy with a defined prepare attribute is triggered, Zerops will
#### Command exit code
-If any command fails, it returns an exit code other than 0 and the deploy is canceled. Read the [prepare runtime log](/rust/how-to/logs#prepare-runtime-log) to troubleshoot the error. If the command ends successfully, it returns the exit code 0 and Zerops triggers the following command. When all `prepareCommands` commands are finished, your custom runtime environment is ready for the deploy phase.
+If any command fails, it returns an exit code other than 0 and the deploy is canceled. Read the [prepare runtime log](/ruby/how-to/logs#prepare-runtime-log) to troubleshoot the error. If the command ends successfully, it returns the exit code 0 and Zerops triggers the following command. When all `prepareCommands` commands are finished, your custom runtime environment is ready for the deploy phase.
#### Cache of your custom runtime environment
@@ -36134,9 +36276,9 @@ Some packages or tools can take a long time to install. Therefore, Zerops caches
1. Content of the [build.addToRunPrepare](#copy-folders-or-files-from-your-build-container) and `run.prepareCommands` attributes didn't change from the previous deploy
2. The custom runtime cache wasn't invalidated in the Zerops GUI.
-To invalidate the Zerops runtime cache go to your service detail in Zerops GUI, choose **Service dashboard & runtime containers** from the left menu and click on the **Open pipeline detail** button. Then click on the **Clear runtime prepare cache** button.
+To invalidate the custom runtime cache go to `yyy`
-When the prepare cache is used, Zerops doesn't create a prepare runtime container and executes the deployment of your application directly.
+When the custom runtime cache is used, Zerops doesn't create a prepare runtime container and executes the deployment of your application directly.
#### Single or separated shell instances
@@ -36145,8 +36287,8 @@ You can configure your prepare commands to be run in a single shell instance or
### Copy folders or files from your build container
- The prepare runtime container contains {data.alpine.default}, the
- selected major version of Rust, [Zerops command line tool](/references/cli) and `npm`, `yarn`, `git` and `npx` tools.
+ The prepare runtime container contains {data.ubuntu.default} or {data.alpine.default} (depending on the chosen base prefix), the
+ selected major version of Ruby, [Zerops command line tool](/references/cli) and `gem`, `bundler` and `git` tools.
The prepare runtime container does not contain your application code nor the built application. If you need to copy some folders or files from the build container to the runtime container (e.g. a configuration file) use the `addToRunPrepare` attribute in the [build section](#build-pipeline-configuration).
@@ -36163,10 +36305,10 @@ zerops:
# ==== how to run your application ====
run:
# OPTIONAL. Customise the runtime environment by installing additional packages
- # or tools to the base Rust runtime environment.
+ # or tools to the base Ruby runtime environment.
prepareCommands:
- - sudo apt-get something
- - curl something else
+ - sudo apt-get update
+ - sudo apt-get install -y some-package
...
```
@@ -36187,12 +36329,12 @@ zerops:
run:
# OPTIONAL. Run one or more commands each time a new runtime container
# is started or restarted. These commands are triggered before
- # your Rust application is started.
+ # your Ruby application is started.
initCommands:
- rm -rf ./cache
```
-These commands are triggered in the runtime container before your Rust application is started via the [start command](#start).
+These commands are triggered in the runtime container before your Ruby application is started via the [start command](#start).
:::note
`run.initCommands` run in the `/var/www` directory.
@@ -36201,14 +36343,14 @@ These commands are triggered in the runtime container before your Rust applicati
Use init commands to clean or initialise your application cache or similar operations.
:::caution
-The init commands will delay the start of your application each time a new runtime container is started (including the horizontal [scaling](/rust/how-to/scaling) or when a runtime container is restarted).
+The init commands will delay the start of your application each time a new runtime container is started (including the horizontal [scaling](/ruby/how-to/scaling) or when a runtime container is restarted).
Do not use the init commands for customising your runtime environment. Use the [run:prepareCommands](#preparecommands-1) attribute instead.
:::
#### Command exit code
-If any of the `initCommands` fails, it returns an exit code other than 0, but deploy is **not** canceled. After all init commands are finished, regardless of the status code, the application is started. Read the [runtime log](/rust/how-to/logs#runtime-log) to troubleshoot the error.
+If any of the `initCommands` fails, it returns an exit code other than 0, but deploy is **not** canceled. After all init commands are finished, regardless of the status code, the application is started. Read the [runtime log](/ruby/how-to/logs#runtime-log) to troubleshoot the error.
#### Single or separated shell instances
@@ -36228,18 +36370,19 @@ zerops:
run:
# OPTIONAL. Defines the env variables for the runtime environment:
envVariables:
- RUST_ENV: production
+ RACK_ENV: production
+ BUNDLE_PATH: vendor/bundle
DB_NAME: db
DB_HOST: db
DB_USER: db
DB_PASS: ${db_password}
```
-Read more about [environment variables](/rust/how-to/env-variables) in Zerops.
+Read more about [environment variables](/ruby/how-to/env-variables) in Zerops.
### start
-_REQUIRED._ Defines the start command for your Rust application.
+_REQUIRED._ Defines the start command for your Ruby application.
```yaml
zerops:
@@ -36250,10 +36393,12 @@ zerops:
# ==== how to run your application ====
run:
- # REQUIRED. Your Rust application start command
- start: ./app
+ # REQUIRED. Your Ruby application start command
+ start: bundle exec puma -p 8080 -b tcp://0.0.0.0
```
+We recommend starting your Ruby application via `bundle exec`, so the gems vendored into `vendor/bundle` during the build are used at runtime. The `-b tcp://0.0.0.0` option makes Puma bind to all interfaces, not just localhost.
+
### health check
_OPTIONAL._ Defines a health check.
@@ -36307,8 +36452,8 @@ zerops:
# ==== how to run your application ====
run:
- # REQUIRED. Your Rust application start command
- start: ./app
+ # REQUIRED. Your Ruby application start command
+ start: bundle exec puma -p 8080 -b tcp://0.0.0.0
# OPTIONAL. Define a health check with a HTTP GET request option.
# Configures the check on http://127.0.0.1:80/status
@@ -36323,26 +36468,11 @@ zerops:
Configures the health check to run a local command.
Following attributes are available:
-
-
-
-
Parameter
-
Description
-
-
-
-
-
command
-
- Defines a local command to be run.
-
- The command has access to the same [environment variables](/rust/how-to/create#set-secret-environment-variables) as your Rust application.
-
- A single string is required. If you need to run multiple commands create a shell script or, use a multiline format as in the example below.
-
-
-
-
+| Parameter | Description |
+| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **command** | Defines a local command to be run.
+The command has access to the same [environment variables](/ruby/how-to/create#set-secret-environment-variables) as your Ruby application.
+A single string is required. If you need to run multiple commands create a shell script or, use a multiline format as in the example below. |
**Example:**
@@ -36355,8 +36485,1459 @@ zerops:
# ==== how to run your application ====
run:
- # REQUIRED. Your Rust application start command
- start: ./app
+ # REQUIRED. Your Ruby application start command
+ start: bundle exec puma -p 8080 -b tcp://0.0.0.0
+
+ # OPTIONAL. Define a health check with a shell command.
+ healthCheck:
+ exec:
+ command: |
+ touch grass
+ rm -rf life
+ mv /outside/user /home/user
+```
+
+### crontab
+
+_OPTIONAL._ Defines cron jobs.
+
+Setup cron jobs in the following format:
+
+```yaml
+zerops:
+ # define hostname of your service
+ - setup: app
+
+ # ==== how to run your application ====
+ run:
+ crontab:
+ # REQUIRED. Sets the command to execute:
+ - command: ""
+ # REQUIRED. Sets the interval time to execute:
+ timing: "0 * * * *"
+```
+
+Read more about setting up [cron](/zerops-yaml/cron) in Zerops.
+
+## Deploy configuration
+
+### readiness check
+
+_OPTIONAL._ Defines a readiness check. Read more about how the [readiness check works](/ruby/how-to/deploy-process#readiness-checks) in Zerops.
+
+`readinessCheck` requires either one `httpGet` object or one `exec` object.
+
+#### httpGet
+
+Configures the readiness check to request a local URL using a http GET method.
+
+Following attributes are available:
+
+
+
+
+
Parameter
+
Description
+
+
+
+
+
port
+
Defines the port of the HTTP GET request.
+The readiness check will trigger a GET request on {'http://127.0.0.1:{port}/{path}'}
+
+
+
path
+
Defines the URL path of the HTTP GET request.
+The readiness check will trigger a GET request on {'http://127.0.0.1:{port}/{path}'}
+
+
+
host
+
Optional. The readiness check is triggered from inside of your runtime container so it always uses the localhost 127.0.0.1. If you need to add a host to the request header, specify it in the host attribute.
+
+
+
scheme
+
Optional. The readiness check is triggered from inside of your runtime container so no https is required.
+If your application requires a https request, set scheme: https
+
+
+
+
+**Example:**
+
+```yaml
+zerops:
+ # hostname of your service
+ - setup: app
+ # ==== how to build your application ====
+ build: ...
+
+ # ==== how to deploy your application ====
+ deploy:
+ # OPTIONAL. Define a readiness check with a HTTP GET request option.
+ # Configures the check on http://127.0.0.1:80/status
+ readinessCheck:
+ httpGet:
+ port: 80
+ path: /status
+
+ # ==== how to run your application ====
+ run: ...
+```
+
+Read more about how the [readiness check works](/ruby/how-to/deploy-process#readiness-checks) in Zerops.
+
+#### exec
+
+Configures the readiness check to run a local command.
+Following attributes are available:
+
+| Parameter | Description |
+| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **command** | Defines a local command to be run.
+The command has access to the same [environment variables](/ruby/how-to/create#set-secret-environment-variables) as your Ruby application.
+A single string is required. If you need to run multiple commands create a shell script or, use a multiline format as in the example below. |
+
+**Example:**
+
+```yaml
+zerops:
+ # hostname of your service
+ - setup: app
+ # ==== how to build your application ====
+ build: ...
+
+ # ==== how to deploy your application ====
+ deploy:
+ # OPTIONAL. Define a readiness check with a HTTP GET request option.
+ # Configures the check on http://127.0.0.1:80/status
+ readinessCheck:
+ exec:
+ command: |
+ touch grass
+ rm -rf life
+ mv /outside/user /home/user
+```
+
+Read more about how the [readiness check works](/ruby/how-to/deploy-process#readiness-checks) in Zerops.
+
+
+----------------------------------------
+
+# Ruby > How To > Build Process
+
+
+
+----------------------------------------
+
+# Ruby > How To > Controls
+
+
+
+----------------------------------------
+
+# Ruby > How To > Create
+
+
+Zerops provides a powerful Ruby runtime service with extensive build support. The Ruby runtime is highly scalable and customizable to suit your development and production needs. With just a few clicks or commands, you can have a production-ready Ruby environment up and running in no time.
+
+## Create a Ruby service using Zerops GUI
+
+First, set up a project in the Zerops GUI. Then go to the project dashboard page and choose **Add new service** in the left menu under the **Services** section. Pick **Ruby** from the list of runtime services, choose a version and operating system (Ubuntu or Alpine), set a hostname and confirm to create the service.
+
+### Choose a Ruby version
+
+Zerops supports the following Ruby versions:
+
+The service type is always specified together with the operating system, e.g. `ubuntu/ruby@4.0` or `alpine/ruby@4.0`. The `@latest` tag (e.g. `ubuntu/ruby@latest`) is an alias that points to Ruby 4.0.
+
+The Ruby runtime images include a full native-extension toolchain (gcc, make, libpq-dev, libyaml-dev, libffi-dev, etc.), so gems with C extensions build out of the box.
+
+:::info
+You can easily [upgrade](/ruby/how-to/upgrade) the major version at any time later.
+:::
+
+### Set a hostname
+
+Enter a unique service identifier like "app", "cache", "gui", etc. Duplicate services with the same name within the same project are not allowed.
+
+#### Limitations:
+
+- Maximum 25 characters
+- Must contain only lowercase ASCII letters (a-z) or numbers (0-9)
+
+:::caution
+The hostname is fixed after the service is created and cannot be changed later.
+:::
+
+### Set secret environment variables
+
+Add environment variables with sensitive data, such as passwords, tokens, salts, certificates, etc. These will be securely saved inside Zerops and added to your runtime service upon start.
+
+Setting secret environment variables is optional. You can always set them later in the Zerops GUI.
+
+Read more about the [different types of environment variables](/ruby/how-to/env-variables#service-env-variables) in Zerops.
+
+## Create a Ruby service using zCLI
+
+zCLI is the Zerops command-line tool. To create a new Ruby service via the command line, follow these steps:
+
+1. [Install & setup zCLI](/references/cli)
+2. [Create a project description file](/ruby/how-to/create#create-a-project-description-file)
+3. [Create a project with a Ruby and PostgreSQL service](#full-example)
+
+### Create a project description file
+
+Zerops uses a YAML format to describe the project infrastructure.
+
+#### Basic example:
+
+Create a directory called `my-project`. Inside the `my-project` directory, create a `description.yaml` file with the following content:
+```yaml
+# basic project data
+project:
+ # project name
+ name: my-project
+# array of project services
+services:
+ - # service name
+ hostname: app
+ # service type and version number in {os}/ruby@{version} format
+ type: ubuntu/ruby@4.0
+ # defines the minimum number of containers for horizontal autoscaling
+ minContainers: 1
+ # defines the maximum number of containers for horizontal autoscaling. Max value = 6.
+ maxContainers: 6
+ # optional: create env variables
+ envSecrets:
+ S3_ACCESS_KEY_ID: 'P8cX1vVVb'
+ S3_ACCESS_SECRET: 'ogFthuiLYki8XoL73opSCQ'
+```
+
+The yaml file describes your future project infrastructure. The project will contain one Ruby version 4.0 service with default [auto scaling](/ruby/how-to/scaling) configuration. Hostname will be set to "app", the internal port(s) the service listens on will be defined later in the [zerops.yaml](/ruby/how-to/build-pipeline#ports). Following secret env variables will be configured:
+
+```env
+S3_ACCESS_KEY_ID="P8cX1vVVb"
+S3_ACCESS_SECRET="ogFthuiLYki8XoL73opSCQ"
+```
+
+#### Full example:
+
+Create a directory my-project. Create an description.yaml file inside the my-project directory with following content:
+
+```yaml
+# basic project data
+project:
+ # project name
+ name: my-project
+ # optional: project description
+ description: A project with a Ruby and PostgreSQL database
+ # optional: project tags
+ tags:
+ - DEMO
+ - ZEROPS
+# array of project services
+services:
+ - # service name
+ hostname: app
+ # service type and version number in {os}/ruby@{version} format
+ type: ubuntu/ruby@4.0
+ # optional: vertical auto scaling customization
+ verticalAutoscaling:
+ cpuMode: DEDICATED
+ minCpu: 2
+ maxCpu: 5
+ minRam: 2
+ maxRam: 24
+ minDisk: 6
+ maxDisk: 50
+ startCpuCoreCount: 3
+ minFreeRamGB: 0.5
+ minFreeRamPercent: 20
+ # defines the minimum number of containers for horizontal autoscaling. Max value = 6.
+ minContainers: 2
+ # defines the maximum number of containers for horizontal autoscaling. Max value = 6.
+ maxContainers: 4
+ # optional: create env variables
+ envSecrets:
+ S3_ACCESS_KEY_ID: 'P8cX1vVVb'
+ S3_ACCESS_SECRET: 'ogFthuiLYki8XoL73opSCQ'
+ - # second service hostname
+ hostname: db
+ # service type and version number in postgresql@{version} format
+ type: postgresql@16
+ # mode of operation "HA"/"non_HA"
+ mode: NON_HA
+```
+
+The yaml file describes your future project infrastructure. The project will contain a Ruby service and a [PostgreSQL](/postgresql/overview) service.
+
+Ruby service with "app" hostname, the internal port(s) the service listens on will be defined later in the [zerops.yaml](/ruby/how-to/build-pipeline#ports). Ruby service will run on version 4.0 with a custom vertical and horizontal scaling. Following secret env variables will be configured:
+
+```env
+S3_ACCESS_KEY_ID="P8cX1vVVb"
+S3_ACCESS_SECRET="ogFthuiLYki8XoL73opSCQ"
+```
+
+The hostname of the PostgreSQL service will be set to "db". The [single container](/features/scaling#single-container-mode)(/features/scaling#deployment-modes-databases-and-shared-storage) mode will be chosen and the default auto [scaling configuration](/postgresql/how-to/scale#configure-scaling) will be set.
+
+#### Description of description.yaml parameters
+
+The `project:` section is required. Only one project can be defined.
+
+| Parameter | Description | Limitations |
+| --------------- | ------------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
+| **name** | The name of the new project. Duplicates are allowed. | |
+| **description** | **Optional.** Description of the new project. | Maximum 255 characters. |
+| **tags** | **Optional.** One or more string tags. Tags do not have a functional meaning, they only provide better orientation in projects. |
+| **tags** | **Optional.** One or more string tags. Tags do not have a functional meaning, they only provide better orientation in projects. |
+
+At least one service in `services:` section is required. You can create a project with multiple services. The example above contains Ruby and PostgreSQL services but you can create a `description.yaml` with your own combination of [services](/features/infrastructure).
+
+
+
+
+
Parameter
+
Description
+
+
+
+
+
+ hostname
+
+
+ The unique service identifier.
+
+
duplicate services with the same name in the same project are forbidden
+
maximum 25 characters
+
must contain only lowercase ASCII letters (a-z) or numbers (0-9)
+
+
+
+
+
+ type
+
+
+ Specifies the service type and version.
+
+ See what [Ruby service types](/references/import-yaml/type-list#runtime-services) are currently supported.
+
+
+
+
+ verticalAutoscaling
+
+
+ Optional. Defines [custom vertical auto scaling parameters](/ruby/how-to/create#set-auto-scaling-configuration).
+
+ All verticalAutoscaling attributes are optional. Not specified
+ attributes will be set to their default values.
+
+
+
+
+ - cpuMode
+
+
+ Optional. Accepts `SHARED`, `DEDICATED` values. Default is `SHARED`
+
+
+
+
+ - minCpu/maxCpu
+
+
+ Optional. Set the minCpu or maxCpu in CPU cores (integer).
+
+
+
+
+ - minRam/maxRam
+
+
+ Optional. Set the minRam or maxRam in GB (float).
+
+
+
+
+ - minDisk/maxDisk
+
+
+ Optional. Set the minDisk or maxDisk in GB (float).
+
+
+
+
+ minContainers
+
+
+ Optional. Default = 1. Defines the minimum number of containers
+ for [horizontal autoscaling](/ruby/how-to/create#horizontal-auto-scaling).
+
+ Limitations:
+
+ Current maximum value = 10.
+
+
+
+
+ maxContainers
+
+
+ Defines the maximum number of containers for [horizontal autoscaling](/ruby/how-to/create#horizontal-auto-scaling).
+
+ Limitations:
+
+ Current maximum value = 10.
+
+
+
+
+ envSecrets
+
+
+ Optional. Defines one or more secret env variables as a key value
+ map. See env variable [restrictions](/ruby/how-to/env-variables#env-variable-restrictions).
+
+
+
+
+
+### Create a project based on the description.yaml
+
+When you have your `description.yaml` ready, use the `zcli project project-import` command to create a new project and the service infrastructure.
+
+```sh
+Usage:
+ zcli project project-import importYamlPath [flags]
+
+Flags:
+ -h, --help Help for the project import command.
+ --org-id string If you have access to more than one organization, you must specify the org ID for which the
+ project is to be created.
+ --working-dir string Sets a custom working directory. Default working directory is the current directory. (default "./")
+```
+
+Zerops will create a project and one or more services based on the `description.yaml` content.
+
+Maximum size of the `description.yaml` file is 100 kB.
+
+You don't specify the project name in the `zcli project project-import` command, because the project name is defined in the `description.yaml`.
+
+If you have access to more than one client, you must specify the client ID for which the project is to be created. The `clientID` is located in the Zerops GUI under the client name on the project dashboard page.
+
+### Add Ruby service to an existing project
+
+#### Example:
+
+Create a directory `my-project` if it doesn't exist. Create an `import.yaml` file inside the `my-project` directory with following content:
+
+```yaml
+# basic project data
+project:
+ # project name
+ name: my-project
+# array of project services
+services:
+ - # service name
+ hostname: app
+ # service type and version number in {os}/ruby@{version} format
+ type: ubuntu/ruby@4.0
+ # defines the minimum number of containers for horizontal autoscaling
+ minContainers: 1
+ # defines the maximum number of containers for horizontal autoscaling. Max value = 6.
+ maxContainers: 6
+ # optional: create env variables
+ envSecrets:
+ S3_ACCESS_KEY_ID: 'P8cX1vVVb'
+ S3_ACCESS_SECRET: 'ogFthuiLYki8XoL73opSCQ'
+```
+
+The yaml file describes the list of one or more services that you want to add to your existing project. In the example above, one Ruby service version 4.0 with default [auto scaling](/ruby/how-to/scaling) configuration will be added to your project. Hostname of the new service will be set to `app`. Following secret env variables will be configured:
+
+```env
+S3_ACCESS_KEY_ID="P8cX1vVVb"
+S3_ACCESS_SECRET="ogFthuiLYki8XoL73opSCQ"
+```
+
+The content of the `services:` section of `import.yaml` is identical to the project description file. The `import.yaml` never contains the `project:` section because the project already exists.
+
+When you have your `import.yaml` ready, use the `zcli project service-import` command to add one or more services to your existing Zerops project.
+
+```sh
+Usage:
+ zcli project service-import importYamlPath [flags]
+
+Flags:
+ -h, --help Help for the project service import command.
+ -P, --project-id string If you have access to more than one project, you must specify the project ID for which the
+ command is to be executed.
+```
+
+zCLI commands are interactive, when you press enter after `zcli project service-import importYamlPath`, you will be given a list of your projects to choose from.
+
+Maximum size of the import.yaml file is 100 kB.
+
+
+----------------------------------------
+
+# Ruby > How To > Customize Runtime
+
+
+
+----------------------------------------
+
+# Ruby > How To > Deploy Process
+
+
+
+----------------------------------------
+
+# Ruby > How To > Env Variables
+
+
+
+----------------------------------------
+
+# Ruby > How To > Filebrowser
+
+
+
+----------------------------------------
+
+# Ruby > How To > Logs
+
+
+
+----------------------------------------
+
+# Ruby > How To > Scaling
+
+
+
+----------------------------------------
+
+# Ruby > How To > Shared Storage
+
+
+
+----------------------------------------
+
+# Ruby > How To > Trigger Pipeline
+
+
+
+----------------------------------------
+
+# Ruby > How To > Upgrade
+
+
+
+----------------------------------------
+
+# Ruby > Overview
+
+
+[Ruby ↗](https://www.ruby-lang.org/en/) is a dynamic, object-oriented programming language with a focus on simplicity and programmer happiness.
+
+As said, there is no need for coding yet, we have created a [Github repository ↗](https://github.com/zerops-recipe-apps/ruby-hello-world-app), a **_recipe_**, containing a simple Ruby (Sinatra) web application served by Puma. The repo will be used as a source from which the app will be built.
+
+1. Log in/sign up to [Zerops GUI ↗](https://app.zerops.io)
+
+2. In the **Projects** box click on **Import a project** and paste in the following YAML config:
+
+```yaml
+project:
+ name: recipe-ruby
+ tags:
+ - zerops-recipe
+
+services:
+ - hostname: app
+ type: ubuntu/ruby@4.0
+ zeropsSetup: prod
+ enableSubdomainAccess: true
+ buildFromGit: https://github.com/zerops-recipe-apps/ruby-hello-world-app
+
+ - hostname: db
+ type: postgresql@16
+ mode: NON_HA
+ priority: 1
+```
+
+3. Click on **Import project** and wait until all pipelines have finished.
+
+**That's it, your application is now up and running! :star: Let's check it works:**
+
+1. A _subdomain_ should have been enabled and visible in the project's **IP addressed & Public Routing Overview** box. Its format should look similar to this `https://app-808-8080.prg1.zerops.app`.
+2. Click or the `subdomain` URL to open it in a browser and you should see
+
+```
+{"type":"ruby","greeting":"Hello from Zerops!","status":{"database":"OK"}}
+```
+
+:::tip
+Do you have any questions? Check the step-by-step tutorial, browse the documentation and join our **[Discord](https://discord.com/invite/WDvCZ54)** community to get help from our team and other members.
+:::
+
+## How to start
+
+It doesn't matter whether it's your first curious introduction to Zerops, you have already mastered the basics and are looking for a tiny detail or inspiration. Below, choose a section that fits your needs:
+
+- [Care for details?](/ruby/how-to/create) — Dive in all Zerops has to offer for your Ruby application.
+- [Ruby recipes](https://github.com/zeropsio?q=ruby&type=all&language=&sort=) — Get inspired by already existing repositories, ready to be imported to Zerops.
+
+## Feature Highlights
+
+- [Create Ruby service](/ruby/how-to/create) — Start with creating a Ruby service using GUI or zCLI.
+- [Zerops.yaml](/ruby/how-to/build-pipeline#add-zeropsyaml-to-your-repository) — See a full example of zerops.yaml file to create your own app.
+- [Scaling configuration](/ruby/how-to/scaling) — Set up scaling of your Ruby application so that it runs smoothly while using only necessary resources.
+
+{" "}
+
+- [Customize build environment](/ruby/how-to/build-process#customize-build-environment)
+- [Customize runtime environment](/ruby/how-to/customize-runtime)
+
+## When in doubt, reach out
+
+Don't know how to start or got stuck during the process? You might not be the first one, visit the FAQ section to find out.
+
+In case you haven't found an answer (and also if you have), we and our community are looking forward to hearing from you on Discord.
+
+Have you build something that others might find useful? Don't hesitate to share your knowledge!
+
+- [FAQ](/ruby/faq) — Most common questions in one place.
+- [Discord](https://discord.com/invite/WDvCZ54) — Join our core team and Zerops community on Discord. Ask questions and share your tips with other members.
+
+## Popular Guides
+
+- [zCLI](/references/cli) — Get even more out of Zerops with the zCLI command line tool.
+- [Zerops VPN](/references/networking/vpn) — Connect to your services easily with Zerops VPN.
+
+
+----------------------------------------
+
+# Rust > How To > Build Pipeline
+
+
+Zerops provides a customizable build and runtime environment for your Rust application.
+
+## Add zerops.yaml to your repository
+
+Start by adding `zerops.yaml` file to the **root of your repository** and modify it to fit your application:
+
+```yaml
+zerops:
+ # define hostname of your service
+ - setup: app
+ # ==== how to build your application ====
+ build:
+ # REQUIRED. Set the base technology for the build environment:
+ base: rust@latest
+
+ # OPTIONAL. Set the operating system for the build environment.
+ # os: ubuntu
+
+ # OPTIONAL. Customize the build environment by installing additional packages
+ # or tools to the base build environment.
+ # prepareCommands:
+ # - sudo apt-get something
+ # - curl something else
+
+ # OPTIONAL. Build your application
+ buildCommands:
+ - cargo b --release
+
+ # REQUIRED. Select which files / folders to deploy after
+ # the build has successfully finished
+ deployFiles:
+ - target/release/~app
+
+ # OPTIONAL. Which files / folders you want to cache for the next build.
+ # Next builds will be faster when the cache is used.
+ # cache: file.txt
+
+ # ==== how to run your application ====
+ run:
+ # OPTIONAL. Sets the base technology for the runtime environment:
+ base: rust@latest
+
+ # OPTIONAL. Sets the internal port(s) your app listens on:
+ ports:
+ # port number
+ - port: 8080
+
+ # OPTIONAL. Customize the runtime Rust environment by installing additional
+ # dependencies to the base Rust runtime environment.
+ # prepareCommands:
+ # - sudo apt-get something
+ # - curl something else
+
+ # OPTIONAL. Run one or more commands each time a new runtime container
+ # is started or restarted. These commands are triggered before
+ # your Rust application is started.
+ # initCommands:
+ # - rm -rf ./cache
+
+ # REQUIRED. Your Rust application start command
+ start: ./app
+```
+
+The top-level element is always `zerops`.
+
+### Setup
+
+The first element `setup` contains the **hostname** of your service. A runtime service with the same hostname must exist in Zerops.
+Zerops supports the definition of multiple runtime services in a single `zerops.yaml`. This is useful when you use a monorepo. Just add multiple setup elements in your `zerops.yaml`:
+
+```yaml
+zerops:
+ # definition for app service
+ - setup: app
+ # optional
+ build: ...
+ # optional
+ deploy: ...
+ # required
+ run: ...
+
+ # definition for api service
+ - setup: api
+ # optional
+ build: ...
+ # optional
+ deploy: ...
+ # required
+ run: ...
+```
+
+Each service configuration contains at least the `run` section. Optional `build` and `deploy` sections can be added to further customize your process.
+
+## Build pipeline configuration
+
+### base
+
+_REQUIRED._ Sets the base technology for the build environment.
+
+Following options are available for Rust builds:
+
+- `rust@1`, `rust@latest`, `rust@stable`
+- `rust@1.86`
+- `rust@1.80`
+- `rust@1.78`
+- `rust@nightly`
+
+```yaml
+zerops:
+ # hostname of your service
+ - setup: app
+ # ==== how to build your application ====
+ build:
+ # REQUIRED. Sets the base technology for the build environment:
+ base: rust@latest
+ ...
+```
+
+
+ The base build environment contains {data.alpine.default}, the selected
+ major version of Rust, [Zerops command line tool](/references/cli), `npm` , `yarn`, `git` and `npx` tools.
+
+
+:::info
+You can change the base environment when you need to. Just simply modify the `zerops.yaml` in your repository.
+:::
+
+If you need to install more technologies to the build environment, set multiple values as a yaml array. For example:
+
+```yaml
+zerops:
+ # hostname of your service
+ - setup: app
+ # ==== how to build your application ====
+ build:
+ # REQUIRED. Sets the base technology for the build environment:
+ base:
+ - rust@latest
+ prepareCommands:
+ - zsc add go@latest
+ ...
+```
+
+See the full list of supported [build base environments](/zerops-yaml/base-list#runtime-services).
+
+To customize your build environment use the [prepareCommands](#preparecommands) attribute.
+
+:::note
+Modifying the base technology will invalidate your build cache. See our [Build Cache Documentation](/features/build-cache) for more details about cache invalidation.
+:::
+
+### os
+
+_OPTIONAL._ Sets the operating system for the build environment.
+
+Following options are available:
+
+- `alpine`
+- `ubuntu`
+
+Default value is `alpine`.
+
+We are currently using following os version:
+
+- {data.alpine.default}
+- {data.ubuntu.default}
+
+:::caution
+The os version is fixed and cannot be customized.
+:::
+
+:::note
+Modifying the OS will invalidate your build cache. See our [Build Cache Documentation](/features/build-cache) for more details about cache invalidation.
+:::
+
+### prepareCommands
+
+_OPTIONAL._ Customizes the build environment by installing additional dependencies or tools to the base build environment.
+
+The base build environment contains:
+
+- {data.alpine.default}
+- selected version of Rust defined in the [base](#base) attribute
+- [Zerops command line tool](/references/cli)
+- `npm`, `yarn`, `git` and `npx` tools
+
+To install additional packages or tools add one or more prepare commands:
+
+```yaml
+zerops:
+ # hostname of your service
+ - setup: app
+ # ==== how to build your application ====
+ build:
+ # REQUIRED. Set the base technology for the build environment:
+ base: rust@latest
+
+ # OPTIONAL. Customize the build environment by installing additional packages
+ # or tools to the base build environment.
+ prepareCommands:
+ - cargo b --release
+ ...
+```
+
+When the first build is triggered, Zerops will
+
+1. create a build container
+2. download your application code from your repository
+3. run the prepare commands in the defined order
+
+The application code is available in `/build/source` before the prepare commands are triggered, so you can use any file from your repository in your prepare commands (e.g. a configuration file). The commands themselves run in the `/home/zerops` directory.
+
+:::note
+These commands are skipped when using cached environment. Modifying `prepareCommands` will invalidate your build cache. See our [Build Cache Documentation](/features/build-cache) for details about cache invalidation.
+:::
+
+#### Command exit code
+
+If any command fails, it returns an exit code other than 0 and the build is canceled. Read the [build log](/rust/how-to/logs#build-log) to troubleshoot the error. If the command ends successfully, it returns the exit code 0 and Zerops triggers the following command. When all prepare commands are finished, your custom build environment is ready for the build phase.
+
+#### Single or separated shell instances
+
+You can configure your prepare commands to be run in a single shell instance or multiple shell instances. The format is identical to [build commands](#buildcommands).
+
+### buildCommands
+
+_OPTIONAL._ Defines build commands.
+
+```yaml
+zerops:
+ # hostname of your service
+ - setup: app
+ # ==== how to build your application ====
+ build:
+ # REQUIRED. Set the base technology for the build environment:
+ base: rust@latest
+
+ # OPTIONAL. Build your application
+ buildCommands:
+ - cargo b --release
+ ...
+```
+
+Build commands are optional. Zerops triggers each command in the defined order in a dedicated build container, running from the `/build/source` directory.
+
+Before the build commands are triggered the build container contains:
+
+1. base environment defined by the [base](#base) attribute
+2. optional customisation of the base environment defined in the [prepareCommands](#preparecommands) attribute
+3. your application code
+
+#### Run build commands as a single shell instance
+
+Use following syntax to run all commands in the same environment context. For example, if one command changes the current directory, the next command continues in that directory. When one command creates an environment variable, the next command can access it.
+
+```yaml
+buildCommands:
+ - |
+ cargo b --release
+```
+
+#### Run build commands as a separate shell instances
+
+When the following syntax is used, each command is triggered in a separate environment context. For example, each shell instance starts in the home directory again. When one command creates an environment variable, it won't be available for the next command.
+
+```yaml
+buildCommands:
+ - cargo b --release
+```
+
+#### Command exit code
+
+If any command fails, it returns an exit code other than 0 and the build is canceled. Read the [build log](/rust/how-to/logs#build-log) to troubleshoot the error. If the error log doesn't contain any specific error message, try to run your build with the --verbose option.
+
+```yaml
+buildCommands:
+ - cargo b --release
+```
+
+If the command ends successfully, it returns the exit code 0 and Zerops triggers the following command. When all `buildCommands` are finished, the application build is completed and ready for the deploy phase.
+
+### deployFiles
+
+_REQUIRED._ Selects which files or folders will be deployed after the build has successfully finished. To filter out specific files or folders, use [`.deployignore`](#deployignore) file.
+
+```yaml
+# REQUIRED. Select which files / folders to deploy after
+# the build has successfully finished
+deployFiles:
+ - target/release/~app
+```
+
+Determines files or folders produced by your build, which should be deployed to your runtime service containers.
+
+The path starts from the **root directory** of your project (the location of `zerops.yaml`). You must enclose the name in quotes if the folder or the file name contains a space.
+
+The files/folders will be placed into `/var/www` folder in runtime, e.g. `./src/assets/fonts` would result in `/var/www/src/assets/fonts`.
+
+#### Examples
+
+Deploys a folder, and a file from the project root directory:
+
+```yaml
+deployFiles:
+ - target/release/~app
+```
+
+Deploys the whole content of the build container:
+
+```yaml
+deployFiles: .
+```
+
+Deploys a folder, and a file in a defined path:
+
+```yaml
+deployFiles:
+ - ./path/to/file.txt
+ - ./path/to/dir/
+```
+
+#### How to use a wildcard in the path
+
+Zerops supports the `~` character as a wildcard for one or more folders in the path.
+
+Deploys all `file.txt` files that are located in any path that begins with `/path/` and ends with `/to/`
+
+```yaml
+deployFiles: ./path/~/to/file.txt
+```
+
+Deploys all folders that are located in any path that begins with `/path/to/`
+
+```yaml
+deployFiles: ./path/to/~/
+```
+
+Deploys all folders that are located in any path that begins with `/path/` and ends with `/to/`
+
+```yaml
+deployFiles: ./path/~/to/
+```
+:::note Example
+By default, `./src/assets/fonts` deploys to `/var/www/src/assets/fonts`, keeping the full path. Adding `~`, like `./src/assets/~fonts`, shortens it to `/var/www/fonts`
+:::
+
+#### .deployignore
+
+Add a `.deployignore` file to the root of your project to specify which files and folders Zerops should ignore during deploy. The syntax follows the same pattern format as [`.gitignore`](https://git-scm.com/docs/gitignore#_pattern_format).
+
+To ignore a specific file or directory path, start the pattern with a forward slash (`/`). Without the leading slash, the pattern will match files with that name in any directory.
+
+:::tip
+For consistency, it's recommended to configure both your `.gitignore` and `.deployignore` files with the same patterns.
+:::
+
+Examples:
+
+```yaml title="zerops.yaml"
+zerops:
+ - setup: app
+ build:
+ deployFiles: ./
+```
+
+```text title=".deployignore"
+/src/file.txt
+```
+The example above ignores `file.txt` only in the root src directory.
+```text title=".deployignore"
+src/file.txt
+```
+This example above ignores `file.txt` in ANY directory named `src`, such as:
+- `/src/file.txt`
+- `/folder2/folder3/src/file.txt`
+- `/src/src/file.txt`
+
+:::note
+`.deployignore` file also works with [`zcli service deploy`](/references/zcli/commands#deploy) command.
+:::
+
+### cache
+
+_OPTIONAL._ Defines which files or folders will be cached for the next build.
+
+```yaml
+# OPTIONAL. Which files / folders you want to cache for the next build.
+# Next builds will be faster when the cache is used.
+cache: file.txt
+```
+
+The cache attribute helps optimize build times by preserving specified files between builds.
+
+The cache attribute supports the [~ wildcard character](#how-to-use-a-wildcard-in-the-path).
+
+Learn more about the [build cache system](/features/build-cache) in Zerops.
+
+### envVariables
+
+_OPTIONAL._ Defines the environment variables for the build environment.
+
+Enter one or more env variables in following format:
+
+```yaml
+zerops:
+ # define hostname of your service
+ - setup: app
+ # ==== how to build your application ====
+ build:
+ base: rust@latest
+ …
+
+ # OPTIONAL. Defines the env variables for the build environment:
+ envVariables:
+ RUST_ENV: production
+ DB_NAME: db
+ DB_HOST: db
+ DB_USER: db
+ DB_PASS: ${db_password}
+```
+
+Read more about [environment variables](/rust/how-to/env-variables) in Zerops.
+
+## Runtime configuration
+
+### base
+
+_OPTIONAL._ Sets the base technology for the runtime environment.
+If you don't specify the `run.base` attribute, Zerops keeps the current Rust version for your runtime.
+
+Following options are available for Rust builds:
+
+- `rust@1`, `rust@latest`, `rust@stable`
+- `rust@1.86`
+- `rust@1.80`
+- `rust@1.78`
+- `rust@nightly`
+
+```yaml
+zerops:
+ # hostname of your service
+ - setup: app
+ # ==== how to build your application ====
+ build:
+ # REQUIRED. Sets the base technology for the build environment:
+ base: rust@latest
+ ...
+
+ # ==== how to run your application ====
+ run:
+ # OPTIONAL. Sets the base technology for the runtime environment:
+ base: rust@latest
+ ...
+```
+
+
+ The base runtime environment contains {data.alpine.default}, the
+ selected major version of Rust, Zerops command line tool, npm, yarn, git and
+ npx tools.
+
+
+:::info
+You can change the base environment when you need to. Just simply modify the zerops.yaml in your repository.
+:::
+
+If you need to install more technologies to the runtime environment, set multiple values as a yaml array. For example:
+
+```yaml
+zerops:
+ # hostname of your service
+ - setup: app
+ # ==== how to build your application ====
+ build:
+ # REQUIRED. Sets the base technology for the build environment:
+ base: rust@latest
+ ...
+
+ # ==== how to run your application ====
+ run:
+ # OPTIONAL. Sets the base technology for the runtime environment:
+ base:
+ - rust@latest
+ prepareCommands:
+ - zsc add go@latest
+ ...
+```
+
+See the full list of supported [run base environments](/zerops-yaml/base-list).
+
+To customize your build environment use the `prepareCommands` attribute.
+
+### os
+
+_OPTIONAL._ Sets the operating system for the runtime environment.
+
+Following options are available:
+
+- `alpine`
+- `ubuntu`
+
+Default value is `alpine`.
+
+We are currently using following os version:
+
+- {data.alpine.default}
+- {data.ubuntu.default}
+
+:::caution
+The os version is fixed and cannot be customised.
+:::
+
+### ports
+
+_OPTIONAL._ Specifies one or more internal ports on which your application will listen.
+
+Projects in Zerops represent a group of one or more services. Services can be of different types (runtime services, databases, message brokers, object storage, etc.). All services of the same project share a **dedicated private network**. To connect to a service within the same project, just use the service hostname and its internal port.
+
+For example, to connect to a Rust service with hostname = "app" and port = 8080 from another service of the same project, simply use `app:8080`. Read more about [how to access a Rust service](/references/networking/internal-access#basic-service-communication).
+
+Each port has following attributes:
+
+
+
+
+
Parameter
+
Description
+
+
+
+
+
port
+
Defines the port number. You can set any port number between 10 and 65435. Ports outside this interval are reserved for internal Zerops systems.
+
+
+
protocol
+
Optional. Defines the protocol. Allowed values are TCP or UDP. Default value is TCP.
+
+
+
httpSupport
+
Optional.httpSupport = true is the default setting for TCP protocol. Set httpSupport = false if a web server isn't running on the port. Zerops uses this information for the configuration of [public access](/features/access). httpSupport = true is available only in combination with the TCP protocol.
+
+
+
+
+### prepareCommands
+
+_OPTIONAL._ Customises the Rust runtime environment by installing additional dependencies or tools to the runtime base environment.
+
+
+ The base Rust environment contains {data.alpine.default}, the selected
+ major version of Rust, [Zerops command line tool](/references/cli) and `npm` , `yarn`, `git` and `npx` tools. To install additional packages or tools add one or
+ more prepare commands:
+
+
+```yaml
+zerops:
+ # hostname of your service
+ - setup: app
+ # ==== how to build your application ====
+ build:
+ ...
+
+ # ==== how to run your application ====
+ run:
+ # OPTIONAL. Customise the runtime environment by installing additional packages
+ # or tools to the base Rust runtime environment.
+ prepareCommands:
+ - sudo apt-get something
+ - curl something else
+ ...
+```
+
+When the first deploy with a defined prepare attribute is triggered, Zerops will
+
+1. create a prepare runtime container
+2. optionally: [copy selected folders or files from your build container](#copy-folders-or-files-from-your-build-container)
+3. run the `prepareCommands` commands in the defined order
+
+:::note
+`run.prepareCommands` run in the `/home/zerops` directory.
+:::
+
+#### Command exit code
+
+If any command fails, it returns an exit code other than 0 and the deploy is canceled. Read the [prepare runtime log](/rust/how-to/logs#prepare-runtime-log) to troubleshoot the error. If the command ends successfully, it returns the exit code 0 and Zerops triggers the following command. When all `prepareCommands` commands are finished, your custom runtime environment is ready for the deploy phase.
+
+#### Cache of your custom runtime environment
+
+Some packages or tools can take a long time to install. Therefore, Zerops caches your custom runtime environment after the installation of your custom packages or tools is completed. When the second or following deploy is triggered, Zerops will use the custom runtime cache from the previous deploy if following conditions are met:
+
+1. Content of the [build.addToRunPrepare](#copy-folders-or-files-from-your-build-container) and `run.prepareCommands` attributes didn't change from the previous deploy
+2. The custom runtime cache wasn't invalidated in the Zerops GUI.
+
+To invalidate the Zerops runtime cache go to your service detail in Zerops GUI, choose **Service dashboard & runtime containers** from the left menu and click on the **Open pipeline detail** button. Then click on the **Clear runtime prepare cache** button.
+
+When the prepare cache is used, Zerops doesn't create a prepare runtime container and executes the deployment of your application directly.
+
+#### Single or separated shell instances
+
+You can configure your prepare commands to be run in a single shell instance or multiple shell instances. The format is identical to [build commands](#buildcommands).
+
+### Copy folders or files from your build container
+
+
+ The prepare runtime container contains {data.alpine.default}, the
+ selected major version of Rust, [Zerops command line tool](/references/cli) and `npm`, `yarn`, `git` and `npx` tools.
+
+
+The prepare runtime container does not contain your application code nor the built application. If you need to copy some folders or files from the build container to the runtime container (e.g. a configuration file) use the `addToRunPrepare` attribute in the [build section](#build-pipeline-configuration).
+
+```yaml
+zerops:
+ # hostname of your service
+ - setup: app
+ # ==== how to build your application ====
+ build:
+ ...
+ addToRunPrepare: ./runtime-config.yaml
+
+ # ==== how to run your application ====
+ run:
+ # OPTIONAL. Customise the runtime environment by installing additional packages
+ # or tools to the base Rust runtime environment.
+ prepareCommands:
+ - sudo apt-get something
+ - curl something else
+ ...
+```
+
+In the example above Zerops will copy the `runtime-config.yaml` file from your build container **after the build has finished** into the new **prepare runtime** container. The copied files and folders will be available in the `/home/zerops` folder in the new prepare runtime container before the prepare commands are triggered.
+
+### initCommands
+
+_OPTIONAL._ Defines one or more commands to be run each time a new runtime container is started or a container is restarted.
+
+```yaml
+zerops:
+ # hostname of your service
+ - setup: app
+ # ==== how to build your application ====
+ build: ...
+
+ # ==== how to run your application ====
+ run:
+ # OPTIONAL. Run one or more commands each time a new runtime container
+ # is started or restarted. These commands are triggered before
+ # your Rust application is started.
+ initCommands:
+ - rm -rf ./cache
+```
+
+These commands are triggered in the runtime container before your Rust application is started via the [start command](#start).
+
+:::note
+`run.initCommands` run in the `/var/www` directory.
+:::
+
+Use init commands to clean or initialise your application cache or similar operations.
+
+:::caution
+The init commands will delay the start of your application each time a new runtime container is started (including the horizontal [scaling](/rust/how-to/scaling) or when a runtime container is restarted).
+
+Do not use the init commands for customising your runtime environment. Use the [run:prepareCommands](#preparecommands-1) attribute instead.
+:::
+
+#### Command exit code
+
+If any of the `initCommands` fails, it returns an exit code other than 0, but deploy is **not** canceled. After all init commands are finished, regardless of the status code, the application is started. Read the [runtime log](/rust/how-to/logs#runtime-log) to troubleshoot the error.
+
+#### Single or separated shell instances
+
+You can configure your `initCommands` to be run in a single shell instance or multiple shell instances. The format is identical to [build commands](#buildcommands).
+
+### envVariables
+
+_OPTIONAL._ Defines the environment variables for the runtime environment.
+
+Enter one or more env variables in following format:
+
+```yaml
+zerops:
+ # define hostname of your service
+ - setup: app
+ # ==== how to run your application ====
+ run:
+ # OPTIONAL. Defines the env variables for the runtime environment:
+ envVariables:
+ RUST_ENV: production
+ DB_NAME: db
+ DB_HOST: db
+ DB_USER: db
+ DB_PASS: ${db_password}
+```
+
+Read more about [environment variables](/rust/how-to/env-variables) in Zerops.
+
+### start
+
+_REQUIRED._ Defines the start command for your Rust application.
+
+```yaml
+zerops:
+ # hostname of your service
+ - setup: app
+ # ==== how to build your application ====
+ build: ...
+
+ # ==== how to run your application ====
+ run:
+ # REQUIRED. Your Rust application start command
+ start: ./app
+```
+
+### health check
+
+_OPTIONAL._ Defines a health check.
+
+`healthCheck` requires either one `httpGet` object or one `exec` object.
+
+#### httpGet
+
+Configures the health check to request a local URL using a HTTP GET method.
+
+Following attributes are available:
+
+
+
+
+
Parameter
+
Description
+
+
+
+
+
port
+
Defines the port of the HTTP GET request.
+The readiness check will trigger a GET request on {'http://127.0.0.1:{port}/{path}'}
+
+
+
path
+
Defines the URL path of the HTTP GET request.
+The readiness check will trigger a GET request on {'http://127.0.0.1:{port}/{path}'}
+
+
+
host
+
Optional. The readiness check is triggered from inside of your runtime container so it always uses the localhost 127.0.0.1. If you need to add a host to the request header, specify it in the host attribute.
+
+
+
scheme
+
Optional. The readiness check is triggered from inside of your runtime container so no https is required.
+If your application requires a https request, set scheme: https
+
+
+
+
+**Example:**
+
+```yaml
+zerops:
+ # hostname of your service
+ - setup: app
+ # ==== how to build your application ====
+ build: ...
+
+ # ==== how to run your application ====
+ run:
+ # REQUIRED. Your Rust application start command
+ start: ./app
+
+ # OPTIONAL. Define a health check with a HTTP GET request option.
+ # Configures the check on http://127.0.0.1:80/status
+ healthCheck:
+ httpGet:
+ port: 80
+ path: /status
+```
+
+#### exec
+
+Configures the health check to run a local command.
+Following attributes are available:
+
+
+
+
+
Parameter
+
Description
+
+
+
+
+
command
+
+ Defines a local command to be run.
+
+ The command has access to the same [environment variables](/rust/how-to/create#set-secret-environment-variables) as your Rust application.
+
+ A single string is required. If you need to run multiple commands create a shell script or, use a multiline format as in the example below.
+
+
+
+
+
+**Example:**
+
+```yaml
+zerops:
+ # hostname of your service
+ - setup: app
+ # ==== how to build your application ====
+ build: ...
+
+ # ==== how to run your application ====
+ run:
+ # REQUIRED. Your Rust application start command
+ start: ./app
# OPTIONAL. Define a health check with a shell command.
healthCheck:
@@ -37019,60 +38600,60 @@ Have you build something that others might find useful? Don't hesitate to share
----------------------------------------
-# Shared Storage > How To > Backup
-
-
-Zerops provides automated data backup for Shared Storage services with full encryption and flexible management options.
-
-For general backup information including configuration, scheduling, and management options, see the [Zerops Backups](/features/backup) documentation, which covers:
-- Backup scheduling and retention policies
-- Tagging system and storage quotas
-- Manual backup creation and CLI tools
-- Security and encryption details
+# Shared Storage > How To > Connect
-## Shared Storage Backup Format
-Shared Storage backups are created using tar archival:
+This page covers how to connect an existing shared storage to runtime services, how to disconnect it, and how the mount behaves inside your containers.
-- **Format**: `.tar.gz` (archive of directory contents)
-- **Tooling**: `tar`
-- **Content**: Compresses the entire shared volume (`/mnt/`)
-- **Storage**: Encrypted and stored in isolated object storage
+## How the mount behaves
-### Restoring Backups
+Once a shared storage is connected to a runtime service, Zerops mounts it at `/mnt/` in the runtime containers. For example, `/mnt/teststorage` for a `teststorage` shared storage:
-To restore a Shared Storage backup: Download the backup file (`.tar.gz`) from the Zerops UI, extract the archive, and upload the contents back to your Shared Storage.
+- The content is shared among **all containers** of the connected runtime service; if you connect multiple runtime services, they all see the same content
+- Multiple storages can be mounted to a single service (e.g. `/mnt/files1`, `/mnt/files2`)
+- The mount point is owned by the `zerops` user and group (no sudo required)
+- Mounting removes any existing content of the mount directory
+- The mount is only available in runtime containers, not during build and prepare runtime phases
+- The mount process logs its standard output and error to the runtime service logs (tagged `zerops-mount-`); individual file operations are not logged
-## Best Practices
+:::note
+The `df` command may show incorrect or misleading information for shared storage mounts. Refer to the Zerops GUI for accurate storage metrics.
+:::
-For Shared Storage backups:
-- Regularly clean up unnecessary files from your Shared Storage to reduce backup size
-- Create manual backups with protected tags before major file operations or migrations
-- Consider adjusting your backup frequency for optimal storage usage
-- Test your restore process periodically to ensure data integrity
+## Connect in Zerops GUI
-For additional best practices and troubleshooting, refer to the [main backup documentation](/features/backup).
+When creating a new shared storage service, you can directly select which runtime services it should be connected to. See [Create shared storage](/shared-storage/how-to/create) for the creation process.
-----------------------------------------
+For existing storage, go to the shared storage service detail page and select **Shared storage connections**. Toggle ON any runtime services you wish to connect to this storage.
-# Shared Storage > How To > Connect
+To disconnect storage, toggle OFF the desired runtime service in the same place.
+:::note
+Connecting and disconnecting a shared storage is applied live; the runtime containers are not restarted.
+:::
-This page covers how to connect an existing shared storage to runtime services and how to disconnect services when needed.
+## Mount and unmount using zsc
-## In Zerops GUI
+You can also mount and unmount a shared storage from within a runtime container using the `zsc` command:
-### Connect a new shared storage
+```sh
+# Mount a shared storage
+zsc shared-storage mount
-When creating a new shared storage service, you can directly select which runtime services it should be connected to. See [Create Shared Storage](/shared-storage/how-to/create) for details about the creation process.
+# Unmount a shared storage
+zsc shared-storage unmount
-### Connect an existing shared storage
+# Wait until the storage mount is ready
+zsc shared-storage wait
+```
-For existing storage, go to the shared storage service detail page and select **Shared storage connections**. Toggle ON any runtime services you wish to connect to this storage.
+Run these commands from your runtime container via web terminal or SSH, or as part of your `zerops.yaml` commands. For more details, see the [zsc reference](/references/zsc#shared-storage).
-## Disconnect a shared storage in Zerops GUI
+:::caution
+The shared storage must be mounted (through the GUI connection toggle or via `zsc shared-storage mount`) before your application can access the `/mnt/` directory.
-To disconnect storage, access the shared storage service detail page, select **Shared storage connections**, and toggle OFF the desired runtime service.
+Before stopping or deleting a shared storage service, you must first disconnect (unmount) it from all runtime services.
+:::
----------------------------------------
@@ -37080,22 +38661,19 @@ To disconnect storage, access the shared storage service detail page, select **S
# Shared Storage > How To > Create
-Shared Storage provides persistent file storage that can be mounted as a POSIX-compatible filesystem to your runtime services. Built on [SeaweedFS ↗](https://github.com/seaweedfs/seaweedfs), it enables reliable data persistence and sharing across services in your infrastructure.
-
## Create Using Zerops GUI
-First, set up a project in Zerops GUI and add a runtime service. Then go to the project dashboard page and choose **Add new service** in the left menu in the **Services** block. Then add a new Shared Storage service:
+Go to your project dashboard and choose **Add new service** in the left menu in the **Services** block. Then add a new Shared Storage service:
[Video: /vids/services/shared-storage.webm](/vids/services/shared-storage.webm)
### Set a Hostname
-Enter a unique service identifier like "storage", "files" etc. Duplicate services with the same name in the same project are forbidden.
-
-#### Hostname Limitations:
+Enter a unique service identifier like "storage", "files" etc.
- Maximum 25 characters
- Must contain only lowercase ASCII letters (a-z) or numbers (0-9)
+- Must be unique within the project
:::note
The hostname is fixed after the service is created. It can't be changed later.
@@ -37103,41 +38681,52 @@ The hostname is fixed after the service is created. It can't be changed later.
### Connect to Services
-Select one or more project's runtime services in the Share with Services block:
-
-The new Shared Storage will be connected to the selected runtimes.
+Select one or more of the project's runtime services in the **Share with Services** block:
-:::note
-Runtime services can be connected and disconnected at any time even after the shared storage is created.
-:::
+Runtime services can be [connected and disconnected](/shared-storage/how-to/connect) at any time, even after the shared storage is created.
### Choose Deployment Mode
-Choose between **Highly Available** (recommended for production) or **Single Container** (suitable for development) deployment.
+Choose between **Highly Available** (recommended for production) and **Single Container** (suitable for development). See the [mode comparison](/shared-storage/overview#deployment-modes) and [technical details](/shared-storage/tech-details#deployment-modes).
:::warning
-The Shared Storage deployment mode is fixed after the service is created. It can't be changed later.
-
-See [Technical Details](/shared-storage/tech-details#deployment-modes) for more information about deployment modes.
+The deployment mode is fixed after the service is created. It can't be changed later.
:::
### Set Auto Scaling Configuration
-Configure vertical auto scaling parameters to control resource allocation and costs.
+Configure vertical auto scaling parameters to control resource allocation and costs:
:::note
-For detailed information about auto scaling capabilities and recommendations, see [Technical Details](/shared-storage/tech-details#auto-scaling-configuration).
+Raising the disk limits does not raise the usable data capacity, which is capped at 60GB. See [Storage Capacity](/shared-storage/tech-details#storage-capacity) and [Auto Scaling Configuration](/shared-storage/tech-details#auto-scaling-configuration).
:::
-## Create Using zCLI
+## Create Using Import YAML
-zCLI is the Zerops command-line tool. To create a new Shared Storage service via the command-line, follow these steps:
+The deployment mode is part of the service type:
-1. [Install & setup zCLI](/references/cli)
-2. Create a project description file
-3. Create a project with a runtime and a Shared Storage service
+```yaml title="zerops-import.yaml"
+services:
+ - # Shared storage service in highly available mode
+ hostname: teststorage
+ type: shared-storage:ha
+```
-### Choose Your Runtime
+Use `shared-storage:single` for the single container mode.
+
+:::note
+You can also pin the underlying engine version directly with `seaweedfs:ha@3` or `seaweedfs:single@3`.
+:::
+
+Import the file with the [zCLI](/references/cli):
+
+```sh
+zcli project service-import zerops-import.yaml
+```
+
+To mount the storage into a runtime service in the same import, add the `mount:` parameter to the runtime service. See the [import reference](/references/import#service-mount-shared-storage) for details and requirements.
+
+### Runtime-Specific Guides
export const languages = [
{ name: "Node.js", link: "/nodejs/how-to/shared-storage#create-nodejs-service-with-a-shared-storage-using-zcli" },
@@ -37148,29 +38737,32 @@ export const languages = [
{ name: "Rust", link: "/rust/how-to/shared-storage#create-rust-service-with-a-shared-storage-using-zcli" }
]
+
----------------------------------------
# Shared Storage > How To > Manage
-Zerops Shared Storage provides several web interfaces to manage, monitor, and troubleshoot your storage. These interfaces are accessible through the [Zerops VPN](/references/networking/vpn) and offer different capabilities for managing your data and monitoring system performance.
+Zerops Shared Storage provides several web interfaces to manage, monitor, and troubleshoot your storage, and automated encrypted backups of your data.
-## Access Web Interfaces
+## Web interfaces
+
+The web interfaces are accessible through the [Zerops VPN](/references/networking/vpn).
### Filer UI
* `http://.zerops:8888`
-The Filer UI provides a web-based interface for managing files and directories in your Shared Storage:
+A web-based interface for managing files and directories:
- Browse the directory structure and create new directories
-- Upload new files (up to 64MB) and download existing files
+- Upload new files (up to 64MB per file) and download existing files
- Rename and delete files and directories
### Master UI
-* `http://node-stable-1.db..zerops:9333`
-The Master UI provides system status and monitoring information:
+* `http://node-stable-1.db..zerops:9333`
+System status and monitoring information:
- View cluster topology
- Monitor volume servers
- Check system status and health
@@ -37180,8 +38772,7 @@ The Master UI provides system status and monitoring information:
* `http://node-stable-.db..zerops:8080/ui/index.html`
-The Volume UI allows you to monitor individual storage volumes:
-
+Monitoring of individual storage volumes:
- View volume status
- Check disk usage
- Monitor I/O operations
@@ -37191,90 +38782,138 @@ The Volume UI allows you to monitor individual storage volumes:
Several options are available to help you monitor your Shared Storage:
-### Runtime Service Logs
-* Navigate to your runtime service detail page → **Runtime Logs** section → filter using the tag `zerops-mount-`
+- **Runtime service logs**: navigate to your runtime service detail page, open the **Runtime Logs** section, and filter using the tag `zerops-mount-` (standard output and error of the mount process)
+- **Shared Storage logs**: access from the Shared Storage service detail page, **Runtime Logs** tab
+- **System and volume status**: monitor replication status, disk usage, and performance metrics through the Master UI and Volume UI
-### Shared Storage Logs
-* Access from the Shared Storage service detail page → **Runtime Logs** tab → browse or search for relevant information
+## Backups
-### System and Volume Status
-* Monitor replication status, disk usage, and performance metrics through the Master UI and Volume UI
+Zerops provides automated data backup for Shared Storage services with full encryption.
+For general backup information including configuration, scheduling, retention, tagging, quotas, and CLI tools, see the [Zerops Backups](/features/backup) documentation.
-----------------------------------------
+### Backup format
+
+Shared Storage backups are created using tar archival:
-# Shared Storage > How To > Use
+- **Format**: `.tar.gz` (archive of the directory contents)
+- **Content**: the entire shared volume (`/mnt/`)
+- **Storage**: encrypted and stored in isolated object storage
+### Restoring backups
-Once a Shared Storage is [connected](/shared-storage/how-to/connect) to a runtime service, Zerops will create a new folder `/mnt/[shared storage name]` in the runtime service's filesystem.
+To restore a Shared Storage backup:
-For example, `/mnt/teststorage` for a `teststorage` Shared Storage:
+1. Download the backup file (`.tar.gz`) from the Zerops UI.
+2. Transfer the archive into a runtime service that has the shared storage [connected](/shared-storage/how-to/connect), for example over the [Zerops VPN](/references/networking/vpn).
+3. Extract the archive into the mount directory from within the runtime container:
-:::note
-The content of this folder is shared among all containers of the connected runtime service.
+```sh
+tar -xzf backup.tar.gz -C /mnt/
+```
-If you connect multiple runtimes, the content of the folder will be shared among all containers of these services.
+:::note
+Do not restore by uploading files through the Filer UI. Uploads there are limited to 64MB per file, so extracting the archive directly into the mounted directory is the reliable path.
:::
-## Mount Points and Multiple Volumes
+### Best practices
-- Multiple storage volumes can be mounted to a single service (e.g., `/mnt/files1`, `/mnt/files2`, etc.)
-- Shared storage mount is only available in runtime containers, not during build and prepare runtime phases
-- All filesystem operations are automatically logged to runtime logs
+- Regularly clean up unnecessary files from your Shared Storage to reduce backup size
+- Create manual backups with protected tags before major file operations or migrations
+- Test your restore process periodically to ensure data integrity
-For technical details about mount behavior and filesystem capabilities, see the [Technical Details](/shared-storage/tech-details#mount-integration) page.
-## Use Cases
+----------------------------------------
-Shared Storage is ideal for:
+# Shared Storage > Overview
-- **Persistent filesystem-based databases**: SQLite, Prometheus DB, etc.
-- **Configuration sharing**: Deploy configurations once and share across multiple services
- - Example: Deploy Apache Airflow configurations and DAG files once and share with all worker nodes
-- **Alternative to object storage**: For applications that require filesystem semantics rather than object storage
-- **Application data**: Store and serve images, documents, and other assets
-## Performance Considerations
+Zerops **Shared Storage** is a managed disk volume that mounts as a POSIX-compatible filesystem into your runtime services. Files written by one container are immediately visible to every other container that mounts the same storage, whether they belong to the same service or to different services in the project.
-When using Shared Storage, keep in mind:
+Under the hood it runs [SeaweedFS ↗](https://github.com/seaweedfs/seaweedfs), a distributed filesystem optimized for high-volume storage. See [Technical details](/shared-storage/tech-details) for the architecture.
-- For write-heavy workloads, consider batching operations
-- Minimize operations with many small files for better performance
+## Quick start
-For more detailed information about performance constraints and limitations, see the [Technical Details](/shared-storage/tech-details#performance-considerations) page.
+Add a shared storage to your project and mount it into a runtime service with a `zerops-import.yaml`:
-## Troubleshooting
+```yaml title="zerops-import.yaml"
+services:
+ - hostname: teststorage
+ # type is shared-storage:{mode}
+ type: shared-storage:ha
-### Common Issues
+ - hostname: app
+ type: nodejs@22
+ buildFromGit: https://github.com/myorg/myapp
+ # mount the storage into /mnt/teststorage
+ mount:
+ - teststorage
+```
-- The `df` command may show incorrect or misleading information when used with shared storage mounts. Please refer to the Zerops GUI for accurate storage metrics.
+The storage appears in the runtime containers at `/mnt/teststorage`. See [Create shared storage](/shared-storage/how-to/create) for the GUI flow and configuration details, and [Connect & mount](/shared-storage/how-to/connect) for how the mount behaves.
-----------------------------------------
+## Use cases
-# Shared Storage > Overview
+Shared Storage is a good fit for:
+- **Configuration sharing**: deploy configuration once and share it across services (e.g. Apache Airflow configuration and DAG files shared with all worker nodes)
+- **Application data**: user uploads, images, documents, and other assets shared between containers (avoid huge trees of tiny files, see [performance considerations](/shared-storage/tech-details#performance-considerations))
+- **Fallback to object storage**: applications that strictly require filesystem semantics and cannot work with [object storage](/object-storage/overview)
-# Shared Storage
+:::caution Not suitable for databases
+Do **not** run databases on Shared Storage. This includes SQLite, Prometheus TSDB, and other filesystem-based databases.
-Zerops provides a fully managed and scaled **Shared Storage** service, which can be mounted to your runtime services. It offers:
-- Persistent file sharing between containers of the same service or different services
-- Standard filesystem operations through a POSIX-compatible interface
-- Built-in high-availability configuration
+- File locks are enforced **per mount only**: a lock held in one container does not prevent another container from writing to the same file, which leads to data corruption.
+- The underlying storage engine is an append-only chunk store: every modification writes new chunks, so the frequent small writes typical for databases cause heavy write amplification.
-## Documentation Sections
+Use [managed databases](/postgresql/overview) instead. For storing database *backups*, prefer [object storage](/object-storage/overview): it is cheaper and better suited for write-once files.
+:::
-- [Connect to Services](/shared-storage/how-to/connect) — Connect your Shared Storage to runtime services.
-- [Usage & Limitations](/shared-storage/how-to/use) — Learn how to save and access data, along with use cases and technical limits.
-- [Management & Access](/shared-storage/how-to/manage) — Access UI components and troubleshoot common issues.
-- [Backups](/shared-storage/how-to/backup) — Configure automatic backups of your data.
-- [Technical Details](/shared-storage/tech-details) — Explore the technical architecture and deployment options.
+## Deployment modes
-*Need help? Join our [Discord community](https://discord.gg/zeropsio).*
+Choose the reliability model when you create the service. It is set through the service `type` and is **fixed for the life of the service**.
-## Popular Guides
+
+
+
+
Mode
+
type
+
What it is
+
+
+
+
+
Highly Available
+
shared-storage:ha
+
Two containers with data and metadata replicated 1:1 across both, with automatic repair when a node fails. Recommended for production.
+
+
+
Single container
+
shared-storage:single
+
One container, so lower cost but no redundancy. All data is lost if the container fails. Good for development and non-critical data.
+
+
+
-- [zCLI](/references/cli) — Get even more out of Zerops with the zCLI command line tool.
-- [Zerops VPN](/references/networking/vpn) — Connect to your services easily with Zerops VPN.
+## Key limits
+
+- The storage holds at most **60GB of data**, regardless of the disk resource in autoscaling. See [storage capacity](/shared-storage/tech-details#storage-capacity).
+- File locks work **only within a single container's mount**, not across containers. See [filesystem capabilities](/shared-storage/tech-details#filesystem-capabilities).
+- The mount is available only in runtime containers, not during build and prepare phases.
+
+## Next steps
+
+- [Create shared storage](/shared-storage/how-to/create) — Create a service in the GUI or with zerops-import.yaml.
+- [Connect & mount](/shared-storage/how-to/connect) — Connect runtime services and understand how the mount behaves.
+- [Manage & backups](/shared-storage/how-to/manage) — Web interfaces, monitoring, backups, and restore.
+- [Technical details](/shared-storage/tech-details) — Architecture, storage engine behavior, limits, and autoscaling.
+
+## Need help?
+
+Stuck, or want to share what you built? Our core team and community are on Discord.
+
+- [Discord](https://discord.com/invite/WDvCZ54) — Join the Zerops community on Discord. Ask questions and share your tips.
+- [zCLI](/references/cli) — Get more out of Zerops with the command-line tool.
----------------------------------------
@@ -37282,25 +38921,21 @@ Zerops provides a fully managed and scaled **Shared Storage** service, which can
# Shared Storage > Tech Details
-Zerops Shared Storage is built on [SeaweedFS](https://github.com/seaweedfs/seaweedfs), a distributed filesystem optimized for high-volume storage with efficient retrieval.
+Zerops Shared Storage is built on [SeaweedFS ↗](https://github.com/seaweedfs/seaweedfs), a distributed filesystem optimized for high-volume storage with efficient retrieval.
## Architecture
Shared Storage consists of three main components:
-- **Master Server**: Manages metadata and coordinates volume servers
-- **Volume Servers**: Store the actual file data
-- **Filer**: Provides a POSIX-compatible interface for file operations
-An **automatic vacuum process** helps maintain optimal storage performance by reclaiming space from deleted files. This process is triggered when the size of deleted content exceeds 15% (reduced from the default 30%).
+- **Master Server**: manages metadata and coordinates volume servers
+- **Volume Servers**: store the actual file data
+- **Filer**: provides a POSIX-compatible interface for file operations
+
+### Storage Engine Behavior
-### Mount Integration
+SeaweedFS stores file data in append-only volumes. Files are split into chunks, and when a file is modified, new chunks are written while the old ones remain on disk until they are reclaimed. Frequent small modifications of existing files therefore cause significant write amplification, which is one of the reasons Shared Storage is [not suitable for databases](/shared-storage/overview#use-cases).
-When connected to a runtime service:
-- Storage is mounted at `/mnt/`
-- Mount point is owned by the `zerops` user and group (no sudo required)
-- All filesystem operations are logged to runtime logs (tagged as `zerops-mount-`)
-- Mounting will overwrite any existing content in the mount directory
-- Shared storage mount is only available in runtime containers, not during build and prepare runtime phases
+An **automatic vacuum process** reclaims the space occupied by deleted and superseded chunks. This process is triggered when the size of deleted content exceeds 15% (reduced from the SeaweedFS default of 30%).
## Deployment Modes
@@ -37310,8 +38945,8 @@ Zerops provides Shared Storage in two deployment modes:
Recommended for production environments where data reliability is critical.
-- **Architecture**: 2 volume servers with the master located on one of them
-- **Data Durability**: Data and filer metadata are replicated 1:1 across nodes
+- **Architecture**: Two containers, each running its own filer and volume server; the master server runs on the first container
+- **Data Durability**: Data and filer metadata are replicated 1:1 across both containers
- **Fault Tolerance**:
- If a node fails, an automatic repair process begins
- A new container replaces the failed one
@@ -37324,7 +38959,6 @@ Suitable for development environments or non-critical data storage.
- **Architecture**: Master, volume, and filer server all located on a single container
- **Data Durability**: All data is lost if the container fails
-- **Recommended For**: Development environments or temporary data storage
:::warning
The deployment mode is fixed after the service is created and cannot be changed later.
@@ -37336,45 +38970,51 @@ Shared Storage supports standard POSIX filesystem operations:
- Create, read, update, and delete files and directories
- Set permissions (with some limitations)
-- Standard file locking operations
+- File locking (per mount only, see the warning below)
- Hard and symbolic links
- Directory listing and traversal
-For a complete list of supported features, see the [SeaweedFS FUSE documentation](https://github.com/seaweedfs/seaweedfs/wiki/FUSE-Mount#supported-features).
+For a complete list of supported features, see the [SeaweedFS FUSE documentation ↗](https://github.com/seaweedfs/seaweedfs/wiki/FUSE-Mount#supported-features).
-## Resource Constraints
+:::caution File locks are per-mount only
+File locks (`flock`, POSIX locks) are enforced only within a single container's mount. A process in another container can freely write to a file you hold locked. Do not rely on file locking for coordination between containers, and do not run [lock-dependent workloads such as databases](/shared-storage/overview#use-cases) on Shared Storage.
+:::
-### Storage Limits
+## Storage Capacity
-- Maximum storage space: 60GB (can be increased via support request)
-- Maximum file size: Unlimited within the 60GB total storage constraint
-- Maximum upload size via Filer UI: 64MB
+:::caution The 60GB data limit is independent of disk scaling
+A Shared Storage service holds at most **60GB of data**, no matter how much disk the service scales to. The disk resource (up to 250GB) provides headroom for the vacuum process and filesystem metadata; raising the disk limits in autoscaling does **not** increase the amount of data you can store.
-### Memory Usage
+If you need more than 60GB, contact support.
+:::
-- Base memory consumption: ~60MB when idle
-- Peak memory usage: ~150MB under higher filesystem loads
-- Optimized for low RAM usage (may trade off some performance)
+- Maximum storage space: 60GB (can be increased via support request)
+- Maximum file size: no fixed limit through the mount (files are stored in chunks), up to the available storage space
+- Maximum upload size via Filer UI: 64MB per file
-### Performance Considerations
+## Performance Considerations
-- **Latency**: Higher latency compared to local storage due to network-based distributed architecture
-- **Write Performance**: For write-heavy workloads, consider batching operations
-- **Small Files**: Minimize operations with many small files for better performance
+- **Latency**: higher latency compared to local storage due to the network-based distributed architecture
+- **Write performance**: for write-heavy workloads, batch operations where possible
+- **Small files**: minimize operations with many small files for better performance
+- **Mount process memory**: the mount process runs inside each connected runtime container and consumes its memory, roughly 60MB when idle and around 150MB under higher filesystem loads; it is optimized for low RAM usage, which may trade off some performance
## Auto Scaling Configuration
-Zerops scales Shared Storage services automatically by raising or lowering the hardware resources of each database container.
+Zerops scales Shared Storage services automatically by raising or lowering the hardware resources of each container.
Vertical auto scaling has the following default configuration:
+:::note
+Disk size only gives the storage engine working space; the usable data capacity stays capped at 60GB. See [Storage Capacity](#storage-capacity).
+:::
+
For most cases, the default parameters will work without issues. If you need to limit the cost of the Shared Storage service, lower the maximal resources. Zerops will never scale above the selected maximums.
-When you are experiencing problems with insufficient Shared Storage performance or capacity, increase the minimal resources. Zerops will never scale below the selected minimums.
+When you are experiencing problems with insufficient Shared Storage performance, increase the minimal resources. Zerops will never scale below the selected minimums.
+
+You can change the auto scaling parameters at any time.
-:::note
-You can change the auto scaling parameters later.
-:::
----------------------------------------
@@ -39471,39 +41111,166 @@ Import configuration version:
Zerops offers Valkey in two deployment configurations to meet different availability requirements.
-### Non-HA Setup
+### Single Setup
- Single node deployment on port `6379` (non-TLS) and `6380` (TLS)
-- No backup mechanism beyond Zerops infrastructure reliability
-- Data persists unless the hardware node fails
- Suitable for development or non-critical workloads
+See [Persistence](#persistence) for how data is stored and recovered.
+
### HA (High Availability) Setup
-Our HA implementation uses a unique approach to ensure high availability while maintaining compatibility with all Redis clients:
-
-- 3-node configuration (1 master + 2 replicas)
-- Access ports:
- - `6379` - read/write operations (non-TLS, routed to master)
- - `6380` - read/write operations over TLS (routed to master)
- - `7000` - read-only operations (non-TLS)
- - `7001` - read-only operations over TLS
-- Implementation details:
- - All nodes are configured identically and listen on standard ports
- - First node in the cluster is designated as the master
- - On replica nodes, ports `6379`/`6380` traffic is forwarded to the master
- - Ports `7000`/`7001` are mapped locally to each node for direct replica access
- - When a master fails, a replica is promoted and routing is updated automatically
- - DNS entries are updated for seamless client connection
- - This implementation provides traffic forwarding to master (not natively supported by Valkey)
+The HA deployment is a 3-node cluster with automatic failover, fronted by an HAProxy load balancer on every node.
+
+- 3-node configuration: 1 primary + 2 replicas
+- Client-facing ports (available on every node):
+ - `6379` — read/write (non-TLS), routed to the current primary
+ - `6380` — read/write over TLS, routed to the current primary
+ - `7000` — read-only (non-TLS), load-balanced across replicas
+ - `7001` — read-only over TLS, load-balanced across replicas
+- Failover is handled by a built-in [Sentinel](https://valkey.io/topics/sentinel/) cluster. When the primary becomes unreachable, a replica is promoted automatically and HAProxy starts routing writes to it.
+- TLS is terminated at HAProxy.
+- Connect your application to the standard ports — the address never changes when the primary moves.
:::note
-Be aware that replica data may lag slightly behind the master due to asynchronous replication.
+Replica reads (ports `7000`/`7001`) can lag slightly behind the primary due to asynchronous replication.
:::
+**Failover client impact:** expect roughly 10–15 seconds of write unavailability while a new primary is elected and HAProxy reconverges. Read traffic on surviving replicas is unaffected.
+
:::tip Trusting the TLS certificate
-The certificates served on the TLS ports (`6380` and `7001`) are signed by the Zerops Certificate Authority. To verify them from outside Zerops, download and trust the [Zerops CA](/references/networking/zerops-ca) — e.g. `redis-cli --tls --cacert ./zerops-ca.pem -h -p 6380`.
+The certificates served on the TLS ports (`6380` and `7001`) are signed by the Zerops Certificate Authority. To verify them from outside Zerops, download and trust the [Zerops CA](/references/networking/zerops-ca) — e.g. `redis-cli --tls --cacert ./zerops-ca.pem -h -p 6380 -a `.
+:::
+
+## Connecting
+
+Zerops generates the connection details as environment variables on the Valkey service. Reference them from another service in the same project as `${_}` — for a service named `db`, the connection string is `${db_connectionString}`. The examples below assume the hostname `db`.
+
+| Variable | Example value | Notes |
+|---|---|---|
+| `hostname` | `db` | Service hostname; reachable as `db.zerops` inside the project |
+| `port` | `6379` | Plain (non-TLS) port |
+| `portTls` | `6380` | TLS port |
+| `password` | *(generated)* | Password for the `default` user (sensitive) |
+| `connectionString` | `redis://default:@db.zerops:6379` | Ready-to-use non-TLS URL |
+| `connectionTlsString` | `rediss://default:@db.zerops:6380` | Ready-to-use TLS URL |
+| `connectionTlsString` | `rediss://default:@db.zerops:6380` | Ready-to-use TLS URL |
+
+In **HA mode** four additional variables expose the read-only replica endpoints (load-balanced across replicas):
+
+| Variable | Example value | Notes |
+|---|---|---|
+| `portReplicas` | `7000` | Read-only plain port |
+| `portTlsReplicas` | `7001` | Read-only TLS port |
+| `connectionStringReplicas` | `redis://default:@db.zerops:7000` | Read-only non-TLS URL |
+| `connectionTlsStringReplicas` | `rediss://default:@db.zerops:7001` | Read-only TLS URL |
+| `connectionTlsStringReplicas` | `rediss://default:@db.zerops:7001` | Read-only TLS URL |
+
+The connection string format is `redis://default:@.zerops:` (or `rediss://` for TLS). The username is always `default`.
+
+:::note Authentication
+Valkey requires a password. It is generated automatically, exposed as the sensitive `${db_password}` variable, and already embedded in the `connectionString` variables above. Connect with it directly — e.g. `redis-cli -h db.zerops -p 6379 -a "$db_password"`.
+
+Services created **without** a `password` variable (older deployments) keep working without authentication and are unaffected. **All deployments created since this release require the password.**
+:::
+
+### Idle connection timeout
+
+Valkey closes connections that stay **idle for 5 minutes** (`timeout 300`). This is intentional on the managed instances — we avoid keeping infinite idle connections open. Older Valkey services ran with no timeout (`timeout 0`); if you connected before this change, your connections used to stay open indefinitely.
+
+"Idle" means **no commands sent on the connection** — the server resets the timer on every command, so a busy connection is never closed. The connections most likely to be affected are long-lived ones that sit waiting rather than sending commands, typically **pub/sub subscribers** and **blocking reads** (`BLPOP`, `XREAD`, …). Most clients reconnect automatically, so you may only see log lines such as `Redis subscriber socket closed; reconnecting if possible.` — but the reconnect churn can drop pub/sub messages published in the gap.
+
+To keep idle connections open, send an application-level **`PING` on an interval shorter than 300s**. A TCP keep-alive alone is **not** enough — keepalive packets live below the application layer and don't count as Valkey commands, so they don't reset the idle timer.
+
+Many clients have a built-in option for this. For example, [node-redis](https://github.com/redis/node-redis):
+
+```js
+const redisClient = createClient({
+ url: redisURL,
+ pingInterval: 10000, // send PING every 10s; keeps the connection under the 300s idle limit
+});
+```
+
+If your client has no equivalent option, run your own heartbeat on every long-lived connection:
+
+```js
+const heartbeat = setInterval(() => {
+ publisher.ping().catch(() => {});
+ subscriber.ping().catch(() => {});
+}, 60000); // any interval under 300s
+```
+
+For ordinary request/response traffic, a [connection pool](https://valkey.io/topics/clients/) that recycles connections handles this transparently.
+
+## Persistence
+
+Valkey persists data to disk with **AOF (append-only file)**, so the dataset survives restarts and is rebuilt automatically on startup.
+
+- **AOF is enabled** (`appendonly yes`) and synced to disk **every second** (`appendfsync everysec`). After an unclean crash you lose at most ~1 second of the most recent writes.
+- **RDB snapshots are disabled** (`save ""`) — durability relies on AOF, not periodic snapshots.
+
+**Durability by mode:**
+- **Single:** the AOF lives on the node's local disk. Data survives service restarts but is lost if the underlying hardware node fails and no backup exists.
+- **HA:** writes are additionally replicated to two replicas, so the dataset survives the loss of any single node via automatic failover.
+
+:::note Backups
+Platform-managed encrypted backups are available for both Single and HA setups. They are **disabled by default** — enable them on the service if you need point-in-time recovery beyond AOF and replication.
+:::
+
+## Memory and Autoscaling
+
+You don't set `maxmemory` directly. Zerops sizes it at **80% of the container's available RAM** — precisely 80% of the *smaller* of your configured maximum RAM and the cgroup-allocated RAM. It is re-evaluated and adjusted automatically about every 30 seconds, so `maxmemory` tracks the container as it scales vertically. The remaining 20% covers Valkey's internal overhead (fork on AOF rewrite / replica sync, fragmentation) and the OS.
+
+:::warning Keep minimum free RAM above 20% when customizing autoscaling
+If you edit the autoscaling configuration, keep the **minimum free RAM above 20%**. Zerops caps `maxmemory` at 80% of available RAM, so the dataset alone can never push free RAM below 20%. If your minimum free RAM threshold is at or below 20%, the scale-up trigger may **never fire at all** — free RAM never crosses it, so the service stays stuck at its current size and starts evicting keys (or rejecting writes under `noeviction`) instead of scaling up. Setting the threshold above 20% lets the dataset's growth toward the 80% cap cross the trigger, so the service scales up in time and keeps headroom for the fork during an AOF rewrite or replica sync. The built-in profiles all keep this threshold above 20%.
+:::
+
+:::note Check the logs for OOM events
+Watch the service's runtime logs for out-of-memory events — typically the kernel OOM-killer terminating and restarting Valkey when a fork during an AOF rewrite or replica sync briefly inflates memory. Recurring OOMs mean the reserved headroom isn't enough for your workload's peaks. Raise the **minimum free RAM** (more headroom) or the **minimum RAM** (a higher floor) until they stop.
+:::
+
+## Tunable Parameters
+
+The `maxmemory-policy` Valkey setting is exposed as an **autoscaling profile override**. In the GUI, open the service's **Automatic scaling configuration**, click **Adjust scaling** and set them under **Overrides**. Zerops applies the change live — **no service restart**, no client reconnect. In HA mode the change is rolled out to every node.
+
+To set the parameters at creation time, use `profileOverrides` in your import YAML (a `profile` must be selected to use overrides — available profiles are `hobby`, `staging` and `production`):
+
+```yaml
+services:
+ - hostname: redis
+ type: valkey:ha@7.2
+ profile: staging
+ profileOverrides:
+ maxmemory-policy: noeviction
+```
+
+:::note Migrating from environment variables
+Services created before profile overrides existed configure this setting via the `VALKEY_MAXMEMORY_POLICY` environment variable. It keeps working, but once a profile override is set it takes precedence over the environment variable.
:::
+### `maxmemory-policy`
+
+Default: `allkeys-lru`. Controls what Valkey does when the dataset reaches `maxmemory`.
+
+| Value | Behavior | When to use |
+|---|---|---|
+| `noeviction` | Reject writes with an OOM error | Datasets where every key must be preserved (session storage without TTL, job queues). Requires careful capacity planning. |
+| `allkeys-lru` | Evict least-recently-used keys | General-purpose caching — the safe default |
+| `allkeys-lfu` | Evict least-frequently-used keys | Hot/cold workloads where access frequency matters more than recency |
+| `allkeys-random` | Evict random keys | Uniform access patterns (rare) |
+| `volatile-lru` | Evict LRU keys *with a TTL set* | Mixed workloads: persistent keys without TTL are protected, cache keys with TTL are evictable |
+| `volatile-lfu` | Evict LFU keys with a TTL | Same as `volatile-lru`, frequency-based |
+| `volatile-random` | Evict random keys with a TTL | Rarely appropriate |
+| `volatile-ttl` | Evict keys with the shortest remaining TTL | When TTL reflects priority |
+| `volatile-ttl` | Evict keys with the shortest remaining TTL | When TTL reflects priority |
+
+:::warning `noeviction` and memory pressure
+With `noeviction`, Valkey cannot free memory on its own — once the dataset reaches `maxmemory`, writes fail with OOM errors until the service scales up or keys are deleted. Make sure your autoscaling limits (maximum RAM) leave enough room for the dataset's growth.
+:::
+
+## Metrics
+
+Prometheus-compatible metrics are exported by default for scraping, on the port given by the `ZEROPS_PROMETHEUS_PORT` variable (`db:9121`).
+
## Learn More
- [Official Valkey Documentation](https://valkey.io/docs) - Comprehensive guide to Valkey features
@@ -39576,11 +41343,11 @@ repeated failure"]
reachability -->|needs human| blocker
behavior -->|needs human| blocker
- classDef user fill:#f7fbff,stroke:#2d72d9,stroke-width:1.5px,color:#172033;
- classDef zcpbox fill:#eef7f1,stroke:#32845a,stroke-width:1.5px,color:#172033;
- classDef work fill:#fffaf0,stroke:#c47f17,stroke-width:1.5px,color:#172033;
- classDef done fill:#f5f3ff,stroke:#7157d9,stroke-width:1.5px,color:#172033;
- classDef stop fill:#fff1f2,stroke:#d33f49,stroke-width:1.5px,color:#172033;
+ classDef user stroke:#2d72d9,stroke-width:1.5px;
+ classDef zcpbox stroke:#32845a,stroke-width:1.5px;
+ classDef work stroke:#c47f17,stroke-width:1.5px;
+ classDef done stroke:#7157d9,stroke-width:1.5px;
+ classDef stop stroke:#d33f49,stroke-width:1.5px;
class intent user;
class state,scope zcpbox;
@@ -39977,7 +41744,7 @@ To start, add remote setup in Zerops or initialize local setup beside your edito
**Zerops token.** The MCP server connects through `ZCP_API_KEY`, a Zerops token limited to one project. Remote setup gets it from the platform; local setup reads it from `.mcp.json`. Token details live in [Tokens and credentials](/zcp/security/tokens-and-project-access).
-**Workspace freedom.** The `zcp@1` service is still a normal Zerops service. You can install another agent CLI, add private MCP servers or helper tools, edit `CLAUDE.md`, add team dotfiles, and adapt the workspace. Details live in [What remote workspace gives you](/zcp/setup/hosted-workspace#make-customization-persistent).
+**Workspace freedom.** The `zcp@1` service is still a normal Zerops service. You can install another agent CLI, add private MCP servers or helper tools, edit `CLAUDE.md`, add team dotfiles, and adapt the workspace. Details live in [What remote workspace gives you](/zcp/setup/hosted-workspace).
:::caution Production boundary
Use this setup for development or staging work. Production should stay in a separate Zerops project and receive released work through your CI or release process; see [Promote to production](/zcp/workflows/promote-to-production) for the practical flow and [Production boundary](/zcp/security/production-policy) for the policy.
diff --git a/apps/docs/static/llms-small.txt b/apps/docs/static/llms-small.txt
index ad3a3c6a..29459910 100644
--- a/apps/docs/static/llms-small.txt
+++ b/apps/docs/static/llms-small.txt
@@ -8191,7 +8191,7 @@ Zerops provides an automated, secure backup system for supported services. This
## Supported Services
-Zerops provides automated backup functionality for the following services. For specific backup format details and restore instructions, visit each service's documentation: [MariaDB](/mariadb/how-to/backup), [PostgreSQL](/postgresql/how-to/backup), [Qdrant](/qdrant/overview), [Elasticsearch](/elasticsearch/overview), [NATS](/nats/overview), [Meilisearch](/meilisearch/overview), and [Shared Storage](/shared-storage/how-to/backup).
+Zerops provides automated backup functionality for the following services. For specific backup format details and restore instructions, visit each service's documentation: [MariaDB](/mariadb/how-to/backup), [PostgreSQL](/postgresql/how-to/manage#backups), [Qdrant](/qdrant/overview), [Elasticsearch](/elasticsearch/overview), [NATS](/nats/overview), [Meilisearch](/meilisearch/overview), and [Shared Storage](/shared-storage/how-to/manage#backups).
## Managing Backups in the UI
@@ -9014,6 +9014,20 @@ Remember that only publicly accessible objects will be cached by the CDN. Privat
Zerops was built on the idea of **environment parity** — giving developers the full development lifecycle, from remote development to highly available production, with the observability and developer tools for maximum flexibility, and sensible defaults so the configs stay reasonable. Turns out that's **exactly what coding agents need** to produce and iterate on production-ready applications.
+
+
+
+
## What is it
An **[MCP server](/zcp/overview)** for your agents — with an optional remote cloud development environment container to run them in — that makes use of the flexibility and processes Zerops provides. There's no "Zerops system prompt" hogging your context window. The MCP server is a thin layer that makes **your agent a Zerops platform power user** — when needed — so it understands:
@@ -10586,8 +10600,8 @@ Usage:
Flags:
--archive-file-path string If set, zCLI creates a tar.gz archive with the application code in the required path relative
to the working directory. By default, no archive is created.
- --deploy-git-folder Sets a custom path to the zerops.yaml file relative to the working directory. By default zCLI
- looks for zerops.yaml in the working directory.
+ --deploy-git-folder If set, the .git folder is also uploaded with the deploy. By default, the .git folder
+ is ignored.
-h, --help the service deploy command.
--project-id string If you have access to more than one project, you must specify the project ID for which the
command is to be executed.
@@ -14374,6 +14388,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
@@ -14407,21 +14423,19 @@ 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 |
-| 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` |
+| 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.
----------------------------------------
@@ -14508,18 +14522,10 @@ 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
----------------------------------------
@@ -14572,16 +14578,16 @@ 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 [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}`)
----------------------------------------
@@ -14595,28 +14601,16 @@ zsc cdn purge /style.css$ # Purge exact file
| Need | Choice | Why |
|------|--------|-----|
-| **Any caching need** | **Valkey** (default) | Active development, full HA, Redis-compatible |
-| Legacy KeyDB apps | KeyDB | Only if migrating existing KeyDB deployment |
-| Legacy KeyDB apps | KeyDB | Only if migrating existing KeyDB deployment |
+| **Any caching need** | **Valkey** (default) | Active development, optional HA, Redis-compatible |
+| **Any caching need** | **Valkey** (default) | Active development, optional HA, Redis-compatible |
## 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
+- **HA vs single → the `type` variant:** `valkey:single@7.2` (default) / `valkey:ha@7.2`. Only `7.2` exists — the platform rejects `valkey@8` (`serviceStackTypeNotFound`).
+- **Scaling tier → the `profile` field:** `hobby` / `staging` *(default)* / `production`. Recommend `hobby` for dev (cheapest), `staging` for production; escalate to `production` only on a clear load signal. Omitting `profile` applies `staging`.
+- **Legacy form** (older YAML / existing services): `type: valkey@7.2` + `mode:` — `mode: NON_HA` ≡ `:single`, `mode: HA` ≡ `:ha`. Still accepted (remaps to the variant) but `mode` is deprecated; author new YAML as the variant + profile.
+- Connection: Valkey **requires auth** on Zerops — use `${cache_connectionString}` (full auth'd URL) or supply `${cache_password}` (e.g. `REDIS_PASSWORD: ${cache_password}`). There is no separate `${cache_user}` (default user + password); an unauthenticated `redis://${hostname}:6379` fails with `NOAUTH Authentication required`.
----------------------------------------
@@ -14624,42 +14618,64 @@ zsc cdn purge /style.css$ # Purge exact file
# Guides > Choose Database
-**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.
## 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 |
-| Analytics / OLAP | ClickHouse | Columnar storage, ReplicatedMergeTree, 4 protocol ports |
+| Analytics / OLAP | ClickHouse | Columnar storage, ReplicatedMergeTree |
+| Analytics / OLAP | ClickHouse | Columnar storage, ReplicatedMergeTree |
+
+## PostgreSQL — two import-YAML choices, both fixed for the service's life
+
+**1. HA vs single node → the `type` variant** (NOT a `mode:` field):
+
+- `postgresql:single@` — one node. Default for dev and most apps.
+- `postgresql:ha@` — 3-node cluster + proxies. Production-grade; resources start at the production tier.
-## PostgreSQL (Default Choice)
+**2. Scaling tier → the `profile` field** (autoscaling envelope + PostgreSQL tuning preset):
-- 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
+| Workload | `:single` profile | `:ha` profile |
+|---|---|---|
+| dev / prototype / learning — cheapest | **`oltp-hobby`** | — (HA has no hobby) |
+| staging / early-stage app | `oltp-staging` *(single default)* | `oltp-staging` |
+| business-critical transactional | `oltp-production` | `oltp-production` *(HA default)* |
+| high-throughput OLTP at scale | — | `oltp-enterprise` |
+| analytics / warehouse | `olap-production` | `olap-production` |
+| ingestion / write-heavy | `writeheavy-production` | `writeheavy-production` |
+| ingestion / write-heavy | `writeheavy-production` | `writeheavy-production` |
+
+**Recommended defaults — set `profile` explicitly; escalate only on a clear signal:** dev → `postgresql:single@` + `profile: oltp-hobby` (lowest cost); production → `profile: oltp-staging` (move to `oltp-production` / `oltp-enterprise` only when load clearly warrants it). Omitting `profile` applies the default — single → `oltp-staging`, **HA → `oltp-production` (dedicated CPU + high minima)** — which over-provisions HA.
+
+```yaml
+services:
+ - hostname: db # dev — cheapest
+ type: postgresql:single@18
+ profile: oltp-hobby
+ - hostname: db # production HA — default tier (profile optional)
+ type: postgresql:ha@18
+ profile: oltp-production
+```
+
+- `profileOverrides` (individual params: `work_mem`, `max_wal_size`, `autovacuum_*`, …) is valid **only** with `profile: custom`.
+- `verticalAutoscaling` (CPU mode, min/max CPU/RAM/disk) tightens the envelope on top of any profile — no need for `custom` just to resize.
+
+**Legacy form — you may still meet it** in older YAML, recipes, or existing services: `type: postgresql@` + a separate `mode:` field. The mapping is `mode: NON_HA` ≡ `:single`, `mode: HA` ≡ `:ha`. The platform still accepts the legacy form (it remaps to the variant) but `mode` is **deprecated and ignored by validation**. Author new YAML as `:single`/`:ha` + `profile`; treat a discovered `mode:` as its equivalent variant.
+
+- 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
+- Type variant: `mariadb:single@` / `mariadb:ha@`. No `profile` — scale with `verticalAutoscaling`.
+- 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
+- Type variant: `clickhouse:single@` / `clickhouse:ha@`. No `profile` — scale with `verticalAutoscaling`.
+- HA: replicated databases use a `Replicated(...)` engine `ON CLUSTER`; tables use a `Replicated*MergeTree` engine (without `ON CLUSTER`).
----------------------------------------
@@ -14680,74 +14696,29 @@ zsc cdn purge /style.css$ # Purge exact file
| Event sourcing / audit logs | Kafka | Indefinite topic retention, strong ordering |
| 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.
-
----------------------------------------
# Guides > Choose Runtime Base
-**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 |
| Pre-built Docker images | Docker | VM-based, bring your own image |
-| CGO / native libs | Ubuntu | Better glibc compatibility than Alpine's musl |
-| 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 ` (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
@@ -14759,66 +14730,42 @@ NATS exposes **two distinct messaging shapes**. Pick ONE per recipe and write ya
- 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
-
----------------------------------------
# Guides > Choose Search
-**Use Meilisearch** for simple full-text search. Use **Elasticsearch** for advanced queries or HA requirements. Use **Qdrant** for vector/AI search.
+Four managed search engines, chosen by workload. **Meilisearch** for simple full-text search (the default). **Elasticsearch** for advanced queries or HA full-text. **Typesense** for autocomplete with typo-tolerance (HA-capable). **Qdrant** for vector / AI similarity search.
## Decision Matrix
| Need | Choice | Why |
|------|--------|-----|
-| **Simple full-text search** | **Meilisearch** (default) | Instant setup, typo-tolerant, frontend-safe keys |
-| Advanced queries / HA | Elasticsearch | Cluster support, plugins, JVM tuning |
-| Autocomplete + typo-tolerance | Typesense | Raft HA, CORS built-in, fast |
-| Vector / AI similarity | Qdrant | gRPC + HTTP, automatic cluster replication |
-| Vector / AI similarity | Qdrant | gRPC + HTTP, automatic cluster replication |
+| Simple full-text search (default) | **Meilisearch** | Instant setup, typo-tolerant, frontend-safe keys; single-node only (no HA) |
+| Advanced queries / HA full-text | **Elasticsearch** | Multi-node cluster, plugins, JVM tuning |
+| Autocomplete + typo-tolerance, with HA | **Typesense** | 3-node Raft consensus, CORS built-in, fast |
+| Vector / AI similarity | **Qdrant** | HTTP + gRPC, automatic cluster replication; internal-only |
+| Vector / AI similarity | **Qdrant** | HTTP + gRPC, automatic cluster replication; internal-only |
## 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
----------------------------------------
@@ -14845,14 +14792,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: ${{ 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 ` instead
## GitLab Integration (Webhook)
@@ -14863,15 +14811,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=""` when you return to the ZCP develop flow
## GitLab CI
@@ -14913,7 +14859,7 @@ Any CI system with shell access can deploy via `zcli push`:
# Guides > Cloudflare
-Always use **Full (strict)** SSL mode in Cloudflare — "Flexible" causes redirect loops. Shared IPv4 with Cloudflare proxy is not recommended.
+Use **Full (strict)** SSL mode in Cloudflare for production (plain "Full" is acceptable for testing) — **never "Flexible"**, which causes redirect loops. Shared IPv4 with Cloudflare proxy is not recommended.
## DNS Configuration
@@ -14922,14 +14868,6 @@ Always use **Full (strict)** SSL mode in Cloudflare — "Flexible" causes redire
CNAME
```
-### With Cloudflare Proxy (orange cloud)
-| IP Type | Record | Proxy |
-|---------|--------|-------|
-| IPv6 only | `AAAA ` | Proxied |
-| Dedicated IPv4 | `A ` | Proxied |
-| Shared IPv4 | **Not recommended** | Reverse AAAA lookup issues |
-| Shared IPv4 | **Not recommended** | Reverse AAAA lookup issues |
-
### DNS-Only (gray cloud)
| IP Type | Records Required |
|---------|-----------------|
@@ -14945,12 +14883,6 @@ Method B: CNAME *.
ACME: CNAME _acme-challenge..zerops.zone
```
-## SSL/TLS Settings (Cloudflare Dashboard)
-- **Encryption mode: Full (strict)** — mandatory
-- **Never use "Flexible"** — causes infinite redirect loops
-- Enable "Always Use HTTPS"
-- WAF exception: Skip rule for `/.well-known/acme-challenge/` (ACME validation)
-
## Preparing a Service for Cloudflare
Any runtime service (nodejs, go, python, etc.) can be put behind Cloudflare. Steps:
@@ -14967,16 +14899,13 @@ Any runtime service (nodejs, go, python, etc.) can be put behind Cloudflare. Ste
3. **Configure Cloudflare DNS** to point to your Zerops project IP
4. **Set SSL mode to "Full (strict)"** in Cloudflare dashboard
-**Important**: The `zerops_subdomain enable` tool only works on deployed (ACTIVE) services. For new services, use `enableSubdomainAccess: true` in import YAML.
+The `enableSubdomainAccess: true` in step 1 is the portable mechanism — the first deploy (or GUI toggle) activates the L7 route; you do not need a separate enable step. (*In ZCP, `zerops_deploy` auto-enables it on first deploy; see the public-access guide for the eligible-mode list and the `serviceStackIsNotHttp` port-shape rejection in the Gotchas below.*)
Internal service-to-service communication must always use `http://` — never `https://`. SSL terminates at the Zerops L7 balancer.
## Gotchas
-1. **Flexible SSL = redirect loop**: Zerops forces HTTPS, Cloudflare Flexible sends HTTP → infinite redirect
-2. **Shared IPv4 + proxy is broken**: Reverse AAAA lookup doesn't work with Cloudflare proxy on shared IPv4
-3. **ACME challenge needs WAF exception**: Without it, Cloudflare blocks Let's Encrypt validation
-4. **Wildcard SSL on Cloudflare Free**: Free plan doesn't proxy wildcard subdomains — use DNS-only or upgrade
-5. **Subdomain on undeployed service**: `zerops_subdomain enable` returns "Service stack is not http or https" on READY_TO_DEPLOY services — deploy code first or use `enableSubdomainAccess` in import YAML
+1. **Wildcard SSL on Cloudflare Free**: Free plan doesn't proxy wildcard subdomains — use DNS-only or upgrade
+2. **"Service stack is not http or https"**: `zerops_subdomain enable` returns this when the service has no HTTP-shaped port (a worker, or a port without `httpSupport: true`) — it's about port shape, not deploy state. A READY_TO_DEPLOY service WITH `httpSupport: true` can be enabled; a deployed worker still cannot.
----------------------------------------
@@ -15065,38 +14994,9 @@ Default behavior (`temporaryShutdown: false`):
5. Old container processes terminated
6. Old containers deleted
-### temporaryShutdown Behavior
-
-| Setting | Behavior | Downtime |
-|---------|----------|----------|
-| `false` (default) | New containers start BEFORE old ones stop | **Zero downtime** |
-| `true` | Old containers stop BEFORE new ones start | **Temporary downtime** |
-| `true` | Old containers stop BEFORE new ones start | **Temporary downtime** |
-
-Use `temporaryShutdown: true` only when you cannot run two versions simultaneously (e.g., database migrations, singleton locks).
-
----
-
-## Readiness Check vs Health Check
-
-| Aspect | Readiness Check | Health Check |
-|--------|----------------|--------------|
-| When | **During deploy only** | **Continuously after deploy** |
-| Purpose | Gates traffic to new containers | Detects runtime failures |
-| Location | `deploy.readinessCheck` | `run.healthCheck` |
-| Failure action | Container marked failed after timeout, replaced | Container restarted |
-| Failure action | Container marked failed after timeout, replaced | Container restarted |
+### Deploy strategy & checks
-### Readiness Check Mechanics
-
-1. Application starts via `start` command
-2. Readiness check runs (httpGet or exec)
-3. If **fails** -- wait `retryPeriod` seconds (default 5s), retry
-4. If **succeeds** -- container marked active, receives traffic
-5. If still failing after `failureTimeout` (default 300s / 5 min) -- container deleted, new one created
-
-**httpGet**: succeeds on HTTP `2xx`, follows `3xx` redirects, 5-second per-request timeout
-**exec.command**: succeeds on exit code 0, 5-second per-command timeout
+`temporaryShutdown` (in the `deploy` block) controls cutover order: `false` (default) starts new containers before removing old ones (zero-downtime); `true` stops old before new (downtime, use only when two versions cannot coexist — exclusive DB migrations, singleton locks). The **readiness check** gates traffic to the new container during a deploy; the **health check** monitors the live app continuously. For check behavior, params, the httpGet/exec timeouts, and the production pattern, see `zerops_knowledge uri="zerops://guides/readiness-health-checks"`.
---
@@ -15106,7 +15006,7 @@ Typical pipeline events in chronological order:
1. **`stack.build` process RUNNING** -- build container created, pipeline started
2. **`stack.build` process FINISHED** -- build complete, artifact uploaded
-3. **`appVersion` build event ACTIVE** -- deploy started, containers launching
+3. **`appVersion` build event ACTIVE** -- the new version is deployed and running (this is the terminal success state, NOT "launching")
4. **Service status returns to RUNNING** -- all containers active, deploy complete
**Terminal states:**
@@ -15135,23 +15035,20 @@ Zerops keeps **10 most recent versions**. Older auto-deleted. Any archived versi
## Gotchas
-1. **Build and run are SEPARATE containers** -- build output does not automatically appear in runtime. You must specify `deployFiles`
-2. **initCommands run on EVERY container start** -- including restarts and horizontal scaling, not just deploys
-3. **initCommands failures do NOT cancel deploy** -- app starts regardless of init exit code
-4. **prepareCommands in build vs run** -- `build.prepareCommands` customizes build env, `run.prepareCommands` creates custom runtime image. Different containers, different purposes
-5. **deployFiles land in `/var/www`** -- tilde syntax (`dist/~`) extracts contents directly to `/var/www/` (strips directory). Without tilde, `dist` → `/var/www/dist/` (preserved). **CRITICAL**: `run.start` path must match — `dist/~` + `start: bun dist/index.js` BREAKS because the file is at `/var/www/index.js`, not `/var/www/dist/index.js`
+1. **initCommands run on EVERY container start** -- including restarts and horizontal scaling, not just deploys
+2. **initCommands failures DO fail the deploy** -- `run.initCommands` run during runtime-prepare, BEFORE the start command, and are deploy-gating: a non-zero exit aborts the deploy. The platform emits `RUN.INIT COMMANDS FINISHED WITH ERROR` in the runtime log and surfaces the failed command + exit code on the `stack.build` process (`commandExec` / "init command failed"). The new appVersion goes to `DEPLOY_FAILED` and is **never activated**; the start command never runs and the previously-active version keeps serving. **Diagnose via `appVersion.status` (`DEPLOY_FAILED`) + `activationDate` (`null`), NOT the service status** — the service stays `ACTIVE` on the old version, so service-status alone reads as "fine". Keep init commands idempotent and exit 0.
## SSHFS Mount and Deploy Interaction
When using SSHFS (`zerops_mount`) for dev workflows, deploy replaces the container. This has important consequences:
1. **After deploy, run container only has `deployFiles` content.** All other files (including zerops.yml if not in deployFiles) are gone. Use `deployFiles: [.]` for dev services to ensure zerops.yml and source files survive the deploy cycle.
-2. **SSHFS mount auto-reconnects after deploy.** No explicit remount is needed — the SSHFS reconnect mechanism handles the container replacement transparently. The mount only becomes truly stale during stop (container not running); after start it auto-reconnects again.
+2. **SSHFS mount auto-reconnects only while the service is running.** Usually no remount is needed, but if the mount goes stale after a deploy (stat/ls returns empty, writes hang), recover it explicitly with `zerops_mount action="mount"`. A stopped service has no live mount until it starts again.
3. **zerops.yml must be in deployFiles** for dev self-deploy lifecycle. Without it, subsequent deploys from the container fail because zerops.yml is missing.
**Two kinds of "mount" (disambiguation):**
- `zerops_mount` -- SSHFS tool, mounts service `/var/www` locally for development. This is a dev workflow tool.
-- Shared storage mount -- platform feature, attaches a shared-storage volume at `/mnt/{hostname}` via `mount:` in import.yml + zerops.yml `run.mount`. These are completely unrelated features.
+- Shared storage mount -- platform feature, attaches a shared-storage volume at `/mnt/{hostname}` via the service-level `mount:` parameter in import.yml. There is no `mount` field in zerops.yml (a `run.mount` is silently ignored). These are completely unrelated features.
----------------------------------------
@@ -15187,11 +15084,11 @@ Total order for the bare key (highest wins): **system/platform > yaml-baked `run
Build and runtime run in **separate containers**. Variables from one phase are not visible in the other unless explicitly referenced with prefixes:
-| Want to access | From | Use prefix |
-|---------------|------|-----------|
-| Runtime var `API_KEY` | Build container | `${RUNTIME_API_KEY}` |
-| Build var `BUILD_ID` | Runtime container | `${BUILD_BUILD_ID}` |
-| Build var `BUILD_ID` | Runtime container | `${BUILD_BUILD_ID}` |
+| Want to access | From | How |
+|---------------|------|-----|
+| Runtime var `API_KEY` | Build container | `${RUNTIME_API_KEY}` — runtime `run.envVariables` are known at build time, so the build can read them |
+| Build var `BUILD_ID` | Runtime container | **Not available.** The build container is destroyed after build and its vars are not carried into the runtime env store — `${BUILD_BUILD_ID}` reaches the runtime process as the literal string `${BUILD_BUILD_ID}`. Persist the value into a deployed file, or recompute it at runtime. |
+| Build var `BUILD_ID` | Runtime container | **Not available.** The build container is destroyed after build and its vars are not carried into the runtime env store — `${BUILD_BUILD_ID}` reaches the runtime process as the literal string `${BUILD_BUILD_ID}`. Persist the value into a deployed file, or recompute it at runtime. |
```yaml
zerops:
@@ -15217,14 +15114,9 @@ run:
CACHE_URL: ${cache_connectionString}
```
-```javascript
-// App reads the names you mapped above:
-const host = process.env.DB_HOST;
-```
-
- The reference **resolves at container start**, independent of isolation mode — the referenced var does not need to exist at definition time.
- An **unresolved ref stays literal** (`${db_hostname}` reaches the process verbatim) — no error, no blank. A wrong hostname/var on the right-hand side becomes a literal string and the app fails at connect time.
-- **Hostname transformation**: dashes become underscores. Service `my-db` variable `port` is `${my_db_port}`.
+- **Hostname charset**: service hostnames are lowercase alphanumeric only (`[a-z0-9]`) — the platform rejects dashes, underscores, and uppercase with `serviceStackNameInvalid`. So a ref is simply `${hostname_varname}` with the literal hostname (service `cache` → `${cache_port}`); there is no dash-to-underscore rewrite to reason about, because a dashed hostname cannot exist.
Only legacy `envIsolation=none` auto-injects every sibling's vars as bare `_KEY` OS env vars without a ref — see Isolation Modes. New projects are `service`; rely on explicit refs.
@@ -15320,7 +15212,7 @@ The frontend consumes `API_URL` via plain `${API_URL}` in `build.envVariables` (
- Defined via GUI, import.yml `envSecrets`, or `dotEnvSecrets`
- **Read is privilege-gated** -- masked in GUI; via API an admin/write token returns the value verbatim, a read-only token returns `REDACTED` (keyed on `sensitive=true`). In-container the value is plaintext (the app needs it). Project-level `sensitive=true` does NOT persist — only service-level is a true secret surface.
- Can be updated without redeploy, but the service **must be restarted** to pick it up.
-- Overridden by yaml-baked `run.envVariables` with the same key (yaml owns the key).
+- A yaml-baked `run.envVariables` key and a secret on the **same key cannot coexist** — the platform rejects the secret with `userDataDuplicateKey`. The yaml owns the key; edit the yaml and redeploy to change it.
### dotEnvSecrets
@@ -15369,16 +15261,7 @@ An env-store change (secret or project) propagates to the container in ~5–10s
## System-Generated Variables
-Zerops auto-generates variables per service (e.g., `hostname`, `PATH`, DB connection strings). Cannot be deleted. Some read-only (`hostname`), others editable (`PATH`). Reference them from another service with an explicit `${hostname_varname}`.
-
-## Common Mistakes
-
-- **DO NOT** expect a sibling's vars to appear automatically under default `service` isolation — reference them explicitly as `${hostname_varname}` in `run.envVariables` (the bare `_KEY` injected form is `none`-only legacy).
-- **DO NOT** re-reference a var under its SAME name -- self-shadow loop. Project vars auto-inherit (read directly); cross-service uses a DIFFERENT left-hand name (`DB_HOST: ${db_hostname}`).
-- **DO NOT** set a secret/service var on a key already in `run.envVariables` -- rejected (`userDataDuplicateKey`); the yaml owns the key, edit yaml + redeploy.
-- **DO NOT** assume secret values are unreadable -- API read is privilege-gated (admin verbatim, read-only `REDACTED`), not unconditionally write-only.
-- **DO NOT** forget restart after GUI/API env changes -- the running process won't see new values.
-- **DO NOT** expect `envReplace` to recurse subdirectories -- it does not.
+Zerops auto-generates variables per service (e.g., `hostname`, `PATH`, DB connection strings). Some are **hard-reserved** and rejected if you try to set them — `PATH` (uppercase) returns `userDataUseOfSystemKey` in any `envVariables` block. Overridable platform vars include `envIsolation` / `sshIsolation` / `zeropsSubdomainHost` (and the CDN URLs) — but never `PATH`. Reference any of them from another service with an explicit `${hostname_varname}`.
----------------------------------------
@@ -15388,40 +15271,125 @@ Zerops auto-generates variables per service (e.g., `hostname`, `PATH`, DB connec
Zerops uses nftables with restricted TCP ports 1-1024 (only 22, 53, 80, 123, 443, 587 allowed); UDP and ports 1025-65535 are unrestricted.
-## TCP Ports 1-1024 (Restricted)
-
-| Port | Protocol | Status |
-|------|----------|--------|
-| 22 | SSH | Allowed |
-| 25 | SMTP | **Blocked** (spam prevention) |
-| 53 | DNS | Allowed |
-| 80 | HTTP | Allowed |
-| 123 | NTP | Allowed |
-| 443 | HTTPS | Allowed |
-| 465 | SMTPS | **Blocked** (deprecated) |
-| 587 | SMTP/STARTTLS | Allowed |
-| All others | — | **Blocked** |
-| All others | — | **Blocked** |
-
-## UDP Ports
-No restrictions on any UDP port.
-
-## TCP Ports 1025-65535
-No restrictions.
-
-## Direct Port Access Firewall
-For services with direct port access enabled:
-- Configure **blacklist** or **whitelist** rules per port
-- Available on ports 10-65435
-- Protocols: TCP, UDP
-
## Port Modification
Contact `support@zerops.io` with Project ID + Organization ID to request changes to restricted ports.
+
+----------------------------------------
+
+# Guides > Local Development
+
+
+Develop locally with hot reload while connecting to Zerops managed services (DB, cache, storage) via VPN. ZCP generates `.env` with real credentials. Deploy to Zerops with `zerops_deploy` which uses `zcli push` under the hood.
+
+---
+
+## Setup
+
+### Prerequisites
+- **zcli** installed: `npm i -g @zerops/zcli` or [docs.zerops.io/references/cli](https://docs.zerops.io/references/cli)
+- **VPN**: WireGuard (installed by zcli automatically on first `zcli vpn up`)
+- **Project-scoped token**: Create in Zerops GUI → Settings → Access Tokens → Custom access per project
+
+### Configuration
+```json
+// .mcp.json (in project root)
+{
+ "mcpServers": {
+ "zcp": {
+ "command": "zcp",
+ "env": { "ZCP_API_KEY": "" }
+ }
+ }
+}
+```
+
+---
+
+## Workflow
+
+### 1. Connect to Zerops services
+```bash
+zcli vpn up
+```
+- All services accessible by hostname (e.g., `db`, `cache`)
+- One project at a time — switching disconnects the current
+- **Env vars NOT available via VPN** — use `.env` file instead
+
+### 2. Load credentials
+ZCP writes `.env` via `zerops_env action="generate-dotenv"` (it merges three input channels — project `envVariables`, zerops.yaml `run.envVariables`, and `.env.local` — into one resolved file):
+```
+db_hostname=db
+db_port=5432
+db_password=
+db_connectionString=postgresql://db:@db:5432
+```
+> Don't hand-edit `.env` directly — the next `generate-dotenv` refuses with a diff if it finds keys it didn't produce. Put manual overrides in `.env.local` (a no-touch input channel that survives regeneration), or pass `force=true`.
+
+### 3. Develop locally
+Start your dev server as usual — hot reload works against Zerops managed services over VPN.
+
+### 4. Deploy to Zerops
+```
+zerops_deploy targetService="appstage"
+```
+Uses `zcli push` under the hood. Blocks until build completes.
+
+---
+
+## zerops.yml for Local Mode
+
+The same `zerops.yml` works for both local push and container deploy:
+
+```yaml
+zerops:
+ - setup: appstage
+ build:
+ base: nodejs@22
+ buildCommands:
+ - npm ci
+ - npm run build
+ deployFiles: ./dist
+ run:
+ start: node dist/server.js
+ ports:
+ - port: 3000
+ httpSupport: true
+ envVariables:
+ DB_URL: ${db_connectionString}
+```
+
+`${hostname_varName}` references are resolved by Zerops at container runtime — they work regardless of push source (local or container).
+
+---
+
+## Connection Troubleshooting
+
+| Symptom | Diagnosis | Fix |
+|---------|-----------|-----|
+| `nc -zv db 5432` times out | VPN not connected | `zcli vpn up ` |
+| VPN connected, still timeout | Wrong project | `zcli vpn up ` |
+| Connected but auth fails | Stale .env | Regenerate: `zerops_env action="generate-dotenv"` |
+| Service unreachable | Service stopped | `zerops_manage action="start" serviceHostname="db"` |
+| Service unreachable | Service stopped | `zerops_manage action="start" serviceHostname="db"` |
+
+### Diagnostic sequence
+1. `zerops_discover service="db"` — is service RUNNING?
+2. `nc -zv db 5432 -w 3` — network reachable?
+3. Compare `.env` vs `zerops_env action="generate-dotenv" preview=true` (or `zerops_discover includeEnvValues=true` for stored values — `includeEnvs` returns key templates, not resolved values) — credentials current?
+
+---
+
+## Multi-Project
+
+Each project directory has its own `.mcp.json` + `.zcp/state/`. VPN is one per machine — switch manually.
+
+---
+
## Gotchas
-1. **Port 25 is permanently blocked**: Use port 587 with STARTTLS for email sending
-2. **Port 465 is blocked**: Legacy SMTPS — use 587 instead
-3. **Cannot self-service unblock**: Must contact Zerops support for port exceptions
+
+1. **`.env` contains secrets**: Add to `.gitignore` immediately — never commit
+2. **Object storage (S3)**: Uses HTTPS apiUrl — may work without VPN but not fully verified. Include VPN as fallback
----------------------------------------
@@ -15438,15 +15406,12 @@ Zerops captures stdout/stderr as logs; use syslog output format for severity fil
## Access Methods
-### GUI
-- Project detail → service → Logs section
-- Filter by severity, time range, container
-
### CLI
```bash
-zcli service log # Runtime logs
-zcli service log --showBuildLogs # Build logs
+zcli service log -S # Runtime logs (select via -S/--service-id, NOT a positional name)
+zcli service log -S --show-build-logs # Build logs
```
+Agents in this ecosystem read runtime logs via the `zerops_logs` MCP tool — it fetches **runtime logs only** (no build-log flag); build logs surface by auto-attachment on a deploy-failure response.
## Severity Filtering
Logs must output to **syslog format** for severity filtering to work. Plain stdout/stderr logs appear as "info" level.
@@ -15482,11 +15447,7 @@ Certificate paths:
- Custom certs: `ca-file("/etc/syslog-ng/user.crt")`
## Gotchas
-1. **Syslog format required**: Without syslog formatting, all logs appear as same severity — no filtering possible
-2. **Build logs separate**: Use `--showBuildLogs` flag in CLI — not shown by default
-3. **Source name must be `s_src`**: Using `s_sys` (common default) will not capture Zerops logs
-4. **UDP for Logstash**: Zerops forwards logs via UDP syslog — ensure Logstash listens on UDP
-5. **Custom certs path**: Place custom CA certs in `/etc/syslog-ng/user.crt`
+1. **UDP for Logstash**: Zerops forwards logs via UDP syslog — ensure Logstash listens on UDP
----------------------------------------
@@ -15510,12 +15471,13 @@ Zerops supports ELK (APM + logs) and Prometheus/Grafana stacks; expose `/metrics
| `logstash` | Log collection |
### APM Configuration
-```yaml
-envVariables:
- ELASTIC_APM_ACTIVE: "true"
- ELASTIC_APM_SERVICE_NAME: my-app
- ELASTIC_APM_SERVER_URL: https://apmserver.zerops.app
- ELASTIC_APM_SECRET_TOKEN:
+
+Set these on your app as service env vars (GUI or `run.envVariables`). Copy the real APM server URL from the `apmserver` service's subdomain in the GUI — it's a generated subdomain (`apmserver--..zerops.app`), **not** a fixed `apmserver.zerops.app` host:
+```
+ELASTIC_APM_ACTIVE=true
+ELASTIC_APM_SERVICE_NAME=my-app
+ELASTIC_APM_SERVER_URL=https://
+ELASTIC_APM_SECRET_TOKEN=
```
## Prometheus + Grafana Stack Services
@@ -15530,20 +15492,15 @@ envVariables:
### Custom Metrics
1. Expose HTTP `/metrics` endpoint in your app
-2. Set env var: `ZEROPS_PROMETHEUS_PORT=8080` (comma-separated for multiple ports)
+2. Set env var: `ZEROPS_PROMETHEUS_PORT=` (e.g. `9090`; comma-separated for multiple ports)
3. Prometheus auto-discovers and scrapes
## Built-in Metrics
- Service scaling & resource usage
-- PostgreSQL (with `pg_stat_statements` extension)
+- PostgreSQL (some metrics require the `pg_stat_statements` extension — superuser `CREATE EXTENSION` + restart)
- MariaDB
- Valkey
-## Gotchas
-1. **`ZEROPS_PROMETHEUS_PORT` is required**: Without it, Prometheus won't discover your custom metrics endpoint
-2. **APM server must be public**: Use Zerops subdomain to expose apmserver for trace collection
-3. **Cross-project needs forwarder**: Use `prometheuslight` service in source project to forward to global Prometheus
-
----------------------------------------
@@ -15556,14 +15513,6 @@ Zerops networking has two layers: a private VXLAN network per project (service-t
## Architecture Overview
-```
-Internet
- │
- ├─ HTTP/HTTPS ──→ L7 Balancer (SSL termination, nginx) ──→ container VXLAN IP:port
- │
- └─ Direct port ──→ L3/Core Balancer ──→ container VXLAN IP:port
-```
-
**Per-project infrastructure:**
- **Private VXLAN network** — isolated overlay network shared by all services
- **L7 HTTP Balancer** — 2 HA containers, auto-scales, domain routing + SSL
@@ -15600,7 +15549,10 @@ The L7 balancer is **nginx-based**, deployed as 2 HA containers per project. It
The balancer forwards client info via standard headers:
- **`X-Forwarded-For`** / **`X-Real-IP`** — original client IP
-- **`X-Forwarded-Proto`** — `https` (original protocol)
+- **`X-Forwarded-Proto`** — original protocol (`http` or `https`)
+- **`X-Forwarded-Host`** / **`X-Forwarded-Port`** — original host and port
+
+These headers are set by the balancer itself (client-sent values are overwritten), and the `Forwarded` and `Proxy` headers are stripped from incoming requests.
Your app receives plain HTTP but can inspect these headers for the real client info.
@@ -15645,16 +15597,7 @@ Work through these steps **in order**:
6. **Service status** — Is the service ACTIVE? (check `zerops_discover`)
7. **Timeout settings** — For slow responses, increase `send_timeout` (default 2s)
-**Common framework fixes:**
-```bash
-app.listen(3000, '0.0.0.0')
-
-flask run --host=0.0.0.0
-
-http.ListenAndServe(":8080", handler) // implicit 0.0.0.0
-
-server.address=0.0.0.0
-```
+**Fix:** bind the listen address to `0.0.0.0` (e.g. `app.listen(3000, '0.0.0.0')`), never `127.0.0.1`/`localhost`.
---
@@ -15686,12 +15629,7 @@ server.address=0.0.0.0
---
## Gotchas
-1. **Binding localhost = 502**: The L7 balancer connects via VXLAN IP, not localhost — always bind `0.0.0.0`
-2. **Internal HTTPS breaks things**: Service-to-service must use `http://` — the VXLAN network is already isolated
-3. **Subdomain 50MB cap**: zerops.app subdomains have a hard 50MB upload limit — use custom domain for larger files
-4. **send_timeout default is 2s**: Slow API responses may be cut off — increase for long-running endpoints
-5. **Cross-project networking impossible**: Each project is an isolated VXLAN — use public access to bridge projects
-6. **Shared IPv4 needs AAAA**: Missing AAAA record = silent routing failure on shared IPv4
+1. **Cross-project networking impossible**: Each project is an isolated VXLAN — to bridge projects, use public access (L7/public endpoint), not private hostnames
----------------------------------------
@@ -15740,67 +15678,9 @@ https://endpoint.com/bucket-name/object-key
https://bucket-name.endpoint.com/object-key
```
-**Every S3 client must be configured for path-style access.**
-
-## Framework Integration
-
-### PHP (Laravel — Flysystem)
-```php
-// config/filesystems.php
-'s3' => [
- 'driver' => 's3',
- 'endpoint' => env('S3_ENDPOINT'),
- 'use_path_style_endpoint' => true, // REQUIRED
- 'key' => env('S3_ACCESS_KEY'),
- 'secret' => env('S3_SECRET_KEY'),
- 'region' => env('S3_REGION', 'us-east-1'),
- 'bucket' => env('S3_BUCKET'),
-],
-```
-Package: `league/flysystem-aws-s3-v3`
-
-### Node.js (AWS SDK v3)
-```javascript
-
-const s3 = new S3Client({
- endpoint: process.env.S3_ENDPOINT,
- forcePathStyle: true, // REQUIRED
- credentials: {
- accessKeyId: process.env.S3_ACCESS_KEY,
- secretAccessKey: process.env.S3_SECRET_KEY,
- },
- region: process.env.S3_REGION || 'us-east-1',
-});
-```
-Package: `@aws-sdk/client-s3`
-
-### Python (boto3)
-```python
-import boto3
-s3 = boto3.client('s3',
- endpoint_url=os.environ['S3_ENDPOINT'],
- aws_access_key_id=os.environ['S3_ACCESS_KEY'],
- aws_secret_access_key=os.environ['S3_SECRET_KEY'],
- region_name='us-east-1',
- config=boto3.session.Config(s3={'addressing_style': 'path'}), # REQUIRED
-)
-```
-Package: `boto3`
-
-### Java (AWS SDK)
-```java
-S3Client s3 = S3Client.builder()
- .endpointOverride(URI.create(System.getenv("S3_ENDPOINT")))
- .serviceConfiguration(S3Configuration.builder()
- .pathStyleAccessEnabled(true) // REQUIRED
- .build())
- .credentialsProvider(StaticCredentialsProvider.create(
- AwsBasicCredentials.create(
- System.getenv("S3_ACCESS_KEY"),
- System.getenv("S3_SECRET_KEY"))))
- .region(Region.US_EAST_1)
- .build();
-```
+**Every S3 client must be configured for path-style access** (the SDK-specific
+flag: `forcePathStyle`/`use_path_style_endpoint`/`addressing_style: path`/
+`pathStyleAccessEnabled`). Framework wiring lives in the recipe for that stack.
## import.yaml Definition
@@ -15852,13 +15732,13 @@ Each service = one bucket (auto-named, immutable). Need multiple buckets? Create
# Guides > Php Tuning
-Override php.ini via `PHP_INI_*` env vars, FPM via `PHP_FPM_*`. Both require **restart** (not reload). Zerops defaults: upload/post = 1024M, FPM dynamic 20/2/1/3. Upload bottleneck is L7 balancer (50MB subdomain), not PHP.
+Override php.ini via `PHP_INI_*` env vars, FPM via `PHP_FPM_*`. **Applying a change depends on the channel**: values in `run.envVariables` are baked into the app version → changing one requires a **redeploy**; values set via `zerops_env`/GUI service env → changing one requires a **restart** (never reload — see Gotchas). Zerops defaults: upload/post = 1024M, FPM dynamic 20/2/1/3. Upload bottleneck is L7 balancer (50MB subdomain), not PHP.
## PHP Configuration (`PHP_INI_*`)
Override any php.ini directive via `PHP_INI_{directive}` env vars in `run.envVariables` or via `zerops_env` API.
-**Requires restart** to take effect. Reload writes config files (`/etc/php*/conf.d/overwrite.ini`) but FPM master does not re-read INI on reload.
+To apply a CHANGE: if the value lives in `run.envVariables` (baked into the app version), **redeploy**; if set via `zerops_env`/GUI service env, **restart**. A plain reload rewrites the config files (`/etc/php*/conf.d/overwrite.ini`) but the FPM master does not re-read INI on reload, so reload alone never applies the change.
### Zerops Platform Defaults
@@ -15893,7 +15773,7 @@ zerops:
## PHP-FPM (`PHP_FPM_*`)
-Configure FPM process management via `PHP_FPM_*` env vars. **Requires restart** — same as PHP_INI.
+Configure FPM process management via `PHP_FPM_*` env vars — same change semantics as `PHP_INI_*`: redeploy for `run.envVariables`, restart for `zerops_env`/GUI service env, never reload.
Config files are written to `/etc/php*/php-fpm.d/www.conf` by `zerops-zenv` at container startup.
@@ -15915,12 +15795,13 @@ Pre-forks a pool of workers. Good for consistent traffic.
High-traffic example:
```yaml
-envVariables:
- PHP_FPM_PM_MAX_CHILDREN: 50
- PHP_FPM_PM_START_SERVERS: 10
- PHP_FPM_PM_MIN_SPARE_SERVERS: 5
- PHP_FPM_PM_MAX_SPARE_SERVERS: 15
- PHP_FPM_PM_MAX_REQUESTS: 1000
+run:
+ envVariables:
+ PHP_FPM_PM_MAX_CHILDREN: 50
+ PHP_FPM_PM_START_SERVERS: 10
+ PHP_FPM_PM_MIN_SPARE_SERVERS: 5
+ PHP_FPM_PM_MAX_SPARE_SERVERS: 15
+ PHP_FPM_PM_MAX_REQUESTS: 1000
```
### Ondemand Mode
@@ -15928,11 +15809,12 @@ envVariables:
Spawns workers only when requests arrive. Saves memory for low-traffic sites.
```yaml
-envVariables:
- PHP_FPM_PM: ondemand
- PHP_FPM_PM_MAX_CHILDREN: 20
- PHP_FPM_PM_PROCESS_IDLE_TIMEOUT: 60s
- PHP_FPM_PM_MAX_REQUESTS: 500
+run:
+ envVariables:
+ PHP_FPM_PM: ondemand
+ PHP_FPM_PM_MAX_CHILDREN: 20
+ PHP_FPM_PM_PROCESS_IDLE_TIMEOUT: 60s
+ PHP_FPM_PM_MAX_REQUESTS: 500
```
Available parameters for ondemand:
@@ -15980,7 +15862,7 @@ run:
## Gotchas
-- **Reload does NOT apply changes** -- `PHP_INI_*` and `PHP_FPM_*` both require restart. Zerops reload rewrites config files via `zerops-zenv` but does not signal FPM to re-read them.
+- **Reload never applies the change** -- a value in `run.envVariables` needs a **redeploy** (it's baked into the app version); a value in `zerops_env`/GUI service env needs a **restart**. Reload rewrites config files via `zerops-zenv` but does not signal FPM to re-read them.
- **Upload fails at 50MB on subdomain** -- this is the L7 balancer limit, not PHP. Use a custom domain for larger uploads.
- **`post_max_size` must be >= `upload_max_filesize`** -- PHP silently drops the POST body if it exceeds `post_max_size`, even if the file itself is under `upload_max_filesize`.
@@ -15990,18 +15872,21 @@ run:
# Guides > Production Checklist
-Before going to production: (1) databases to HA mode, (2) minContainers: 2 on app services, (3) replace Mailpit with real SMTP, (4) remove Adminer, (5) use Object Storage for uploads, (6) use Redis/Valkey for sessions.
+Before going to production: (1) databases to HA (the `:ha` type variant), (2) minContainers: 2 on app services, (3) replace Mailpit with real SMTP, (4) remove Adminer, (5) use Object Storage for uploads, (6) use Valkey for sessions.
## Database
| Item | Dev | Production |
|------|-----|------------|
-| Mode | `NON_HA` | `HA` (must recreate) |
+| Deployment variant | `:single` (e.g. `postgresql:single@18`) | `:ha` (e.g. `postgresql:ha@18`) — must recreate |
+| Scaling profile | `oltp-hobby` | `oltp-staging` (escalate to `oltp-production` only on clear load) |
| Backups | Optional | Enabled |
| Connection | Single primary | Primary + read replicas |
| Connection | Single primary | Primary + read replicas |
-**HA is immutable** — cannot switch after creation. Delete and recreate with `mode: HA`.
+**HA is immutable** — cannot switch after creation. Delete and recreate with the `:ha` variant.
+
+**Legacy form** you may still see in older YAML or existing services: `type: postgresql@18` + a separate `mode:` field — `mode: NON_HA` ≡ `:single`, `mode: HA` ≡ `:ha`. Still accepted (remapped to the variant) but `mode` is deprecated and ignored by validation; author new YAML in the variant form.
## Application Services
@@ -16023,16 +15908,26 @@ Before going to production: (1) databases to HA mode, (2) minContainers: 2 on ap
## Dev Services to Remove
### Mailpit → Production SMTP
-```yaml
-- hostname: mailpit
- type: go@1
- buildFromGit: https://github.com/zeropsio/recipe-mailpit
-envVariables:
- SMTP_HOST: smtp.sendgrid.net
- SMTP_PORT: "587"
-envSecrets:
- SMTP_PASSWORD: your-production-key
+Mailpit (the dev mail catcher) is defined as:
+```yaml
+services:
+ - hostname: mailpit
+ type: alpine@3.20
+ buildFromGit: https://github.com/zeropsio/recipe-mailpit
+```
+For production, point your app at a real provider — non-secret settings in the app's `run.envVariables` (zerops.yaml), the key in `envSecrets` (import.yaml). They are different files; a bare top-level `envVariables:` block is schema-invalid.
+```yaml
+run:
+ envVariables:
+ SMTP_HOST: smtp.sendgrid.net
+ SMTP_PORT: "587"
+```
+```yaml
+services:
+ - hostname: app
+ envSecrets:
+ SMTP_PASSWORD: your-production-key
```
### Adminer → Remove or Restrict
@@ -16063,7 +15958,7 @@ Remove entirely or disable `enableSubdomainAccess`. Use VPN + pgAdmin/DBeaver lo
| Use case | Solution |
|----------|----------|
-| PHP sessions | Redis/Valkey |
+| PHP sessions | Valkey |
| Laravel sessions | Redis driver |
| Django sessions | Redis backend |
| Express sessions | Redis store |
@@ -16071,8 +15966,7 @@ Remove entirely or disable `enableSubdomainAccess`. Use VPN + pgAdmin/DBeaver lo
```yaml
- hostname: cache
- type: valkey@7.2
- mode: NON_HA # HA for production
+ type: valkey:single@7.2 # valkey:ha@7.2 for production
```
## Framework-Specific Production Settings
@@ -16117,31 +16011,13 @@ Remove entirely or disable `enableSubdomainAccess`. Use VPN + pgAdmin/DBeaver lo
| CPU mode | `cpuMode: DEDICATED` for consistent performance under load |
| Environment separation | Separate projects for dev/staging/prod |
| Stateless design | Sessions in Valkey, uploads in Object Storage — no local state |
-| Database mode | `mode: HA` for all managed services (immutable — plan before creation) |
-| Min containers | `minContainers: 2` on all app services for zero-downtime deploys |
-| Min containers | `minContainers: 2` on all app services for zero-downtime deploys |
+| Database HA | the `:ha` type variant for all HA-capable managed services (immutable — plan before creation) |
+| Min containers | `minContainers: 2+` on app services for throughput + crash-tolerance (rolling deploys are already zero-downtime at any count via the default `temporaryShutdown: false` — don't conflate the two) |
+| Min containers | `minContainers: 2+` on app services for throughput + crash-tolerance (rolling deploys are already zero-downtime at any count via the default `temporaryShutdown: false` — don't conflate the two) |
## Health Check Pattern
-Combined readiness + runtime health check for production services:
-
-```yaml
-zerops:
- - setup: app
- deploy:
- readinessCheck:
- httpGet:
- port: 3000
- path: /health
- run:
- healthCheck:
- httpGet:
- port: 3000
- path: /health
- start: node server.js
-```
-
-Readiness check gates traffic during deploy. Health check runs continuously — unhealthy containers are restarted after 5-minute retry window.
+Production services should pair a `deploy.readinessCheck` (gates traffic during deploy) with a `run.healthCheck` (continuous — the LB routes around an unhealthy container). The combined pattern, params, and behavior are in `zerops_knowledge uri="zerops://guides/readiness-health-checks"`.
## Gotchas
1. **HA is immutable**: Must delete and recreate service to switch modes
@@ -16166,8 +16042,8 @@ Zerops offers three public access methods: zerops.app subdomains (dev only, 50MB
- Max upload: **50 MB**
- **Not for production** — use for development/testing only
- Auto-provisioned SSL
-- Pre-configure via import YAML: `enableSubdomainAccess: true` (works for all runtime/web types)
-- **Activate routing:** `zerops_deploy` **auto-enables** the subdomain on the first deploy for eligible service modes (dev/stage/simple/standard/local-stage) and waits HTTP-ready — the deploy response carries `subdomainAccessEnabled: true` and the URL. Use `zerops_subdomain enable` only as an explicit recovery/ops command if auto-enable was skipped (a worker / non-HTTP service, or launch-production which deliberately opts out in favor of a custom domain). Import's `enableSubdomainAccess: true` pre-configures intent; deploy activates the L7 balancer. Re-deploys do NOT deactivate it. Use `zerops_discover` to check current status and get the URL (`subdomainEnabled` + `subdomainUrl` fields).
+- **Enable it:** set `enableSubdomainAccess: true` in the import YAML (works for all runtime/web types) to pre-configure intent; the first deploy (or the GUI toggle) activates the L7 subdomain route, and re-deploys never deactivate it.
+- *In ZCP:* `zerops_deploy` auto-enables the subdomain on the first deploy for eligible service modes (dev/stage/simple/standard/local-stage) and waits HTTP-ready — the deploy response carries `subdomainAccessEnabled: true` and the URL. `zerops_subdomain enable` is the explicit recovery/ops command if auto-enable was skipped (a worker / non-HTTP service, or launch-production which deliberately opts out in favor of a custom domain). `zerops_discover` shows current status (`subdomainEnabled` + `subdomainUrl`).
- **Port-specific subdomains**: If HTTP ports are defined in zerops.yml, each port gets its own subdomain: `{hostname}-{subdomainHost_prefix}-{port}.{subdomainHost_rest}`. Example: hostname `appdev`, subdomainHost `1df2.prg1.zerops.app`, port 3000 → actual URL `https://appdev-1df2-3000.prg1.zerops.app`. Port 80 omits the port suffix: `https://appdev-1df2.prg1.zerops.app`
- **Internal network fallback**: Every service is accessible internally via `http://{hostname}:{port}` (e.g., `http://appdev:3000`). Use this to verify the app is running when subdomain access is uncertain — `curl http://appdev:3000/health` from the ZCP container or any other service in the project
- Works for: nodejs, static, nginx, go, python, php, java, rust, dotnet, and all other runtime types
@@ -16175,15 +16051,8 @@ Zerops offers three public access methods: zerops.app subdomains (dev only, 50MB
### 2. Custom Domains (Production)
- Per-project HTTPS balancer (2 containers, HA)
- Round-robin load balancing + health checks
-- Full upload limit: 512 MB
-- Requires IP address assignment:
-
-| IP Type | Cost | Protocol | Notes |
-|---------|------|----------|-------|
-| Shared IPv4 | Free | HTTP/HTTPS only | Limited connections, shorter timeouts |
-| Dedicated IPv4 | $3/30 days | All protocols | Non-refundable, auto-renews |
-| IPv6 | Free | All protocols | Dedicated per project |
-| IPv6 | Free | All protocols | Dedicated per project |
+- Upload limit: 512 MB default (`client_max_body_size`, configurable up to 2048m on a custom domain) — not a hard cap
+- Requires IP address assignment
### 3. Direct Port Access
- Available for: Runtime services, PostgreSQL
@@ -16191,17 +16060,101 @@ Zerops offers three public access methods: zerops.app subdomains (dev only, 50MB
- Protocols: TCP, UDP
- Configurable firewall: blacklist or whitelist per port
-## DNS Setup (Custom Domain)
-Point your domain to the project's IP:
-- `A` record → Dedicated IPv4
-- `AAAA` record → IPv6
-- Shared IPv4: Requires **both A and AAAA** records (AAAA needed for SNI routing)
-
## Gotchas
-1. **Shared IPv4 needs AAAA record**: Without AAAA, SNI routing fails — always add both A and AAAA
-2. **zerops.app 50MB limit**: File uploads over 50MB fail on subdomains — use custom domain
-3. **Dedicated IPv4 is non-refundable**: $3/30 days, auto-renews — cannot get refund if removed early
-4. **Ports 80/443 reserved**: Your app cannot bind to these — Zerops uses them for SSL termination
+1. **Dedicated IPv4 is non-refundable**: $3/30 days, auto-renews — the fee isn't refunded if removed early, but the address can be reused in another project until the subscription ends
+
+
+----------------------------------------
+
+# Guides > Readiness Health Checks
+
+
+Two distinct mechanisms, often confused. **Readiness check** (`deploy.readinessCheck`) runs ONLY during a deploy — it gates when the new container starts receiving traffic; if it never passes, the deploy fails and the old version keeps serving. **Health check** (`run.healthCheck`) runs CONTINUOUSLY on the live app — it disconnects an unhealthy container from the load balancer, restarts it, and reconnects it on recovery. Both support `httpGet` or `exec` (mutually exclusive within one block). The field shape lives in the zerops.yml schema; this guide owns the behavior.
+
+---
+
+## The distinction (the #1 confusion)
+
+| | Readiness check | Health check |
+|---|---|---|
+| Location | `deploy.readinessCheck` | `run.healthCheck` |
+| When it runs | **During a deploy only** | **Continuously, after startup** |
+| Purpose | Gate traffic to a NEW container | Detect runtime failure of a LIVE container |
+| On failure | Deploy fails; new appVersion not activated; old version keeps serving | Container removed from LB → restarted → reconnected on recovery |
+| On failure | Deploy fails; new appVersion not activated; old version keeps serving | Container removed from LB → restarted → reconnected on recovery |
+
+A readiness check makes a deploy wait for the app to actually answer before cutting traffic over. A health check keeps a degraded container out of rotation while it's live. Use both on production services.
+
+## Readiness check (`deploy.readinessCheck`)
+
+Checks the **new** container at `localhost`. Until it passes, traffic stays on the old container.
+
+```yaml
+deploy:
+ readinessCheck:
+ httpGet: { port: 3000, path: /health }
+ failureTimeout: 60 # seconds until the container is marked failed
+ retryPeriod: 10 # seconds between attempts
+```
+
+Mechanics: `start` runs → readiness check runs → on fail, wait `retryPeriod` and retry → on success, the container is marked active and receives traffic → if still failing after `failureTimeout`, the container is deleted and the deploy fails (the previous appVersion stays active). Set `failureTimeout`/`retryPeriod` explicitly — there is no fixed schema default to rely on.
+
+## Health check (`run.healthCheck`)
+
+Runs on every container continuously after startup.
+
+```yaml
+run:
+ healthCheck:
+ httpGet: { port: 3000, path: /health }
+ failureTimeout: 30 # consecutive-failure seconds before restart (reset by a success)
+ disconnectTimeout: 30 # seconds before a failing container is pulled from the LB
+ recoveryTimeout: 30 # seconds of success before a restarted container takes traffic again
+ execPeriod: 10 # seconds between attempts
+```
+
+**Failure sequence**: repeated failures → `disconnectTimeout` removes the container from the load balancer → `failureTimeout` triggers a restart → `recoveryTimeout` gates traffic reconnection once it's healthy again.
+
+## httpGet vs exec (both checks)
+
+- **`httpGet`** — GET to `localhost:{port}{path}`, triggered **inside** the container. Success = HTTP `2xx` (follows `3xx` redirects), 5-second per-request timeout. `host` sets a custom Host header; `scheme: https` only if the app demands TLS internally (default is plain HTTP — the L7 balancer terminates SSL upstream).
+- **`exec`** — a local shell command, success = exit `0`, 5-second per-command timeout. Has access to all env vars. Use a YAML `|` block for multi-step scripts.
+
+**DO NOT** put both `httpGet` and `exec` in the same check block — they are mutually exclusive.
+
+## temporaryShutdown (deploy container ordering)
+
+Readiness gating only buys zero-downtime when the old container stays up during cutover — that is `temporaryShutdown` (in the `deploy` block):
+
+| Value | Behavior | Downtime |
+|---|---|---|
+| `false` (default) | New containers start and pass readiness BEFORE old ones are removed | None (zero-downtime rolling deploy) |
+| `true` | Old containers stop BEFORE new ones start | Yes |
+| `true` | Old containers stop BEFORE new ones start | Yes |
+
+Use `true` only when you cannot run two versions simultaneously (exclusive DB-migration access, singleton locks). Rolling cutover is zero-downtime at any `minContainers` value — don't conflate replica count with the deploy strategy.
+
+## Dev/stage placement
+
+In dev+stage pairs, `healthCheck` and `readinessCheck` belong ONLY on the **stage** entry. Dynamic-runtime dev services run `start: zsc noop --silent` (a no-op keepalive that idles the container while the agent drives the real dev server's lifecycle) — adding a `healthCheck` to a dev service causes unwanted container restarts during iteration.
+
+## Production pattern
+
+Combine both on a production service so deploys wait for readiness and the LB routes around runtime failures:
+
+```yaml
+zerops:
+ - setup: app
+ deploy:
+ readinessCheck:
+ httpGet: { port: 3000, path: /health }
+ run:
+ healthCheck:
+ httpGet: { port: 3000, path: /health }
+ start: node server.js
+```
+
+Without health checks, the load balancer cannot route around an unhealthy container — it keeps sending traffic to a degraded instance.
----------------------------------------
@@ -16209,7 +16162,7 @@ Point your domain to the project's IP:
# Guides > Scaling
-Zerops autoscales vertically (CPU/RAM/disk) and horizontally (container count). Runtimes support both. Managed services (DB, cache, shared-storage) support vertical only with fixed container count (NON_HA=1, HA=3). Object-storage and Docker have no autoscaling. Extends grammar.md section 9 with mechanics, thresholds, YAML syntax, and common mistakes.
+Zerops autoscales vertically (CPU/RAM/disk) and horizontally (container count). Runtimes support both. Managed services (DB, cache, shared-storage) support vertical only with fixed container count (the `:single` type variant = 1 node, `:ha` = 3). Object-storage and Docker have no autoscaling. Extends grammar.md section 9 with mechanics, thresholds, YAML syntax, and common mistakes.
## When to Scale Which Way
@@ -16227,12 +16180,12 @@ Zerops autoscales vertically (CPU/RAM/disk) and horizontally (container count).
|---|---|---|---|
| **Runtime** (Node.js, Go, PHP, Python, Java, etc.) | Yes | Yes (1-10 containers) | Full autoscaling |
| **Linux containers** (Alpine, Ubuntu) | Yes | Yes (1-10 containers) | Same as runtimes |
-| **Managed DB** (PostgreSQL, MariaDB) | Yes | No (fixed: NON_HA=1, HA=3) | Mode immutable after creation |
-| **Managed cache** (KeyDB/Valkey) | Yes | No (fixed: NON_HA=1, HA=3) | Mode immutable after creation |
-| **Shared storage** | No (automatic, not configurable) | No (fixed: NON_HA=1, HA=3) | DO NOT set verticalAutoscaling in import.yml |
+| **Managed DB** (PostgreSQL, MariaDB) | Yes | No (fixed: `:single`=1, `:ha`=3) | Variant immutable after creation |
+| **Managed cache** (Valkey) | Yes | No (fixed: `:single`=1, `:ha`=3) | Variant immutable after creation |
+| **Shared storage** | Yes (cpu/ram/disk configurable) | No (fixed: `:single`=1, `:ha`=3) | Accepts verticalAutoscaling in import.yml |
| **Object storage** | No | No | Fixed size at creation, no verticalAutoscaling |
-| **Docker** | No (manual, triggers VM restart) | Yes (VM count changeable, triggers restart) | No autoscaling at all |
-| **Docker** | No (manual, triggers VM restart) | Yes (VM count changeable, triggers restart) | No autoscaling at all |
+| **Docker** | No (manual, triggers VM restart) | Manual only (change VM count, triggers restart) | No automatic autoscaling |
+| **Docker** | No (manual, triggers VM restart) | Manual only (change VM count, triggers restart) | No automatic autoscaling |
## Vertical Autoscaling
@@ -16323,27 +16276,23 @@ Applies to **runtimes and Linux containers only**. New containers are added when
### Managed Services (DB, Cache, Shared Storage)
-Container count is **fixed by deployment mode**, set at creation, **immutable**:
+Container count is **fixed by the deployment variant in the type** (`postgresql:single@18` / `postgresql:ha@18`), set at creation, **immutable**:
-| Mode | Containers | Use case |
+| Variant | Containers | Use case |
|---|---|---|
-| `NON_HA` | 1 | Development, non-critical |
-| `HA` | 3 (on separate physical machines) | Production, automatic failover |
-| `HA` | 3 (on separate physical machines) | Production, automatic failover |
+| `:single` | 1 | Development, non-critical |
+| `:ha` | 3 (on separate physical machines) | Production, automatic failover |
+| `:ha` | 3 (on separate physical machines) | Production, automatic failover |
+
+**Legacy form** you may still see in older YAML: a separate `mode:` field — `mode: NON_HA` ≡ `:single`, `mode: HA` ≡ `:ha`. Still accepted (remapped to the variant) but deprecated and ignored by validation.
HA recovery: failed container is disconnected, new one created on different hardware, data synchronized from healthy copies, failed container removed.
PostgreSQL HA exposes read replica port **5433** for distributing SELECT queries.
-## Configuring Thresholds via zerops_scale
-
-Threshold parameters can be set via the `zerops_scale` MCP tool, not just import.yml:
+## Autoscaling Thresholds
-```
-zerops_scale serviceHostname="api" minFreeRamGB=0.5 minFreeRamPercent=5 minFreeCpuCores=0.2
-```
-
-All four threshold parameters (`minFreeRamGB`, `minFreeRamPercent`, `minFreeCpuCores`, `minFreeCpuPercent`) are optional and can be combined with any other scaling parameters in a single call.
+The dual-threshold trigger controls WHEN vertical scaling fires. All four fields (`minFreeRamGB`, `minFreeRamPercent`, `minFreeCpuCores`, `minFreeCpuPercent`) are optional and live in the `verticalAutoscaling` block (see import.yml Syntax below). (*In ZCP they can also be set live: `zerops_scale serviceHostname="api" minFreeRamGB=0.5 minFreeRamPercent=5 minFreeCpuCores=0.2`, combinable with any other scaling parameter in one call.*)
## Docker Services
- Run in **VMs**, not containers. **No autoscaling** -- resources fixed at creation
@@ -16373,8 +16322,7 @@ services:
# Managed DB (vertical only, no container settings)
- hostname: db
- type: postgresql@16
- mode: HA
+ type: postgresql:ha@16
verticalAutoscaling:
cpuMode: DEDICATED
minCpu: 1
@@ -16387,28 +16335,30 @@ services:
## Strategy Presets
-**Development** — SHARED CPU, min resources, 1 container. Cost-effective for dev/staging:
-```
-zerops_scale serviceHostname="api" cpuMode="SHARED" minCpu=1 maxCpu=2 minRam=0.25 maxRam=1 minContainers=1 maxContainers=1
+**Development** — SHARED CPU, min resources, single container (cost-effective for dev/staging):
+```yaml
+minContainers: 1
+maxContainers: 1
+verticalAutoscaling: { cpuMode: SHARED, minCpu: 1, maxCpu: 2, minRam: 0.25, maxRam: 1 }
```
**Production** — DEDICATED CPU, higher minimums, multiple containers for HA:
-```
-zerops_scale serviceHostname="api" cpuMode="DEDICATED" minCpu=2 maxCpu=8 minRam=2 maxRam=8 minContainers=2 maxContainers=6
+```yaml
+minContainers: 2
+maxContainers: 6
+verticalAutoscaling: { cpuMode: DEDICATED, minCpu: 2, maxCpu: 8, minRam: 2, maxRam: 8 }
```
-**Burst workloads** — Wide autoscaling range, SHARED CPU:
-```
-zerops_scale serviceHostname="worker" cpuMode="SHARED" minCpu=1 maxCpu=8 minRam=1 maxRam=16 minContainers=1 maxContainers=10
+**Burst workloads** — wide autoscaling range, SHARED CPU:
+```yaml
+minContainers: 1
+maxContainers: 10
+verticalAutoscaling: { cpuMode: SHARED, minCpu: 1, maxCpu: 8, minRam: 1, maxRam: 16 }
```
-## Common Mistakes
-
-**DO NOT** add `verticalAutoscaling` to **object-storage** or **shared-storage** services in import.yml -- causes import failure. Object storage has a fixed `objectStorageSize` only. Shared storage is managed automatically.
-
-**DO NOT** set `minContainers` or `maxContainers` for managed services (DB, cache, shared-storage) -- container count is fixed by `mode` (NON_HA=1, HA=3). Setting these causes import failure.
+(*In ZCP, the same presets apply live via `zerops_scale serviceHostname=... cpuMode=... minCpu=... ...`.*)
-**DO NOT** use `DEDICATED` CPU for low-traffic or dev services -- wastes resources. Use `SHARED` and switch to `DEDICATED` only when consistent performance matters.
+## Common Mistakes
**DO NOT** set `minFreeRamGB: 0` and `minFreeRamPercent: 0` simultaneously -- the API rejects this with "Invalid custom autoscaling value". Always keep at least the default absolute threshold (0.0625 GB).
@@ -16417,6 +16367,56 @@ zerops_scale serviceHostname="worker" cpuMode="SHARED" minCpu=1 maxCpu=8 minRam=
**DO NOT** assume horizontal scaling works automatically -- your application must be stateless. File-based sessions, local uploads, and in-memory state break with multiple containers.
+----------------------------------------
+
+# Guides > Shared Storage Integration
+
+
+Shared storage is a managed SeaweedFS volume mounted as a POSIX filesystem at `/mnt/` into one or more runtime services — for files that must be shared *between containers/services* (shared config, plugin directories, a common working set). It is mounted via the import.yaml `mount:` field; there is **no `zerops.yaml` mount**. For high-write workloads or user uploads, prefer Object Storage (S3) instead — shared storage is POSIX/NFS-style and not built for high-throughput churn.
+
+## Mounting — import.yaml `mount:` is the only config-file mechanism
+
+Declare the storage service, then list it under the runtime's service-level `mount:`. This auto-connects the storage at provision — import alone is sufficient, no second step:
+
+```yaml
+services:
+ - hostname: storage
+ type: shared-storage
+ - hostname: app
+ type: nodejs@22
+ buildFromGit: https://github.com/myorg/myapp # mount: requires buildFromGit
+ mount:
+ - storage # list one or more shared-storage hostnames
+```
+
+After deploy, the runtime has `/mnt/storage` (SeaweedFS FUSE, writable). Multiple volumes can be mounted to one service (`/mnt/files1`, `/mnt/files2`, …).
+
+**There is NO `zerops.yaml` mount field.** A `mount:` under `run:` is silently stripped by the platform — it even passes yaml validation (validation-passing ≠ honored), but produces no mount and no connection. Mounting is import.yaml-only (or `connect-storage`, below).
+
+## Connecting a storage to a runtime that missed the import mount
+
+A runtime that was READY_TO_DEPLOY at import time (e.g. a stage service created but not yet deployed) does NOT pick up the import `mount:`. Once it's ACTIVE, connect explicitly:
+
+```
+zerops_manage action="connect-storage" serviceHostname="app" storageHostname="storage"
+```
+
+This registers the connection, but the FUSE mount materializes **only on the next fresh deploy (new container creation)** — a plain restart does NOT bring it up. Redeploy the service after connecting.
+
+## Constraints & behavior
+
+- **Mount path**: always `/mnt/`. Runtime containers only — NOT available during build or `run.prepareCommands` phases.
+- **Mounting overwrites** any existing content in the mount directory.
+- **Capacity**: max 60 GB total (raise via support request); file size is unbounded within the 60 GB. `verticalAutoscaling` floors: RAM 0.5 GB, disk 5 GB.
+- **HA**: 1:1 replication with auto-failover; during a master failover the mount is briefly unavailable (~30s).
+- **POSIX**: standard filesystem ops (with minor permission-setting limits). Filesystem operations are logged to runtime logs tagged `zerops-mount-`. `df` can report misleading numbers — use the Zerops GUI for accurate storage metrics.
+- **No env vars**: shared storage exposes no connection variables — it's a filesystem, not a networked service.
+
+## Shared storage vs object storage
+
+Use **shared storage** when you need a POSIX filesystem shared across services (shared config, plugin/extension directories, a common scratch area). Use **object storage** (S3/MinIO) for user uploads, media, and any high-throughput or write-heavy file operations — and for anything that must survive independent of any single service. Don't reach for shared storage as a generic uploads bucket.
+
+
----------------------------------------
# Guides > Smtp
@@ -16445,19 +16445,31 @@ Only port **587** (STARTTLS) is allowed for outbound email — ports 25 and 465
| Amazon SES | `email-smtp.{region}.amazonaws.com` | 587 | Access key | Secret key |
## Configuration Example
+
+Non-secret SMTP settings belong in `run.envVariables` (zerops.yaml); the password is a secret in `envSecrets` (import.yaml, service level). These live in **different files** — a bare top-level `envVariables:`/`envSecrets:` block is rejected (`envVariables` is valid only under `build`/`run`).
+
```yaml
-envVariables:
- SMTP_HOST: smtp.sendgrid.net
- SMTP_PORT: "587"
- SMTP_USER: apikey
-envSecrets:
- SMTP_PASSWORD:
+zerops:
+ - setup: app
+ run:
+ envVariables:
+ SMTP_HOST: smtp.sendgrid.net
+ SMTP_PORT: "587"
+ SMTP_USER: apikey
+```
+
+```yaml
+services:
+ - hostname: app
+ type: nodejs@22
+ envSecrets:
+ SMTP_PASSWORD:
```
+A change to `envSecrets` requires a **service restart** to take effect.
+
## Gotchas
-1. **Port 25 is permanently blocked**: Cannot be unblocked — use 587 with STARTTLS
-2. **Port 465 is also blocked**: Legacy SMTPS is deprecated — use 587
-3. **Gmail needs App Password**: Regular Gmail passwords won't work — generate an App Password in Google Account settings
+- **Gmail SMTP**: a regular Gmail password fails auth — generate an App Password in Google Account settings and use it as `SMTP_PASSWORD`.
----------------------------------------
@@ -16531,7 +16543,7 @@ Zerops VPN uses WireGuard via `zcli vpn up ` — connects to one pro
## Commands
```bash
zcli vpn up # Connect
-zcli vpn up --auto-disconnect # Auto-disconnect on terminal close
+zcli vpn up --auto-disconnect # First disconnect an already-active VPN, then connect
zcli vpn up --mtu 1350 # Custom MTU (default 1420)
zcli vpn down # Disconnect
```
@@ -16554,16 +16566,11 @@ zcli vpn down # Disconnect
|---------|----------|
| Interface already exists | `zcli vpn down` then `zcli vpn up` |
| Hostname not resolving | Try `db.zerops` suffix. On Windows, add `zerops` to DNS suffix list. Note: `dig`/`nslookup` bypass system resolver — use `nc -zv db 5432` to test |
-| WSL2 not working | Enable systemd in `/etc/wsl.conf` under `[boot]` |
+| WSL2 not working | Set `systemd=true` in `/etc/wsl.conf` under `[boot]`, then `wsl --shutdown` |
| Conflicting VPN | Use `--mtu 1350` |
| Ubuntu 25.* issues | Install AppArmor utilities |
| Ubuntu 25.* issues | Install AppArmor utilities |
-## Gotchas
-1. **No env vars via VPN**: Must read env vars from GUI or API — VPN only provides network access
-2. **One project at a time**: Cannot connect to multiple projects simultaneously
-3. **Hostname resolution**: Both `hostname` and `hostname.zerops` work (VPN sets up DNS search domain). Use plain hostname for simplicity. If resolution fails on Windows, add `zerops` to DNS suffix list in Advanced TCP/IP Settings.
-
----------------------------------------
@@ -16574,56 +16581,9 @@ Behavioral semantics for advanced zerops.yml features: health/readiness checks,
---
-## Health Check Behavior
-
-Health checks run **continuously** on every container after startup. Two types (mutually exclusive):
-
-- **`httpGet`**: GET to `localhost:{port}{path}`. Success = 2xx. Runs **inside** the container. Use `host` for custom Host header, `scheme: https` only if app requires TLS.
-- **`exec`**: Shell command, success = exit 0. Has access to all env vars. Use YAML `|` for multi-command scripts.
-
-| Parameter | Purpose |
-|-----------|---------|
-| `failureTimeout` | Seconds of consecutive failures before container restart |
-| `disconnectTimeout` | Seconds before failing container is removed from load balancer |
-| `recoveryTimeout` | Seconds of success before restarted container receives traffic again |
-| `execPeriod` | Interval in seconds between check attempts |
-| `execPeriod` | Interval in seconds between check attempts |
-
-**Failure sequence**: repeated failures -> `disconnectTimeout` removes from LB -> `failureTimeout` triggers restart -> `recoveryTimeout` gates traffic reconnection.
-
-**DO NOT** configure both `httpGet` and `exec` in the same block.
-
----
-
-## Readiness Check Behavior
-
-Runs **only during deployments** to gate traffic switch to a new container.
-
-```yaml
-deploy:
- readinessCheck:
- httpGet: { port: 3000, path: /health }
- failureTimeout: 60
- retryPeriod: 10
-```
-
-**How it works**: Checks the **new** container at `localhost`. Until it passes, traffic stays on the old container. After `failureTimeout`, deploy fails and the old container remains active.
-
-**DO NOT** confuse with healthCheck -- readiness gates a deploy; healthCheck monitors continuously after.
-
-> **Dev/stage distinction**: In dev+stage pairs, healthCheck and readinessCheck belong ONLY on the stage entry. Dev services use `start: zsc noop --silent` — the agent controls server lifecycle via SSH. Adding healthCheck to dev causes unwanted container restarts during iteration.
-
----
-
-## temporaryShutdown
-
-| Value | Behavior | Downtime |
-|-------|----------|----------|
-| `false` (default) | New containers start first, old removed after readiness | None (zero-downtime) |
-| `true` | All old containers stop, then new ones start | Yes |
-| `true` | All old containers stop, then new ones start | Yes |
+## Health / Readiness Checks & temporaryShutdown
-Use `true` when: exclusive DB migration access needed, or brief downtime acceptable. Use `false` for: production web services, APIs, user-facing apps.
+Health checks (`run.healthCheck`, continuous), readiness checks (`deploy.readinessCheck`, deploy-time traffic gate), the httpGet/exec shape + params + failure sequence, the dev/stage placement rule (dev uses `start: zsc noop --silent`, no healthCheck), and `temporaryShutdown` deploy ordering are all owned by `zerops_knowledge uri="zerops://guides/readiness-health-checks"`.
---
@@ -16638,7 +16598,7 @@ run:
allContainers: false
```
-Parameters: `command` (required), `timing` (required, 5-field cron: `min hour dom mon dow`), `workingDir` (default `/var/www`), `allContainers` (`false` = one container, `true` = all containers).
+Parameters: `command` (required), `timing` (required, 5-field cron: `min hour dom mon dow`), `allContainers` (**required** by the schema — `false` = one container, `true` = all containers), `workingDir` (optional, default `/var/www`).
Cron runs inside the runtime container with full env var access. When `allContainers: false`, Zerops picks **one** container (good for DB jobs). Use `true` for cache clearing or log rotation everywhere. Minimum granularity is 1 minute.
@@ -16659,7 +16619,7 @@ run:
- litestream restore -if-replica-exists -if-db-not-exists $DB_NAME
```
-Each entry: `command` (required), `name` (required), `workingDir` (optional), `initCommands` (optional, per-process init). **DO NOT** use both `start` and `startCommands`.
+Each entry: `command` (**required** — the only field the schema requires), `name` (optional, distinguishes processes in logs), `workingDir` (optional), `initCommands` (optional, per-process init). **DO NOT** use both `start` and `startCommands`.
---
@@ -16748,7 +16708,7 @@ Configuration is **merged at the section level** -- child values override parent
Available runtimes and versions are listed in **Service Stacks (live)** -- injected by `zerops_knowledge` and workflow responses. Some key rules:
- PHP: build `php@X`, run `php-nginx@X` or `php-apache@X` (different bases)
-- Deno, Gleam: REQUIRES `os: ubuntu` (not available on Alpine)
+- Deno: REQUIRES `os: ubuntu` (no Alpine build exists). Gleam runs on both Alpine and Ubuntu.
- Static sites: build `nodejs@latest`, run `static`
- `@latest` = newest stable version
@@ -16772,6 +16732,7 @@ export const runtimes = [
{ name: "Bun", link: "/bun/overview", icon: },
{ name: "Elixir", link: "/elixir/overview", icon: },
{ name: "Gleam", link: "/gleam/overview", icon: },
+ { name: "Ruby", link: "/ruby/overview", icon: },
{ name: "Nginx", link: "/nginx/overview", icon: },
{ name: "Static", link: "/static/overview", icon: },
]
@@ -25704,119 +25665,30 @@ Have you build something that others might find useful? Don't hesitate to share
- [Zerops VPN](/references/networking/vpn) — Connect to your services easily with Zerops VPN.
-----------------------------------------
-
-# Postgresql > Faq
-
-
- **Question: How do I properly use PostgreSQL in HA mode?**
-
-In High Availability (HA) mode, PostgreSQL runs on multiple containers with a primary node and replicas. To get the most out of this setup:
-
- - Use port `5432` for all write operations (INSERT, UPDATE, DELETE) — this always routes to the primary node
- - Use port `5433` for read operations (SELECT) — this distributes queries across all replicas, improving performance
-
- The read replica port (`5433`) is only available in HA mode. If you're running PostgreSQL in single container (NON_HA) mode, only port `5432` is available.
-
- See [Connection Parameters](/postgresql/how-to/connect#connection-parameters) for all available ports and environment variables.
-
- **Question: Why is my connection to PostgreSQL from third-party software failing?**
-
-*One possible cause:*
-
- The connection string in Zerops always starts with `postgresql://`. While the official PostgreSQL documentation
- states that both `postgresql://` and `postgres://` URIs are valid, some software requires the shorter `postgres://`
- version.
-
- To resolve this, create your own environment variable with the correct URI. For example, if your PostgreSQL service is named `db`, use the following format:
-
- ```
- postgres://${db_user}:${db_password}@${db_hostname}:${db_port}
- ```
-
-
-----------------------------------------
-
-# Postgresql > How To > Backup
-
-
-Zerops provides automated data backup for PostgreSQL services with full encryption and flexible management options.
-
-For general backup information including configuration, scheduling, and management options, see the [Zerops Backups](/features/backup) documentation, which covers:
-- Backup scheduling and retention policies
-- Tagging system and storage quotas
-- Manual backup creation and CLI tools
-- Security and encryption details
-
-This page focuses on PostgreSQL-specific backup details.
-
-## PostgreSQL Backup Format
-
-PostgreSQL backups are created using `pg_dump` and stored in `.zip` format:
-
-- **Format**: `.zip` (containing per-schema `.dump` files)
-- **Tooling**: `pg_dump`
-- **Compression**: Custom format (`-Fc`), schema files named `schemaName.dump`
-- **Storage**: Encrypted and stored in isolated object storage
-
-## Restoring PostgreSQL Backups
-
-To restore a PostgreSQL backup:
-
-1. **Download** the backup file (`.zip`) from the Zerops UI
-2. **Extract** the zip file to access the individual schema dump files
-3. **Prepare** your target environment (clean existing data or use a new instance)
-4. **Restore** using PostgreSQL native tools. Follow the [official PostgreSQL backup documentation](https://www.postgresql.org/docs/current/backup-dump.html) for detailed restore procedures, or use web-based management tools like phpMyAdmin or Adminer as described in [PostgreSQL Management](/postgresql/how-to/manage).
-
-For assistance with the restoration process, contact Zerops support.
-
-## High Availability
-
-For PostgreSQL services running in High Availability mode:
-- Backups are created on a randomly selected healthy node
-- Other nodes remain operational during the backup process
-- Manual backups typically run on the primary node
-
-## Best Practices
-
-- Always create a manual backup with a protected tag before database migrations or major schema changes
-- Test your restore process periodically in a non-production environment
-- Monitor your backup storage usage in the Project Overview
-- Use descriptive tags like `pre-migration-v2` for important snapshots
-- Consider the order of schema restoration if you have dependencies between schemas
-
-For additional best practices and troubleshooting, refer to the [main backup documentation](/features/backup).
-
----------------------------------------
# Postgresql > How To > Connect
-This guide covers how to connect to your PostgreSQL database in Zerops, both from services within the same project and from outside the Zerops environment.
-
-## Connection Options Overview
-
-Zerops provides several ways to connect to PostgreSQL:
+This guide covers everything about reaching your PostgreSQL database in Zerops: connection details, ports and TLS, connection pooling, connecting from other services or from your workstation, and the database tools you can use.
-1. **Internal connections** - Between services in the same Zerops project (via private network)
-2. **Remote connections**:
- - **VPN access** - From your local machine via Zerops VPN
- - **Direct IP access** - Enables external applications to connect using TLS encryption by opening public ports on IPv6 (available by default) or IPv4 (requires add-on activation if not already enabled)
+## Connection options at a glance
-## Connection Details
+- **Internal**: between services in the same project, over the private network.
+- **Remote**: from outside Zerops, either through the [VPN](#connect-via-zerops-vpn) (your machine joins the private network) or through [public IP access](#connect-via-public-ip) (TLS through pgBouncer).
-You'll find PostgreSQL connection details in the service detail page under the **Peek access details** button (shows hostname, port, user, password, and connection string).
+## Connection details
-The full list of connection-related environment variables is available in the service detail under **Environment variables**.
+Find your connection details in the service detail page under **Peek access details** (hostname, port, user, password, connection string). The full list of connection-related environment variables lives under **Environment variables** in the same service detail.
-### Connection Parameters
+### Connection parameters
Parameter
Internal
-
External (TLS)
+
Public IP (TLS)
Env Variable
@@ -25841,7 +25713,7 @@ The full list of connection-related environment variables is available in the se
User
-
Identical to hostname
+
`db` (default)
Same as internal
`user`
@@ -25865,216 +25737,268 @@ The full list of connection-related environment variables is available in the se
Database name
-
db
+
`db` (default)
Same as internal
`dbName`
-:::tip
-If you're running PostgreSQL in High Availability (HA) mode, configure your application to route read queries to port **5433**. This distributes the load across all replicas, reducing pressure on the primary node and improving overall throughput.
-:::
-
:::warning
-Zerops creates a system user named `zps` with full privileges for maintenance purposes. Do not delete, change the password, or remove privileges from this user, as it will disrupt Zerops' ability to maintain the database cluster.
+Zerops creates a system user named `zps` with full privileges for maintenance. Do not delete it, change its password, or remove its privileges, as doing so disrupts Zerops' ability to maintain the database cluster.
:::
-:::info
-For more information about default PostgreSQL setup, users, and databases, see [Manage PostgreSQL Users and Databases](/postgresql/how-to/manage).
-:::
+## Connection ports and TLS
+
+PostgreSQL on Zerops exposes three ports, each for a different purpose:
+
+
-## Connect from Services in the Same Project
+- **Ports `5432` and `5433` do not support TLS.** Connect with `sslmode=disable`. Inside a project these ports are reachable only on the private network, and over the [VPN](#connect-via-zerops-vpn) the tunnel already encrypts the traffic, so requesting TLS on them fails the handshake.
+- **Port `6432` (pgBouncer) requires TLS.** Connect with at least `sslmode=require`; the connection is always encrypted. pgBouncer presents a certificate signed by the [Zerops CA](/references/networking/zerops-ca) but does not force the client to validate it, so `sslmode=require` is enough. If you want to verify the server's identity, use `sslmode=verify-full` together with the Zerops CA. This holds whether you reach `6432` from inside the project or over a public IP.
-All services within a Zerops project share a dedicated private network. There are two ways to implement connections between services in the same project:
+## Connection pooling with pgBouncer
-### Method 1: Direct Connection Parameters
+Port `6432` puts [pgBouncer](https://www.pgbouncer.org/) in front of PostgreSQL and multiplexes many client connections onto a small pool of server connections. This is valuable for applications that open many short-lived connections (serverless functions, PHP-FPM, or anything with high connection churn), and **we recommend connecting through `6432` rather than `5432` for these workloads, including for internal service-to-service connections.**
-You can directly use the connection parameters from Peek Access Details:
+A few things to know:
-```
-host = database1
-port = 5432
-user = database1
-password = ********** (find under Peek Access Details)
-```
+- **Transaction pooling.** A server connection is returned to the pool after each transaction, not when the client disconnects. Prepared statements are supported (cached per server connection), but session-scoped features that span transactions (session-level `SET`, advisory locks held across statements, `LISTEN`/`NOTIFY`) won't behave as expected. Use a direct `5432` connection for those.
+- **TLS is required** on `6432` (see [above](#connection-ports-and-tls)), even for internal connections.
+- **HA mode.** pgBouncer pools connections to the primary (writes). Read routing across replicas on port `5433` is separate and is not pooled.
+
+## Connect from services in the same project
+
+All services in a project share a private network, so other services reach PostgreSQL directly by its hostname. There are two ways to wire it up.
-For read operations in HA mode, use port `5433` instead of `5432` with the same credentials.
+### Direct connection parameters
-### Method 2: Environment Variables (Recommended)
+Use the parameters from **Peek access details**:
-For better maintainability, Zerops creates environment variables for each PostgreSQL service that you can use in your application configuration. List of service environment variables is available in Zerops GUI. Go to a PostgreSQL service detail and choose **Environment variables**.
+```ini
+host = db # your PostgreSQL service hostname
+port = 5432 # 5433 for read-only replicas in HA mode
+user = db # default user
+password = **********
+```
-To use variables from one service in another, prefix the variable name with the service hostname and underscore - to access the `connectionString` variable of `postgresql1`, use `postgresql1_connectionString`.
+### Environment variables (recommended)
-For read-only connections (HA mode only), use the `connectionStringReplicas` variable instead.
+Zerops generates connection environment variables for every PostgreSQL service. To use one service's variables from another, prefix the variable name with the service hostname and an underscore. For example, to read the `connectionString` of service `db`, reference `db_connectionString`.
-For more details on how to use environment variables, and instructions for adding your own custom variables, see the [Environment Variables](/features/env-variables) documentation.
+For read-only connections (HA mode only), use `connectionStringReplicas` instead.
+
+See the [Environment Variables](/features/env-variables) documentation for details and for adding your own variables.
:::caution Important notes
-- When changing passwords, update both the database user password and the environment variable separately - they don't automatically synchronize.
-- While both `postgresql://` and `postgres://` URI formats are valid, Zerops uses the `postgresql://` format. If your software requires `postgres://`, create a custom environment variable with this format.
-- Do not use SSL/TLS protocols for internal connections. Security is assured by the project's private network.
+- When changing a password, update both the database user and the environment variable, since they don't synchronize automatically.
+- Zerops uses the `postgresql://` URI scheme. If your software requires the shorter `postgres://`, create a custom environment variable with that format.
+- Internal connections on `5432`/`5433` don't use SSL/TLS. Security comes from the private network. For TLS-encrypted [connection pooling](#connection-pooling-with-pgbouncer), connect to pgBouncer on `6432`.
:::
-## Connect Remotely
+## Connect remotely
-Zerops offers two methods for connecting to your PostgreSQL database from outside the Zerops environment:
+There are two ways to reach PostgreSQL from outside Zerops: the VPN (recommended for development) and public IP access (for external applications).
-### Method 1: Connect via Zerops VPN
+### Connect via Zerops VPN
-You can securely connect to PostgreSQL from your local workstation via Zerops VPN:
+The VPN puts your workstation on the project's private network, so you connect exactly as an internal service would.
1. [Install & set up zCLI](/references/cli)
2. [Start the Zerops VPN](/references/networking/vpn#start-vpn)
-3. Use the connection details from Access Details in the PostgreSQL service detail in Zerops GUI
+3. Use the connection details from **Peek access details** in the service detail
4. When finished, [stop the Zerops VPN](/references/networking/vpn#stop-vpn)
:::warning Important notes
-* Do not use SSL/TLS protocols when connecting over VPN. Security is provided by the VPN tunnel.
-* If your connection over VPN doesn't work, try adding `.zerops` suffix to the service hostname (e.g., `database1.zerops`). For additional help, check the [VPN troubleshooting page](/references/networking/vpn#troubleshooting).
+* Connect to `5432`/`5433` without SSL/TLS over the VPN, since the tunnel already encrypts the traffic.
+* Environment variables are **not** available over the VPN; copy the access details from the GUI.
+* If a connection doesn't work, try the `.zerops` suffix on the hostname (e.g. `db.zerops`). See the [VPN troubleshooting page](/references/networking/vpn#troubleshooting).
:::
-### Method 2: Connect via Direct IP Access
-
-Direct IP Access uses [pgBouncer](https://www.pgbouncer.org/) for connection pooling and TLS termination.
+### Connect via public IP
-Internally, port `5432` is available without SSL (and port `5433` for reads in HA mode). Externally, connections are secured with TLS through pgBouncer (port `6432`) before being routed to your PostgreSQL service. The read replica port is not available for external connections.
+Public access always goes through pgBouncer on port `6432` over TLS (see [Connection ports and TLS](#connection-ports-and-tls)), which also pools your connections. The read-only replica port (`5433`) is **not** exposed publicly. Route reads through your application logic instead.
:::tip Trusting the TLS certificate
-The TLS certificate served on port `6432` is signed by the Zerops Certificate Authority. To verify it from outside Zerops, download and trust the [Zerops CA](/references/networking/zerops-ca) — e.g. `psql "... sslmode=verify-full sslrootcert=./zerops-ca.pem"`.
+The certificate on `6432` is signed by the Zerops Certificate Authority. To verify it from outside Zerops, download and trust the [Zerops CA](/references/networking/zerops-ca), for example `psql "... sslmode=verify-full sslrootcert=./zerops-ca.pem"`.
:::
-#### Enable external access
+To enable public access:
-1. Navigate to your PostgreSQL service in the Zerops GUI and choose the **Public Access through IP Addresses** section
-2. Choose either IPv6 (available by default) or IPv4 (requires the [unique IPv4](/references/networking/public-access#ipv4-configuration) add-on)
-3. Open one or more ports and point them to your PostgreSQL service (the system will direct them through pgBouncer)
- - Choose any port from 10-65435 (except 80 and 443)
- - Select destination service and internal port
- - Each public port can be mapped to any internal service port
- - Multiple public ports can point to the same internal port if needed
- - Port configurations can be set independently for IPv4 and IPv6
-4. Optionally enable firewall protection for additional security
-5. Click the **Publish X IP access change(s)** button to apply your settings
+1. Open your PostgreSQL service in the GUI and go to **Public Access through IP Addresses**
+2. Choose IPv6 (available by default) or IPv4 (requires the [unique IPv4](/references/networking/public-access#ipv4-configuration) add-on)
+3. Open one or more ports pointing to your service (routed through pgBouncer):
+ - Any port from 10–65435 (except 80 and 443)
+ - Each public port can map to any internal port; multiple public ports can share one internal port
+ - IPv4 and IPv6 can be configured independently
+4. Optionally enable firewall protection
+5. Click **Publish X IP access change(s)** to apply
-For database management tools and how to manage users and databases, see [Manage PostgreSQL Users and Databases](/postgresql/how-to/manage).
+## Database management tools
-----------------------------------------
+You can use any PostgreSQL-compatible tool to administer your database. For a zero-setup option, Zerops provides a ready-to-use recipe for [Adminer](https://www.adminer.org), a lightweight, full-featured web-based tool that supports PostgreSQL.
-# Postgresql > How To > Control
+### Install Adminer
+In the GUI, open your project, select **Import services**, and paste:
+```yaml title="zerops-import.yaml"
+services:
+ - hostname: adminer
+ type: php-apache@8.5
+ maxContainers: 1
+ enableSubdomainAccess: true
+ buildFromGit: https://github.com/zeropsio/recipe-adminer
+```
-----------------------------------------
+Then [start the VPN](/references/networking/vpn) and open `http://adminer` in your browser (try `http://adminer.zerops` if it doesn't resolve).
-# Postgresql > How To > Create
+:::caution
+Do not use `https` when reaching management tools over the VPN.
+:::
+### Desktop tools and psql
-## Create PostgreSQL using Zerops GUI
+Popular desktop clients (pgAdmin, DBeaver, DataGrip, or any PostgreSQL-compatible tool) work with Zerops over the VPN:
-First, set up a project in Zerops GUI. Then go to the project dashboard page and choose **Add new service** in the left menu in the **Services** block. Then add a new PostgreSQL service:
+1. [Start the Zerops VPN](/references/networking/vpn) to open an encrypted tunnel to your project
+2. Copy the [connection details](#connection-details) from the GUI (environment variables aren't available over the VPN)
+3. Connect without SSL/TLS on `5432`/`5433`, since the tunnel already encrypts the connection
-[Video: /vids/services/postgres.webm](/vids/services/postgres.webm)
+For the `psql` command-line client:
-### Choose PostgreSQL version
+```sh
+psql -h [hostname] -U [user] -d [database_name]
+```
-Following PostgreSQL versions are currently supported:
+`psql` has no password flag, so it prompts you. To pass the password non-interactively, use `PGPASSWORD`:
-### Set a hostname
+```sh
+PGPASSWORD=[password] psql -h [hostname] -U [user] -d [database_name]
+```
-Enter a unique service identifier like `postgresql`, `sql`, `db` etc.
+:::tip
+Try `[hostname].zerops` instead of `[hostname]` if you hit connection issues over the VPN.
+:::
-#### Limitations:
+To move data in and out with these tools, see [Export & import data](/postgresql/how-to/manage#export--import-data).
-- Duplicate services with the same name within the same project are not allowed
-- Maximum 25 characters
-- Must contain only lowercase ASCII letters (a-z) or numbers (0-9)
-:::caution
-The hostname is fixed after the service is created and cannot be changed later.
-:::
+----------------------------------------
-### Configure auto scaling
+# Postgresql > How To > Control
-Zerops automatically scales PostgreSQL services based on actual database usage. Configure the scaling parameters to match your database needs and control costs.
-**CPU Mode**: Choose between shared (cost-effective) or dedicated (consistent performance).
-**Resource Limits**: Set minimum and maximum resources for CPU, RAM, and disk to control costs and ensure performance.
+----------------------------------------
-**Deployment Mode**: Choose the reliability configuration for your PostgreSQL service:
+# Postgresql > How To > Create
-- **Highly Available**: Multiple containers with redundancy across different physical machines. Recommended for production environments.
-- **Single Container**: One container suitable for development and non-critical environments.
-:::warning
-Deployment mode cannot be changed after service creation.
-:::
+Create a PostgreSQL service in the [GUI](#create-in-the-gui), or describe it in YAML and [import](#import-with-yaml) it through the GUI or zCLI. Both paths configure the same things; the YAML route is repeatable and versionable.
-:::tip Learn More
-For detailed scaling configuration, deployment mode details, and troubleshooting, see:
-- [How Zerops scales PostgreSQL](/postgresql/how-to/scale) - Database-specific scaling guide
-- [Automatic Scaling and High Availability](/features/scaling) - Complete technical details
-:::
+## Create in the GUI
-## Create PostgreSQL using zCLI
+Go to your project dashboard and choose **Add new service** in the **Services** block:
-zCLI is the Zerops command-line tool. To create a new PostgreSQL service via the command line, follow these steps:
+[Video: /vids/services/postgres.webm](/vids/services/postgres.webm)
-1. [Install & setup zCLI](/references/cli)
-2. [Create a project description file](#create-a-project-description-file)
-3. Create a project and a PostgreSQL service
+The wizard asks for four things:
-### Create a project description file
+### Version
-Zerops uses a YAML format file to describe the project infrastructure.
+### Hostname
-#### Basic example
+A unique service identifier, like `db`, `sql`, or `postgresql`. Maximum 25 characters, lowercase ASCII letters (a-z) and numbers (0-9) only, unique within the project.
-Create a directory `my-project`. Create a `description.yaml` file inside the directory with the following content:
+### Deployment mode
-```yaml
-# Basic project data
-project:
- # project name
- name: my-project
-# array of project services
+**Highly Available** (a 3-node cluster, recommended for production) or **Single container** (lower cost, no redundancy). See [Deployment modes](/postgresql/overview#deployment-modes) for the comparison.
+
+### Scaling profile and resources
+
+Pick a [scaling profile](/postgresql/how-to/scale#scaling-profiles) matched to your workload (OLTP, OLAP, or write-heavy). The profile sets the autoscaling defaults and tunes the PostgreSQL configuration; on top of it you can set the CPU mode (shared or dedicated) and minimum/maximum limits for CPU, RAM, and disk. See [Scale & profiles](/postgresql/how-to/scale) for details.
+
+:::caution
+The **hostname** and **deployment mode** are fixed once the service is created. The scaling **profile** and resource limits can be changed at any time.
+:::
+
+## Import with YAML
+
+Zerops uses a YAML file to describe services declaratively. You can paste it in the GUI (**Import services** in the left menu of your project) or import it with the [zCLI](/references/cli).
+
+### Add a service to an existing project
+
+```yaml title="zerops-import.yaml"
services:
- - # service name
- hostname: postgresql1
- # service type and version number in postgresql@{version} format
- type: postgresql@12
- # mode of operation "HA"/"NON_HA"
- mode: NON_HA
+ - hostname: db
+ # postgresql:{single|ha}@{version}
+ type: postgresql:single@18
+ # optional: autoscaling profile, see Scale & profiles
+ profile: oltp-staging
```
-The YAML file describes your future project infrastructure. The project will contain one PostgreSQL service in the single container mode with default [auto scaling](/postgresql/how-to/scale) configuration. The hostname will be set to `postgresql1`.
+With zCLI:
-#### Full example
+```sh
+zcli project service-import zerops-import.yaml
+```
-Create a directory `my-project`. Create a `description.yaml` file inside the directory with the following content:
+The command is interactive: it lists your projects and lets you pick which one to import into. You can also pass the project ID directly with `-P`:
-```yaml
-# Basic project data
+```sh
+Usage:
+ zcli project service-import importYamlPath [flags]
+
+Flags:
+ -h, --help Help for the project service import command.
+ -P, --project-id string If you have access to more than one project, you must specify the project ID for which the
+ command will be executed.
+```
+
+### Create a new project
+
+A project import additionally contains the `project:` section:
+
+```yaml title="zerops-import.yaml"
project:
- # project name
name: my-project
- # optional: project description
+ # optional: project description and tags
description: A project with a PostgreSQL database
- # optional: project tags
tags:
- DEMO
- ZEROPS
-# array of project services
+
services:
- - # first service hostname
- hostname: postgresql1
- # service type and version number in postgresql@{version} format
- type: postgresql@12
- # mode of operation "HA"/"NON_HA"
- mode: HA
- # optional: vertical auto-scaling customization
+ - hostname: db
+ type: postgresql:ha@18
+ profile: oltp-production
+ # optional: override the profile's autoscaling defaults
verticalAutoscaling:
cpuMode: DEDICATED
minCpu: 2
@@ -26086,384 +26010,143 @@ services:
startCpuCoreCount: 3
minFreeRamGB: 0.5
minFreeRamPercent: 20
- - # second service hostname
- hostname: postgresql2
- # service type and version number in postgresql@{version} format
- type: postgresql@12
- # mode of operation "HA"/"non_HA"
- mode: NON_HA
+ - hostname: dbstaging
+ type: postgresql:single@18
+ profile: oltp-staging
```
-The YAML file describes your future project infrastructure. The project will contain two PostgreSQL services.
+```sh
+zcli project project-import zerops-import.yaml
+```
-The hostname of the first service will be set to `postgresql1`. The [high availability](/features/scaling#highly-available-ha-mode) mode will be chosen and the custom [auto scaling configuration](/postgresql/how-to/scale) will be set.
+The project name comes from the YAML, so the command doesn't take one. If you are a member of more than one organization (called *client* in the GUI), pass the ID with `--org-id`; you'll find it under the client name on the project dashboard.
-The hostname of the second service will be set to `postgresql2`. The [single container](/features/scaling#single-container-mode) mode will be chosen and the default [auto scaling configuration](/postgresql/how-to/scale) will be set.
+```sh
+Usage:
+ zcli project project-import importYamlPath [flags]
-#### Description of description.yaml parameters
+Flags:
+ -h, --help Help for the project import command.
+ --org-id string If you have access to more than one organization, you must specify the org ID for which the
+ project will be created.
+ --working-dir string Sets a custom working directory. The default working directory is the current directory. (default "./")
+```
-The `project:` section is required. Only one project can be defined.
+:::note
+The maximum size of an import YAML is 100 kB. A project import can mix PostgreSQL with [any other service type](/references/import-yaml/type-list).
+:::
+
+### Project parameters
+
+The `project:` section is required for a project import. Only one project can be defined.
-
Parameter
-
Description
-
Limitations
+
Parameter
+
Description
-
name
+
name
The name of the new project. Duplicates are allowed.
-
-
description
-
Optional. Description of the new project.
-
Maximum 255 characters.
+
description
+
Optional. Description of the new project. Maximum 255 characters.
-
tags
-
Optional. One or more string tags. Tags do not have a functional meaning, they only provide better orientation in projects.
-
+
tags
+
Optional. One or more string tags. Tags have no functional meaning, they only provide better orientation in projects.
-At least one service in the `services:` section is required. You can create a project with multiple services. The example above contains only PostgreSQL services but you can create a `description.yaml` with [different types] of services.
+### Service parameters
+
+At least one service in the `services:` section is required.
-
Parameter
+
Parameter
Description
+
hostname
- hostname
-
-
- The unique service identifier.
-
- The hostname of the new database will be set to the `hostname` value.
-
- Limitations:
-
-- duplicate services with the same name in the same project are
- forbidden
-
- - maximum 25 characters
-
-- must contain only lowercase ASCII letters (a-z) or numbers (0-9)
-
-
-
-
- type
-
-
- Specifies the service type and version.
-
- See what [PostgreSQL service types](/references/import-yaml/type-list#database-services) are currently supported.
-
-
-
-
- mode
-
-
- Defines the operation mode of the PostgreSQL service.
-
- HA
-
- Creates a PostgreSQL cluster with 3 database containers and 2 free
- database proxies. This mode is suited for production.
-
- Zerops always keeps the 3 database containers on different physical
- machines. All your data is stored redundantly in 3 identical copies. In
- case of a failure of a container or the underlying physical machine,
- Zerops automatically disconnects the failed container from the cluster,
- creates a new container and syncs all data from the remaining 2 copies.
- Finally, the broken container is automatically deleted.
-
- In HA mode, a dedicated read replica port (5433) is available, allowing you to route read queries to replicas for better performance. See [Connection Parameters](/postgresql/how-to/connect#connection-parameters) for details.
-
- NON_HA
-
- Zerops will create a PostgreSQL database installed in a single
- container. Useful for non-essential data or dev environments.
-
- Your data is stored only in a single container. If the container or the
- the underlying physical machine fails, your data since the last backup are
- lost. Zerops doesn't provide any automatic repairs of a single node
- PostgreSQL services.
-
-
-
-
- verticalAutoscaling
-
-
- Optional. Defines [custom vertical auto-scaling parameters](/postgresql/how-to/scale#configure-scaling).
-
- All verticalAutoscaling attributes are optional. Not specified
- attributes will be set to their default values.
+ The unique service identifier; becomes the database hostname on the project's private network. Maximum 25 characters, lowercase ASCII letters (a-z) and numbers (0-9) only, unique within the project. Fixed after creation.
+
type
- - cpuMode
-
-
- Optional. Accepts `SHARED`, `DEDICATED` values. Default is `SHARED`
+ The service type, deployment mode, and version in postgresql:{single|ha}@{version} format. The :single/:ha part selects the [deployment mode](/postgresql/overview#deployment-modes) and is fixed after creation; the standalone mode field is deprecated. See the [list of supported types](/references/import-yaml/type-list#database-services).
+
profile
- - minCpu/maxCpu
-
-
- Optional. Set the minCpu or maxCpu in CPU cores (integer).
+ Optional. The [autoscaling profile](/postgresql/how-to/scale#scaling-profiles): sets the autoscaling envelope and tunes the PostgreSQL configuration for your workload. Defaults to oltp-staging (single) or oltp-production (HA). Can be changed at any time in the GUI.
+
profileOverrides
- - minRam/maxRam
-
-
- Optional. Set the minRam or maxRam in GB (float).
+ Optional. Only valid with profile: custom; overrides individual PostgreSQL configuration values. See [Custom profile](/postgresql/how-to/scale#custom-profile) for the overridable keys and their units.
+
verticalAutoscaling
- - minDisk/maxDisk
-
-
- Optional. Set the minDisk or maxDisk in GB (float).
+ Optional. Overrides the profile's autoscaling defaults. All attributes are optional: cpuMode (SHARED/DEDICATED), minCpu/maxCpu (cores), minRam/maxRam (GB), minDisk/maxDisk (GB), startCpuCoreCount, minFreeRamGB, minFreeRamPercent. See [Configure scaling](/postgresql/how-to/scale#configure-scaling).
-:::caution
-The PostgreSQL service **hostname** and **mode** are fixed after the service is created. They can't be changed later.
-:::
-
-### Create a project based on the description.yaml
-
-When you have your `description.yaml` ready, use the `zcli project project-import` command to create a new project and the service infrastructure.
-
-```sh
-Usage:
- zcli project project-import importYamlPath [flags]
-
-Flags:
- -h, --help Help for the project import command.
- --org-id string If you have access to more than one organization, you must specify the org ID for which the
- project will be created.
- --working-dir string Sets a custom working directory. The default working directory is the current directory. (default "./")
-```
-
-Zerops will create a project and one or more services based on the `description.yaml` content.
-
-The maximum size of the `description.yaml` file is 100 kB.
-
-You don't specify the project name in the `zcli project project-import` command, because the project name is defined in the `description.yaml`.
-
-If you have access to more than one client, you must specify the client ID for which the project is to be created. The `clientID` is located in the Zerops GUI under the client name on the project dashboard page.
-
-### Add PostgreSQL service to an existing project
-
-#### Example
-
-Create a directory `my-project` if it doesn't exist. Create an `import.yaml` file inside the `my-project` directory with following content:
-
-```bash
-# array of project services
-services:
- -
- # service name
- hostname: postgresql1
- # service type and version number in postgresql@{version} format
- type: postgresql@12
- # mode of operation "HA"/"NON_HA"
- mode: NON_HA
-```
-
-The YAML file describes the list of one or more services that you want to add to your existing project. In the example above, one PostgreSQL service in the [single container](/features/scaling#single-container-mode) with default [auto scaling](/postgresql/how-to/scale) configuration will be added to your project. The hostname of the new service will be set to `postgresql1`.
-
-The content of the `services:` section of `import.yaml` is identical to the [project description file](#create-a-project-description-file). The `import.yaml` never contains the `project:` section because the project already exists.
-
-When your `import.yaml` is ready, use the `zcli project service-import` command to add one or more services to your existing Zerops project.
-
-```sh
-Usage:
- zcli project service-import importYamlPath [flags]
-
-Flags:
- -h, --help Help for the project service import command.
- -P, --project-id string If you have access to more than one project, you must specify the project ID for which the
- command will be executed.
-```
-
-zCLI commands are interactive, when you press enter after `zcli project service-import importYamlPath`, you will be given a list of your projects to choose from.
-
-The maximum size of the `import.yaml` file is 100 kB.
-
-----------------------------------------
-
-# Postgresql > How To > Export Import Data
-
-
-## Use Adminer or phpMyAdmin to export or import data
-* [Adminer ↗](https://www.adminer.org) - an open source full-featured database management tool written in PHP
-* [phpMyAdmin ↗](https://www.phpmyadmin.net) - a free software tool written in PHP, intended to handle the administration of PostgreSQL over the Web
-
-1. [Install the tools to Zerops](/postgresql/how-to/manage#installing-management-tools)
-2. Use their standard export or import functions
-
-## Use a database management tool on your workstation to export or import data
-
-Do you already use a database management tool that supports PostgreSQL on your workstation? Connect it securely to PostgreSQL from your local workspace via Zerops VPN.
-
-Zerops VPN client is included into zCLI, the Zerops command-line tool. To start the VPN connection, read [how to connect to PostgreSQL remotely](/postgresql/how-to/connect#connect-remotely).
-:::caution
-Do not use SSL/TLS protocols when connecting to PostgreSQL over VPN. Zerops PostgreSQL is not configured to support these protocols. The security is assured by the VPN.
-:::
-
-Once the connection to PostgreSQL is established, use the standard export or import functions of your favourite management tool.
-
-## Use psql CLI to export or import data
-
-If you are using the [psql ↗](https://www.postgresql.org/docs/current/app-psql.html) command-line client to manage your PostgreSQL on your local workspace, you can connect it securely to PostgreSQL via Zerops VPN.
-
-Zerops VPN client is included into zCLI, the Zerops command-line tool. To start the VPN connection, read [how to connect to PostgreSQL remotely](/postgresql/how-to/connect#connect-remotely).
-
-Once the VPN session is established, you have the secured connection to the project's private network in Zerops. You can access all project services locally by using their hostname. The only difference is that no [environment variables](/postgresql/how-to/connect#method-2-environment-variables-recommended) are available when connected through VPN. To connect to PostgreSQL in Zerops you have to copy the [access details](/postgresql/how-to/connect#connection-details) manually from Zerops GUI.
-
-Use [psql ↗](https://www.postgresql.org/docs/current/app-psql.html) command to connect to PostgreSQL in Zerops:
-
-```sh
-psql -h [hostname] -U [user] -p [password] -d [database_name]
-```
-
-:::caution
-Do not use SSL/TLS protocols when connecting to PostgreSQL over VPN. Zerops PostgreSQL is not configured to support these protocols. The security is assured by the VPN.
-:::
-
-To export your database data and structure, use the [pg_dump ↗](https://www.postgresql.org/docs/current/backup-dump.html) command.
-
-```sh
-pg_dump [database_name] > dumpfilename.sql
-```
-
-To import your database data and structure, use the `mysql` command.
-
-```sh
-mysql [database_name] < dumpfilename.sql
-```
-
----------------------------------------
# Postgresql > How To > Manage
-This guide covers how to manage your PostgreSQL databases in Zerops, including default setup, database management tools, plugins, and best practices.
+This guide covers the default database setup, installing extensions, backups, and moving data in and out. For connecting and management tools, see [Connect](/postgresql/how-to/connect).
-## Default Database and User
-
-Zerops creates a default database and user automatically when a new PostgreSQL service is [created](/postgresql/how-to/create).
-
-### Database
+## Default database and user
-- **Name**: Identical to the service hostname
-- **Encoding**: `utf8mb4`
+Zerops creates a default database and user automatically when a PostgreSQL service is [created](/postgresql/how-to/create).
-### DB User
+- **Database name**: `db`
+- **User**: `db`
+- **Password**: randomly generated
+- **Encoding**: `UTF8` (locale `C.UTF-8`)
-- **Username**: Identical to the service hostname
-- **Password**: Generated randomly
+Both names are fixed; they don't follow the service hostname. Besides the `db` user, Zerops also creates a `postgres` superuser for you (see [Extensions](#extensions)).
:::info
-For connection methods and environment variables, see the [Connect to PostgreSQL in Zerops](/postgresql/how-to/connect) page.
-:::
-
-:::caution Important notes
-- When changing passwords, update both the database user password and the environment variable separately - they don't automatically synchronize.
-- While both `postgresql://` and `postgres://` URI formats are valid, Zerops uses the `postgresql://` format. If your software requires `postgres://`, create a custom environment variable with this format.
-- Do not use SSL/TLS protocols for internal connections. Security is assured by the project's private network.
-:::
-
-## Database Management Tools
-
-You can use any PostgreSQL management tool of your choice to administer your databases in Zerops. For convenience, Zerops provides ready-to-use recipes for two popular web-based database management tools:
-
-* [Adminer](https://www.adminer.org) - a lightweight database management tool by Jakub Vrána
-* [phpMyAdmin](https://www.phpmyadmin.net) - a popular free database administration tool that works with both MySQL and PostgreSQL databases
-
-### Installing Management Tools
-
-You can install these tools with a simple one-click import in Zerops:
-
-1. In Zerops GUI, open your project and select **Import services** from the left menu
-2. Copy and paste one of the following YAML configurations:
-
-### Accessing Management Tools
-
-After installation, you can access these tools via VPN:
-
-1. [Start the Zerops VPN](/references/networking/vpn)
-2. Type `http://adminer` or `http://phpmyadmin` in your browser
-
-:::tip
-Try `http://adminer.zerops` or `http://phpmyadmin.zerops` if you encounter any connection issues.
-:::
-
-:::caution
-Do not use https when connecting to management tools via VPN.
+For connection methods, ports, and environment variables, see [Connect to PostgreSQL](/postgresql/how-to/connect).
:::
-## Database Tools on Your Workstation
-
-You can use various database management tools from your local workstation to connect to your PostgreSQL database in Zerops:
-
-1. **Establish a secure tunnel** using the [Zerops VPN](/references/networking/vpn) to create an encrypted connection to your Zerops project
-2. **Obtain the [connection details](/postgresql/how-to/connect#connection-details)** from Zerops GUI
- - Environment variables are not available through VPN connections
-3. Connect with your **preferred database tool**
- - Do not use SSL/TLS (security is provided by the VPN)
- - **Desktop Database Tools** - popular GUI tools like pgAdmin, DBeaver, DataGrip, or any other PostgreSQL-compatible client will work with Zerops
- - **Command Line with psql** - connect using the standard PostgreSQL command-line client with the credential obtained above:
- ```sh
- psql -h [hostname] -U [user] -d [database_name]
- ```
-
-:::tip
- Try `{hostname}.zerops` instead of just `{hostname}` if you encounter any connection issues.
-:::
+## Extensions
-## How to install and manage PostgreSQL plugins
+### List available extensions
-### Viewing available plugins
-You can list all available PostgreSQL plugins by running the following query *(superuser privileges not required)*:
+Any user can list the extensions available to install (superuser not required):
```sql
SELECT * FROM pg_available_extensions ORDER BY name;
```
-### Installing plugins (requires superuser)
+### Install an extension (requires superuser)
-1. **Connect with superuser credentials**:
- - Use the `superUser` (user `postgres`) and `superUserPassword` environment variables from your PostgreSQL service
+1. **Connect as the superuser.** Use the `superUser` (user `postgres`) and `superUserPassword` environment variables from your service.
+2. **Switch to your service database.** As the superuser you start in the `postgres` database, not your service database.
+3. **Create the extensions** you need:
-2. **Switch to your service database**:
- When logging in as the superuser, you're initially in the `postgres` database, not your service database.
-
-3. **Install required extensions**:
```sql
CREATE EXTENSION pg_stat_statements;
CREATE EXTENSION vector;
@@ -26471,16 +26154,14 @@ SELECT * FROM pg_available_extensions ORDER BY name;
```
:::warning
-Currently, it is not possible to add new plugins that are not already listed in `pg_available_extensions`.
+You can only install extensions already listed in `pg_available_extensions`; new extensions cannot be added.
:::
-When working with text search functionality, you'll need to reference the correct `stop`, `dict`, and `affix` files when creating dictionaries in your database. These files are essential for proper text search configuration.
-
-Zerops PostgreSQL includes the following dictionary files:
+When configuring text search dictionaries, reference the correct `stop`, `dict`, and `affix` files. Zerops PostgreSQL ships the following:
#### Available dictionary files
-**Stop word files** - used to remove common words that don't add significant meaning:
+**Stop word files**, used to remove common words that don't add significant meaning:
```
czech.stop
danish.stop
@@ -26501,7 +26182,7 @@ spanish.stop
swedish.stop
turkish.stop
```
-**Dictionary and affix files** - used for stemming and word normalization:
+**Dictionary and affix files**, used for stemming and word normalization:
```
cs_CZ.affix
cs_CZ.dict
@@ -26517,70 +26198,315 @@ sk_SK.dict
unaccent.rules
```
-For more information on text search dictionaries, refer to the [PostgreSQL documentation](https://www.postgresql.org/docs/16/textsearch-dictionaries.html).
+See the [PostgreSQL text search documentation](https://www.postgresql.org/docs/current/textsearch-dictionaries.html) for more.
-----------------------------------------
+## Backups
-# Postgresql > How To > Scale
+Zerops automatically backs up PostgreSQL services with full encryption. For scheduling, retention, tagging, storage quotas, and manual backups (and the CLI tools), see [Zerops Backups](/features/backup). This section covers the PostgreSQL specifics.
+### Backup format
-Zerops automatically scales your PostgreSQL service based on actual database usage. When your database needs more power, resources increase. When demand drops, resources scale down to reduce costs.
+- **Format**: `.zip` containing per-schema `.dump` files
+- **Tooling**: `pg_dump`, custom format (`-Fc`), files named `schemaName.dump`
+- **Storage**: encrypted, in isolated object storage
-:::tip Read More
-For complete scaling details across all services, see [Automatic Scaling and High Availability](/features/scaling).
-:::
+### Restore a backup
-## How PostgreSQL scaling works
+1. **Download** the backup `.zip` from the GUI
+2. **Extract** it to get the per-schema `.dump` files
+3. **Prepare** the target (clean existing data, or use a fresh instance)
+4. **Restore** each schema with `pg_restore`. The dumps are custom format (`-Fc`), so plain `psql` can't read them:
-PostgreSQL services use **vertical scaling** to adjust CPU, RAM, and disk resources within containers based on usage patterns. Unlike runtime services, PostgreSQL does not use horizontal scaling (adding/removing containers). Instead, PostgreSQL services use deployment modes for high availability.
+ ```sh
+ pg_restore -d [database_name] schemaName.dump
+ ```
-## Configure scaling
+ See the [official PostgreSQL backup documentation](https://www.postgresql.org/docs/current/backup-dump.html) for restore options. To use a web UI like [Adminer](/postgresql/how-to/connect#database-management-tools), first convert a dump to plain SQL with `pg_restore -f schema.sql schemaName.dump` and import that SQL file.
-You can configure scaling settings:
+### High availability
-- **During service creation** - Set initial scaling parameters when [creating](/postgresql/how-to/create) your PostgreSQL service
-- **During import** - Define scaling configuration in your YAML import file using `verticalAutoscaling` parameters
-- **After service creation** - Navigate to your PostgreSQL service and select **Automatic scaling configuration** to modify settings
+For HA services:
+- Backups run on a randomly selected healthy node
+- Other nodes stay operational during the backup
+- Manual backups typically run on the primary node
-### Basic settings
+### Best practices
-**CPU Mode**: Choose between shared (cost-effective, variable performance) or dedicated (consistent performance, higher cost). You can change CPU mode once per hour. See [pricing](https://zerops.io/#pricing) for costs.
+- Take a manual backup with a protected tag before migrations or major schema changes
+- Test your restore process periodically in a non-production environment
+- Monitor backup storage usage in the Project Overview
+- Use descriptive tags like `pre-migration-v2`
+- Mind schema dependency order when restoring
-**Resource limits**: Configure minimum and maximum resources for your PostgreSQL service:
+## Export & import data
-- **Lower the maximum** to control costs and prevent over-scaling
-- **Raise the minimum** when you need guaranteed baseline performance
-- **Set minimum = maximum** to disable automatic scaling for that specific resource
+To move data in and out, first connect to your database, either over the [VPN](/postgresql/how-to/connect#connect-via-zerops-vpn) for a local tool or via [Adminer](/postgresql/how-to/connect#database-management-tools) in the browser.
-**Deployment mode**: Choose the reliability configuration for your PostgreSQL service:
+### With Adminer or a desktop tool
-- **Single Container**: One container with vertical scaling only. Suitable for development environments.
-- **Highly Available**: Multiple containers with built-in redundancy. Recommended for production environments.
+Once connected, use the tool's built-in export/import functions. See [Database management tools](/postgresql/how-to/connect#database-management-tools) and [Desktop tools and psql](/postgresql/how-to/connect#desktop-tools-and-psql).
-:::warning
-Deployment mode cannot be changed after service creation.
-:::
+### With the command line
-:::tip HA Mode
-In HA mode, a dedicated read replica port (`5433`) is available for routing read queries to replicas. This improves performance by distributing load across all database containers. See [Connection Parameters](/postgresql/how-to/connect#connection-parameters) for details.
-:::
+Export data and schema with [pg_dump](https://www.postgresql.org/docs/current/app-pgdump.html):
-When a container fails in HA mode, Zerops automatically replaces it with a new container on a different physical machine and synchronizes data from healthy copies.
+```sh
+pg_dump [database_name] > dump.sql
+```
-### Advanced settings
+Import a plain SQL dump with `psql`:
-**Start CPU cores**: Determines how many CPU cores are allocated during database startup. Increase this value if your PostgreSQL service starts slowly or requires more processing power during initialization.
+```sh
+psql [database_name] < dump.sql
+```
-**RAM thresholds**: Help prevent out-of-memory crashes by maintaining buffer space:
+For a custom-format dump (`pg_dump -Fc`, the format Zerops backups use), use [pg_restore](https://www.postgresql.org/docs/current/app-pgrestore.html):
-- **Absolute (GB)**: Maintains this amount of free RAM at all times
-- **Percentage**: Keeps this percentage of total RAM free
+```sh
+pg_restore -d [database_name] dump.dump
+```
-Consider increasing these values if your database experiences memory-related issues.
+See [Desktop tools and psql](/postgresql/how-to/connect#desktop-tools-and-psql) for how to reach the database and pass the password with `PGPASSWORD`.
-:::info Read More
-For detailed technical parameters and scaling behavior, see [Automatic Scaling and High Availability](/features/scaling#resource-scaling-behavior).
-:::
+
+----------------------------------------
+
+# Postgresql > How To > Scale
+
+
+Zerops automatically scales your PostgreSQL service based on actual database usage. When your database needs more power, resources increase. When demand drops, resources scale down to reduce costs.
+
+:::tip Read More
+For complete scaling details across all services, see [Automatic Scaling and High Availability](/features/scaling).
+:::
+
+## How PostgreSQL scaling works
+
+PostgreSQL services use **vertical scaling** to adjust CPU, RAM, and disk resources within containers based on usage patterns. Unlike runtime services, PostgreSQL does not use horizontal scaling (adding/removing containers). Instead, PostgreSQL services use deployment modes for high availability.
+
+:::danger Scaling can briefly interrupt the service
+When scaling changes the service's resources, Zerops regenerates the PostgreSQL configuration and applies it with an automatic **reload**. If the new values require it, the service is **restarted** instead: rolling through the cluster in HA mode, a short outage in single mode.
+
+A restart is only needed when the granted RAM crosses a memory step: `256 MiB`, `512 MiB`, `1 GiB`, `2 GiB`, `4 GiB`, then multiples of `8 GiB`. Scaling within a step reloads only; to rule out restarts entirely, keep `minRam` and `maxRam` within one step.
+:::
+
+## Scaling profiles
+
+A **scaling profile** is the starting point for a PostgreSQL service. Each profile sets two things at once:
+
+1. **The autoscaling envelope**: the default minimum/maximum CPU, RAM, and disk, plus the free-resource headroom that controls how eagerly the autoscaler reacts.
+2. **The PostgreSQL configuration**: memory, WAL, planner, autovacuum, and replication settings tuned for a specific workload shape.
+
+A profile name combines a **workload type** with a **tier**, e.g. `oltp-production`.
+
+### Workload types
+
+
+
+
+
Type
+
Tuned for
+
Notes
+
+
+
+
+
OLTP
+
Transactional workloads like web apps, APIs, order processing, and auth. Short transactions and point lookups.
+
The default and most general-purpose type. Synchronous replication in HA.
+
+
+
OLAP
+
Analytical workloads like reporting, dashboards, and large aggregations.
+
Larger sort/hash memory, aggressive query parallelism, higher-resolution planner statistics. Asynchronous replication in HA (synchronous commits would throttle bulk loads).
+
+
+
WriteHeavy
+
High-volume ingestion like IoT telemetry, event logging, and metrics.
+
Commit batching, WAL compression, and aggressive autovacuum to keep up with write volume. Synchronous replication in HA.
+
+
+
+
+### Available profiles
+
+The tier part of the name sets the size of the autoscaling envelope (and, in HA, the replication topology). Which profiles you can pick depends on the deployment mode:
+
+
+
+
+
Profile
+
Mode
+
Use it for
+
+
+
+
+
oltp-hobby
+
Single
+
Side projects, prototypes, learning. Runs hot with minimal headroom to keep costs low.
+
+
+
oltp-staging
+
Single / HA
+
Staging, internal tools, early-stage apps. Moderate headroom. Default for single.
+
+
+
oltp-production
+
Single / HA
+
Business-critical transactional workloads. Generous headroom for traffic spikes. Default for HA.
+
+
+
oltp-enterprise
+
HA only
+
High-throughput OLTP at scale. Highest connection limits and the most aggressive headroom.
+
+
+
olap-production
+
Single / HA
+
Analytical / warehouse workloads.
+
+
+
writeheavy-production
+
Single / HA
+
Ingestion pipelines and write-heavy workloads.
+
+
+
custom
+
Single / HA
+
OLTP-based profile that lets you override individual PostgreSQL settings. See [Custom profile](#custom-profile).
+
+
+
+
+:::note
+In **HA** mode the OLTP and WriteHeavy profiles run with two synchronous standbys, so an acknowledged `COMMIT` survives a node failure. OLAP runs with asynchronous standbys, trading a small potential window of recent writes for ingest speed.
+:::
+
+### Setting a profile
+
+Set the profile when you create the service in the GUI, or with the `profile` field in your [import YAML](/postgresql/how-to/create#import-with-yaml):
+
+```yaml title="zerops-import.yaml"
+services:
+ - hostname: db
+ type: postgresql:ha@18
+ profile: oltp-production
+```
+
+If you don't set one, the default is used (`oltp-staging` for single, `oltp-production` for HA). **The profile can be changed at any time** in the GUI.
+
+### Overriding the autoscaling envelope
+
+The resource limits a profile sets are defaults. You can override any of them (CPU mode, min/max CPU/RAM/disk, and the free-resource thresholds) **without switching to the custom profile**, using the [`verticalAutoscaling`](/postgresql/how-to/create#service-parameters) block in your import YAML or the **Automatic scaling configuration** in the GUI. These overrides apply on top of any profile.
+
+### Custom profile
+
+The `custom` profile uses the **OLTP** tuning as its base and additionally lets you override individual PostgreSQL configuration values through `profileOverrides`:
+
+```yaml title="zerops-import.yaml"
+services:
+ - hostname: db
+ type: postgresql:single@18
+ profile: custom
+ profileOverrides:
+ random_page_cost: 1.1
+ default_statistics_target: 200
+ autovacuum_max_workers: 5
+ work_mem: 67108864 # bytes (64 MiB)
+ autovacuum_naptime: 120000000000 # nanoseconds (2 minutes)
+```
+
+Settings that Zerops derives from the container's resources or that are required for the managed cluster to operate (`shared_buffers`, `max_connections`, JIT on/off, the pooler limits, and the replication/Patroni settings) are managed automatically and **cannot** be overridden.
+
+Refer to the [PostgreSQL configuration documentation](https://www.postgresql.org/docs/current/runtime-config.html) for the meaning and valid range of each parameter, but **mind the units**: override values are plain numbers, with memory/disk sizes given in **bytes** and durations in **nanoseconds**. This differs from PostgreSQL's own conventions (kB, milliseconds, seconds). The table below shows which unit each key takes.
+
+#### Overridable configuration keys
+
+
+
+
+
Key
+
Type / unit
+
Description
+
+
+
+
work_mem
bytes
Memory per sort/hash operation before spilling to disk
+
hash_mem_multiplier
integer
Multiplier applied to work_mem for hash-based operations
+
maintenance_work_mem
bytes
Memory for maintenance operations (VACUUM, CREATE INDEX, REINDEX)
+
autovacuum_work_mem
bytes
Memory used by each autovacuum worker
+
temp_buffers
bytes
Per-session memory for accessing temporary tables
+
temp_file_limit
bytes
Maximum total disk space a session may use for temporary files
+
effective_cache_size
bytes
Planner's assumption of total cache available (shared_buffers + OS cache)
+
effective_io_concurrency
integer
Estimated number of concurrent disk I/O operations the storage can handle
+
maintenance_io_concurrency
integer
Concurrent disk I/O operations for maintenance (VACUUM, prefetch)
+
random_page_cost
number
Planner's estimated cost of a non-sequential page fetch
+
default_statistics_target
integer
Default number of samples used by ANALYZE for column statistics
+
jit_above_cost
number
Query plan cost above which JIT compilation is considered
+
max_worker_processes
integer
Maximum number of background worker processes
+
max_parallel_workers
integer
Maximum parallel workers that can be active at one time cluster-wide
+
max_parallel_workers_per_gather
integer
Maximum parallel workers a single Gather node can start
+
max_parallel_maintenance_workers
integer
Maximum parallel workers for maintenance (CREATE INDEX, VACUUM)
+
wal_compression
string
Compression algorithm for full-page WAL images (off/pglz/lz4/zstd)
+
wal_buffers
bytes
Shared memory used to buffer WAL data not yet written to disk
+
wal_writer_delay
nanoseconds
Interval at which the WAL writer flushes WAL to disk
+
wal_writer_flush_after
bytes
WAL bytes written before the WAL writer triggers a flush
+
commit_delay
nanoseconds
Artificial delay before a WAL flush during commit to batch concurrent commits
+
commit_siblings
integer
Minimum number of concurrent open transactions required for commit_delay to apply
+
max_wal_size
bytes
Soft upper limit on WAL size that triggers a checkpoint
+
min_wal_size
bytes
Minimum WAL size kept for future reuse before recycling segments
+
autovacuum_max_workers
integer
Maximum autovacuum worker processes running concurrently
+
autovacuum_naptime
nanoseconds
Delay between autovacuum runs on any given database
+
autovacuum_vacuum_scale_factor
number
Fraction of table size added to the autovacuum threshold
+
autovacuum_analyze_scale_factor
number
Fraction of table size added to the auto-analyze threshold
+
autovacuum_vacuum_cost_delay
nanoseconds
Cost-based delay inserted by autovacuum between I/O operations
+
autovacuum_vacuum_cost_limit
integer
Accumulated vacuum cost at which an autovacuum worker sleeps
+
vacuum_cost_delay
nanoseconds
Cost-based delay inserted by manual VACUUM between I/O operations (0 disables)
+
idle_in_transaction_session_timeout
nanoseconds
Terminate sessions that stay idle in a transaction longer than this
+
+
+
+## Configure scaling
+
+You can configure scaling settings:
+
+- **During service creation** - Set initial scaling parameters when [creating](/postgresql/how-to/create) your PostgreSQL service
+- **During import** - Define scaling configuration in your YAML import file using `verticalAutoscaling` parameters
+- **After service creation** - Navigate to your PostgreSQL service and select **Automatic scaling configuration** to modify settings
+
+### Basic settings
+
+**CPU Mode**: Choose between shared (cost-effective, variable performance) or dedicated (consistent performance, higher cost). You can change CPU mode once per hour. See [pricing](https://zerops.io/#pricing) for costs.
+
+**Resource limits**: Configure minimum and maximum resources for your PostgreSQL service:
+
+- **Lower the maximum** to control costs and prevent over-scaling
+- **Raise the minimum** when you need guaranteed baseline performance
+- **Set minimum = maximum** to disable automatic scaling for that specific resource
+
+**Deployment mode** (single container vs. highly available) is chosen when the service is created and cannot be changed later. See [Deployment modes](/postgresql/overview#deployment-modes).
+
+### High availability
+
+In HA mode, Zerops runs a 3-node cluster across separate physical machines, fronted by two database proxy containers (free of charge) that route traffic. When a container fails, Zerops automatically replaces it on a different machine and synchronizes data from the healthy copies.
+
+A dedicated **read-replica port (`5433`)** lets you route read-only queries to the replicas, taking load off the primary and improving throughput. See [Connection parameters](/postgresql/how-to/connect#connection-parameters) and [Connection ports and TLS](/postgresql/how-to/connect#connection-ports-and-tls).
+
+### Advanced settings
+
+**Start CPU cores**: Determines how many CPU cores are allocated during database startup. Increase this value if your PostgreSQL service starts slowly or requires more processing power during initialization.
+
+**RAM thresholds**: Help prevent out-of-memory crashes by maintaining buffer space:
+
+- **Absolute (GB)**: Maintains this amount of free RAM at all times
+- **Percentage**: Keeps this percentage of total RAM free
+
+Consider increasing these values if your database experiences memory-related issues.
+
+:::info Read More
+For detailed technical parameters and scaling behavior, see [Automatic Scaling and High Availability](/features/scaling#resource-scaling-behavior).
+:::
## Monitor usage
@@ -26591,7 +26517,7 @@ Navigate to your PostgreSQL service and select **Service containers & Overview**
## Technical details
-Zerops monitors database usage and automatically adjusts resources based on predefined thresholds and timing parameters. The scaling behavior follows the same principles as other services in the platform.
+Zerops monitors database usage and automatically adjusts resources based on predefined thresholds and timing parameters. The scaling behavior follows the same principles as other services on the platform.
For complete technical specifications including:
- Resource monitoring intervals and thresholds
@@ -26629,39 +26555,82 @@ See [Resource Scaling Behavior](/features/scaling#resource-scaling-behavior) in
# Postgresql > Overview
-[PostgreSQL ↗](https://www.postgresql.org/) is a powerful, open source object-relational database system with over 35 years of active development that has earned it a strong reputation for reliability, feature robustness, and performance.
+[PostgreSQL ↗](https://www.postgresql.org/) is a powerful, open-source object-relational database with a 35-year track record for reliability, robustness, and performance. On Zerops it runs as a **fully managed service**: automated encrypted backups, vertical autoscaling, optional high availability with automatic failover, and a configuration tuned to your workload, with no server to administer.
-## Feature Highlights
+## Quick start
-- [Create PostgreSQL service](/postgresql/how-to/create) — Start with creating a PostgreSQL service using GUI or zCLI.
-- [Import config file](/postgresql/how-to/create#full-example) — Use an example config file to import your own app.
+Add a PostgreSQL service to your project with a `zerops-import.yaml`:
-### Connect to PostgreSQL service
+```yaml title="zerops-import.yaml"
+services:
+ - hostname: db
+ # type is postgresql:{mode}@{version}
+ type: postgresql:single@18
+ # workload-tuned autoscaling profile
+ profile: oltp-staging
+```
-- [Manage users and databases](/postgresql/how-to/manage)
-- [Connect from the same project](/postgresql/how-to/connect#connect-from-services-in-the-same-project)
-- [Connect remotely](/postgresql/how-to/connect#connect-remotely)
+Import it with the zCLI:
-### Others
+```bash
+zcli project service-import zerops-import.yaml
+```
-- [Scale postgresql service](/postgresql/how-to/scale)
-- [Export and import data](/postgresql/how-to/export-import-data)
+The moment the container boots you have a working database. See [Create & import](/postgresql/how-to/create) for the GUI flow and the full configuration reference.
-## When in doubt, reach out
+## What you get out of the box
-Don't know how to start or got stuck during the process? You might not be the first one, visit the FAQ section to find out.
+- **A default database and user**, both named `db` (encoding `UTF8`, locale `C.UTF-8`), with a randomly generated password.
+- **Connection details and environment variables**, including hostname, ports, and ready-made connection strings. See [Connect](/postgresql/how-to/connect).
+- **Workload-tuned configuration** via the [scaling profile](/postgresql/how-to/scale#scaling-profiles) you choose (OLTP, OLAP, or write-heavy).
+- **Automated, encrypted backups** stored in isolated object storage. See [Manage & data](/postgresql/how-to/manage#backups).
+- **Vertical autoscaling** that grows and shrinks CPU, RAM, and disk with real usage.
-In case you haven't found an answer (and also if you have), we and our community are looking forward to hearing from you on Discord.
+### Supported versions
-Have you build something that others might find useful? Don't hesitate to share your knowledge!
+## Deployment modes
-- [FAQ](/postgresql/faq) — Most common questions in one place.
-- [Discord](https://discord.com/invite/WDvCZ54) — Join our core team and Zerops community on Discord. Ask questions and share your tips with other members.
+Choose the reliability model when you create the service. It is set through the service `type` and is **fixed for the life of the service**.
-## Popular Guides
+
+
+
+
Mode
+
type
+
What it is
+
+
+
+
+
Highly Available
+
postgresql:ha@N
+
A 3-node cluster spread across separate physical machines, with data stored redundantly, automatic failover, and a read-replica port. Recommended for production.
+
+
+
Single container
+
postgresql:single@N
+
One container, so lower cost but no redundancy. Data since the last backup is lost if it fails. Good for development and non-critical workloads.
+
+
+
-- [zCLI](/references/cli) — Get even more out of Zerops with the zCLI command line tool.
-- [Zerops VPN](/references/networking/vpn) — Connect to your services easily with Zerops VPN.
+:::tip
+In HA mode, route read-only queries to the replica port (`5433`) to take load off the primary. See [Scale & profiles](/postgresql/how-to/scale#high-availability) and [Connection ports and TLS](/postgresql/how-to/connect#connection-ports-and-tls).
+:::
+
+## Next steps
+
+- [Create & import](/postgresql/how-to/create) — Create a service in the GUI or with zerops-import.yaml.
+- [Connect](/postgresql/how-to/connect) — Ports, TLS, pooling, env vars, VPN, and management tools.
+- [Scale & profiles](/postgresql/how-to/scale) — Autoscaling, workload profiles, and high availability.
+- [Manage & data](/postgresql/how-to/manage) — Users, extensions, backups, and export/import.
+
+## Need help?
+
+Stuck, or want to share what you built? Our core team and community are on Discord.
+
+- [Discord](https://discord.com/invite/WDvCZ54) — Join the Zerops community on Discord. Ask questions and share your tips.
+- [zCLI](/references/cli) — Get more out of Zerops with the command-line tool.
----------------------------------------
@@ -28156,98 +28125,32 @@ For advanced configurations or custom requirements:
----------------------------------------
-# Quickstart
-
-
-With this guide, we'll go from zero to a live app with a managed database, a public URL, and auto-deploy on every git push. Takes about 5 minutes.
-
-Here's what we're building together: a page with a button that says **"I followed the Zerops quickstart"**. Every developer who finishes this guide and clicks it gets counted. It's a real app: Express backend, PostgreSQL database, static frontend, all running on **Zerops**.
-
-**See it live before you build it:** [app-25be-3000.prg1.zerops.app](https://app-25be-3000.prg1.zerops.app/)
-[Video: /vids/quickstart-app.webm](/vids/quickstart-app.webm)
-
-:::info Two ways to use this guide
-**Just want to see how Zerops works?** Follow along as-is. The feedback app covers everything: frontend, backend, managed database, private networking, auto-deploy.
-
-**Already have an app?** Pick a recipe matching your stack in Step 1 instead of Node.js. The steps are identical. Then in Step 5, replace the feedback app code with your own. The `zerops.yml` structure stays the same.
-:::
-
-:::tip Before we start
-Sign up at [app.zerops.io](https://app.zerops.io). You get $15 in promo credits on signup, no credit card needed. Verify your account with a $10 payment and get an additional $50, bringing your total to $65 in credits. A simple app with a database costs roughly $3-5/month, so credits go a long way.
-:::
-
----
-
-## 1. Deploy the recipe
-
-Go to [app.zerops.io/recipes](https://app.zerops.io/recipes). No account needed, the recipes page works even when you're not logged in.
-
-For this guide, find and click **Node.js**. You'll see **Node.js Hello World**, a Node.js app with Express connected to a PostgreSQL database.
-
-Hit **Deploy nodejs-hello-world-small-prod**. If you're not signed in yet, Zerops creates your account and kicks off the deploy in the same step.
-
-:::info What's a recipe?
-A recipe is a working app with infrastructure already configured: managed database, environment variables, `zerops.yaml`, everything connected. It's a correct starting point, not a finished product. We'll swap the code out in Step 5.
-:::
-
-:::note Want a different stack?
-Everything in this guide works the same way for Next.js, Python, Go, Laravel, and more. Just pick the recipe that matches your stack instead. The steps are identical.
-:::
-
----
-
-## 2. Watch it build
-
-After hitting Deploy, you'll land on the dashboard. On the right side you'll see a pipeline already running.
-
-It goes through these steps in real time:
-
-1. Initializing build container
-2. Running build commands from `zerops.yaml`
-3. Creating app version and upgrading service
-4. Done
-
-:::tip Good to know
-The build container is **temporary and free**. Zerops spins it up, runs your build, saves the output, and deletes it. You're only billed for the running app.
-:::
-
-Once the pipeline finishes, your app is live.
+# Quickstart > Quickstart Going Further
---
+title: Deploy Your First App
+description: Swap the recipe for your own code, deploy the feedback app, and connect GitHub for auto-deploy.
+sidebar_label: Deploy Your First App
+slug: /quickstart/deploy-your-first-app
-## 3. See what got created
-
-Click into your new project. You'll see:
-
-- **app**: your Node.js service, already running
-- **db**: a managed PostgreSQL database, already running
-- **Project core**: load balancer, firewall, logger, all managed by Zerops
-
-All of these are already talking to each other on a private network. Nothing to configure.
-
-:::tip Coming from Vercel and Supabase?
-You're probably used to copying a long database URL and pasting it as an environment variable. On Zerops, services talk to each other by hostname. `DB_HOST` is just `db`, not a URL. You still set credentials in `zerops.yaml`, Zerops fills in the values. No connection strings to manage.
-:::
-
+custom_edit_url: null
---
-## 4. Open your live app
-
-The Zerops subdomain is already enabled when you deploy from a recipe. Click into your **app** service and open the URL. It looks something like `app-2437-3000.prg1.zerops.app`.
+You've got a live app running on Zerops. Now let's make it actually yours.
-The default recipe app will be there. Next step, we replace it with the feedback app.
+This page walks you through deploying the feedback app we showed you at the top of the quickstart - a Node.js app with a PostgreSQL database, auto-deploy on every git push, and a wall of everyone who's made it through. If you'd rather skip straight to your own code, there's a note at the bottom for that.
-When you're ready for production, point a custom domain at your app. Zerops handles the SSL certificate automatically.
+### Deploy the feedback app
----
+Start from the recipe template. Click the link below, pick a name for your repo, and hit **Create repository**:
-## 5. Swap in the feedback app
+**[github.com/new?template_name=recipe-nodejs&template_owner=zeropsio](https://github.com/new?template_name=recipe-nodejs&template_owner=zeropsio)**
-Clone the recipe repo and open it in your editor:
+Then clone your new repo and install dependencies:
```bash
-git clone https://github.com/zeropsio/recipe-nodejs
-cd recipe-nodejs
+git clone https://github.com/your-username/your-repo.git
+cd your-repo
npm install
```
@@ -28271,6 +28174,7 @@ export const connectDB = async () => {
await client.query(`
CREATE TABLE IF NOT EXISTS clicks (
id SERIAL PRIMARY KEY,
+ seed INTEGER NOT NULL,
clicked_at TIMESTAMPTZ DEFAULT NOW()
)
`);
@@ -28289,17 +28193,24 @@ app.use(express.static(path.join(__dirname, '../public')));
app.get('/count', async (_, res) => {
const client = await connectDB();
- const result = await client.query('SELECT COUNT(*) FROM clicks');
+ const result = await client.query(
+ 'SELECT seed FROM clicks ORDER BY id ASC LIMIT 20'
+ );
+ const countResult = await client.query('SELECT COUNT(*) FROM clicks');
await client.end();
- res.json({ count: parseInt(result.rows[0].count) });
+ res.json({
+ count: parseInt(countResult.rows[0].count),
+ seeds: result.rows.map((r) => r.seed),
+ });
});
app.post('/click', async (_, res) => {
const client = await connectDB();
- await client.query('INSERT INTO clicks DEFAULT VALUES');
- const result = await client.query('SELECT COUNT(*) FROM clicks');
+ const seed = Math.floor(Math.random() * 1000000);
+ await client.query('INSERT INTO clicks (seed) VALUES ($1)', [seed]);
+ const countResult = await client.query('SELECT COUNT(*) FROM clicks');
await client.end();
- res.json({ count: parseInt(result.rows[0].count) });
+ res.json({ count: parseInt(countResult.rows[0].count), seed });
});
app.get('/status', (_, res) => {
@@ -28311,6 +28222,8 @@ export default app;
**Create a `public/` folder** at the repo root and add `public/index.html`:
+
+
The `zerops.yml` already exists in the repo. Update `deployFiles` to include the `public` folder:
```yaml
@@ -28563,9 +28484,11 @@ zerops:
envVariables:
NODE_ENV: production
DB_NAME: db
- DB_HOST: db
- DB_USER: db
- DB_PASS: ${db_password}
+ DB_HOST: ${db_hostname}
+ DB_USER: ${db_user}
+ DB_PASSWORD: ${db_password}
+ # or use the full connection string:
+ # DB_CONNECTION_STRING: ${db_connectionString}
start: npm run start:prod
healthCheck:
httpGet:
@@ -28573,78 +28496,1601 @@ zerops:
path: /status
```
-Before pushing, point the repo at your own GitHub remote:
+:::tip How Zerops env variables work
+Zerops automatically generates credentials for every managed service. The variable names are derived from the service hostname — so if your database service is named `db`, the variables are `${db_hostname}`, `${db_user}`, `${db_password}`, and `${db_connectionString}`. If you named it `postgres` instead, they'd be `${postgres_hostname}`, `${postgres_password}`, and so on.
+:::
+
+Push to your repo and connect GitHub in the next section.
+
+```bash
+git add .
+git commit -m "add feedback app"
+git push
+```
+
+:::note Want to build something else instead?
+Skip the feedback app. Pick the recipe matching your stack from [app.zerops.io/recipes](https://app.zerops.io/recipes), add a `zerops.yaml` to your repo root copying the structure from the recipe, and adjust `buildCommands`, `deployFiles`, and `start` for your stack. The database env variables (`${db_hostname}`, `${db_user}`, `${db_password}`) stay the same regardless of what you're building.
+:::
+
+### Connect GitHub and auto-deploy
+
+1. Click into your **app** service
+2. Scroll down to **Pipelines & CI/CD settings**
+3. Click **GitHub** to connect your repo
+4. Select your repo and set **Trigger on** to **Push to Branch**, pick `main`
+5. In the **"Which `setup` from zerops.yml to use"** field, type `app`
+6. Click **Activate pipeline trigger**
+
+That's it. Every push to main now builds and deploys automatically. Zero downtime, Zerops runs the new version alongside the old one, waits for a health check, then switches traffic over.
+
+You can also trigger deploys manually with the Zerops CLI: `zcli push`.
+
+### Add yourself to the list
+
+Deployed the feedback app? Open your live app URL and click **"I followed the Zerops quickstart"**. You'll show up alongside everyone else who's made it through.
+
+Check out everyone who's already made it: [app-25be-3000.prg1.zerops.app](https://app-25be-3000.prg1.zerops.app/)
+
+### If something breaks
+
+Got a 502 or an app crash on startup? Start here.
+
+**Check the runtime logs first.** Dashboard, click your app service, click the three-dot menu, then **Runtime log**. The error will be there, usually in the last few lines.
+
+Two things come up most often on a first deploy:
+
+:::tip Debug locally with VPN
+Install zcli first (see [CLI reference](/references/cli)), then run `zcli vpn up [your-project-id]` and your machine joins the project's private network. You can connect to `db:5432` directly from your local machine using TablePlus, psql, or any database client. You can disable SSL when connecting over VPN - the tunnel itself handles security either way. If `db` doesn't resolve, try `db.zerops` instead.
+:::
+
+### What's next
+
+- **[SSH into your container](/references/networking/ssh)**: `zcli service shell [service-name]` for full Linux access
+- **[Custom domain](/references/networking/public-access)**: add your domain, SSL is automatic
+- **[Autoscaling](/features/scaling)**: set min and max CPU and RAM, Zerops scales within that range automatically
+- **[Add more services](/features/infrastructure)**: queues, search engines, object storage, just add them to your project
+- **[Try ZCP](/zcp/quickstart)**: Zerops' AI agent that can deploy, debug, and operate your project
+
+:::note Stuck?
+Jump into the [Zerops Discord](https://docs.zerops.io/discord). The community is active and the team is there.
+:::
+
+
+----------------------------------------
+
+# Quickstart > Quickstart
+
+
+In the next 5 minutes, we'll go from zero to a live app: a managed database, a public URL, and auto-deploy on every git push, all set up for you.
+
+Here's what we're building together: a page with a button that says **"I followed the Zerops quickstart"**. Every developer who finishes this guide and clicks it gets added to the wall. It's a real app, Express backend, PostgreSQL database, static frontend, running entirely on **Zerops**.
+
+Curious what you'll end up with? Here it is, live: [app-25be-3000.prg1.zerops.app](https://app-25be-3000.prg1.zerops.app/)
+
+[Video: /vids/quickstart-app.webm](/vids/quickstart-app.webm)
+
+:::tip Before we start
+Sign up at [app.zerops.io](https://app.zerops.io). You get $15 in promo credits on signup, no credit card needed. Verify your account with a $10 payment and get an additional $50, bringing your total to $65 in credits. A simple app with a database costs roughly $3-5/month, so credits go a long way.
+:::
+
+---
+
+## 1. Deploy the recipe
+
+Go to [app.zerops.io/recipes](https://app.zerops.io/recipes). No account needed, the recipes page works even when you're not logged in.
+
+For this guide, find and click **Node.js**. You'll see **Node.js Hello World**, a Node.js app with Express connected to a PostgreSQL database.
+
+Hit **Deploy nodejs-hello-world-small-prod**. If you're not signed in yet, Zerops creates your account and kicks off the deploy in the same step.
+
+:::info What's a recipe?
+A recipe is a working app with infrastructure already configured: managed database, environment variables, `zerops.yaml`, everything connected. It's a correct starting point, not a finished product.
+:::
+
+---
+
+## 2. Watch it build
+
+After hitting Deploy, you'll land on the dashboard. On the right side you'll see a pipeline already running.
+
+It goes through these steps in real time:
+
+1. Initializing build container
+2. Running build commands from `zerops.yaml`
+3. Creating app version and upgrading service
+4. Done
+
+:::tip Good to know
+The build container is **temporary and free**. Zerops spins it up, runs your build, saves the output, and deletes it. You're only billed for the running app.
+:::
+
+Once the pipeline finishes, your app is live.
+
+---
+
+## 3. See what got created
+
+Click into your new project. You'll see:
+
+- **app**: your Node.js service, already running
+- **db**: a managed PostgreSQL database, already running
+- **Project core**: load balancer, firewall, logger, all managed by Zerops
+
+All of these are already talking to each other on a private network. Nothing to configure.
+
+---
+
+## 4. Open your live app
+
+The Zerops subdomain is already enabled when you deploy from a recipe. Click into your **app** service and open the URL.
+
+The default recipe app will be there.
+
+When you're ready for production, point a custom domain at your app. Zerops handles the SSL certificate automatically.
+
+---
+
+## You're live 🎉
+
+Here's what's running:
+
+- A real app on production infrastructure
+- A managed PostgreSQL database, ready to use
+- All services on a private network, secure by default
+- Full Linux containers for your app — SSH in anytime, install anything
+
+None of the infrastructure needed manual configuration.
+
+:::note Stuck?
+Jump into the [Zerops Discord](https://docs.zerops.io/discord). The community is active and the team is there.
+:::
+
+---
+
+Your app is live and the infrastructure is running.
+
+**Next up:** Swap the recipe for the feedback app we showed you, connect GitHub for auto-deploy, and add yourself to the wall. [Deploy your first app →](/quickstart/deploy-your-first-app)
+
+
+----------------------------------------
+
+# Ruby > How To > Build Pipeline
+
+
+Zerops provides a customizable build and runtime environment for your Ruby application.
+
+## Add zerops.yaml to your repository
+
+Start by adding `zerops.yaml` file to the **root of your repository** and modify it to fit your application:
+
+```yaml
+zerops:
+ # define hostname of your service
+ - setup: app
+ # ==== how to build your application ====
+ build:
+ # REQUIRED. Set the base technology for the build environment:
+ base: ubuntu/ruby@4.0
+
+ # OPTIONAL. Customise the build environment by installing additional packages
+ # or tools to the base build environment.
+ # prepareCommands:
+ # - sudo apt-get update
+ # - sudo apt-get install -y some-package
+
+ # OPTIONAL. Set the env variables for the build environment.
+ # BUNDLE_PATH makes Bundler install gems into ./vendor/bundle
+ # so they can be deployed together with your application.
+ envVariables:
+ BUNDLE_PATH: vendor/bundle
+
+ # OPTIONAL. Build your application
+ buildCommands:
+ - bundle install
+
+ # REQUIRED. Select which files / folders to deploy after
+ # the build has successfully finished
+ deployFiles:
+ - ./vendor
+ - ./Gemfile
+ - ./Gemfile.lock
+ - ./config.ru
+ - ./src
+
+ # OPTIONAL. Which files / folders you want to cache for the next build.
+ # Next builds will be faster when the cache is used.
+ cache: vendor
+
+ # ==== how to run your application ====
+ run:
+ # OPTIONAL. Sets the base technology for the runtime environment:
+ base: ubuntu/ruby@4.0
+
+ # OPTIONAL. Sets the internal port(s) your app listens on:
+ ports:
+ # port number
+ - port: 8080
+
+ # OPTIONAL. Customise the runtime Ruby environment by installing additional
+ # dependencies to the base Ruby runtime environment.
+ # prepareCommands:
+ # - sudo apt-get update
+ # - sudo apt-get install -y some-package
+
+ # OPTIONAL. Run one or more commands each time a new runtime container
+ # is started or restarted. These commands are triggered before
+ # your Ruby application is started.
+ # initCommands:
+ # - rm -rf ./cache
+
+ # OPTIONAL. Set the env variables for the runtime environment.
+ envVariables:
+ BUNDLE_PATH: vendor/bundle
+
+ # REQUIRED. Your Ruby application start command
+ start: bundle exec puma -p 8080 -b tcp://0.0.0.0
+```
+
+The top-level element is always `zerops`.
+
+### Setup
+
+The first element `setup` contains the **hostname** of your service. A runtime service with the same hostname must exist in Zerops.
+Zerops supports the definition of multiple runtime services in a single `zerops.yaml`. This is useful when you use a monorepo. Just add multiple setup elements in your `zerops.yaml`:
+
+```yaml
+zerops:
+ # definition for app service
+ - setup: app
+ # optional
+ build: ...
+ # optional
+ deploy: ...
+ # required
+ run: ...
+
+ # definition for api service
+ - setup: api
+ # optional
+ build: ...
+ # optional
+ deploy: ...
+ # required
+ run: ...
+```
+
+Each service configuration contains at least the `run` section. Optional `build` and `deploy` sections can be added to further customize your process.
+
+## Build pipeline configuration
+
+### base
+
+_REQUIRED._ Sets the base technology for the build environment.
+
+Following options are available for Ruby builds:
+
+- `alpine/ruby@4.0`, `ubuntu/ruby@4.0`, `alpine/ruby@latest`, `ubuntu/ruby@latest`
+- `alpine/ruby@3.4`, `ubuntu/ruby@3.4`
+- `alpine/ruby@3.3`, `ubuntu/ruby@3.3`
+
+The base value always includes the operating system prefix (`ubuntu/` or `alpine/`). The `@latest` tag is an alias that points to Ruby 4.0.
+
+```yaml
+zerops:
+ # hostname of your service
+ - setup: app
+ # ==== how to build your application ====
+ build:
+ # REQUIRED. Sets the base technology for the build environment:
+ base: ubuntu/ruby@4.0
+ ...
+```
+
+
+ The base build environment contains {data.ubuntu.default} or {data.alpine.default} (depending on the chosen base prefix), the selected
+ major version of Ruby, [Zerops command line tool](/references/cli), `gem`, `bundler` and `git` tools. A full native-extension
+ toolchain (gcc, make, libpq-dev, libyaml-dev, libffi-dev, etc.) is included, so gems with C extensions build out of the box.
+
+
+:::info
+You can change the base environment when you need to. Just simply modify the `zerops.yaml` in your repository.
+:::
+
+If you need to install more technologies to the build environment, set multiple values as a yaml array. For example:
+
+```yaml
+zerops:
+ # hostname of your service
+ - setup: app
+ # ==== how to build your application ====
+ build:
+ # REQUIRED. Sets the base technology for the build environment:
+ base:
+ - ubuntu/ruby@4.0
+ prepareCommands:
+ - zsc add nodejs@latest
+ ...
+```
+
+See the full list of supported [build base environments](/zerops-yaml/base-list#runtime-services).
+
+To customise your build environment use the [prepareCommands](#preparecommands) attribute.
+
+:::note
+Modifying the base technology will invalidate your build cache. See our [Build Cache Documentation](/features/build-cache) for more details about cache invalidation.
+:::
+
+### os
+
+The operating system for the build environment is selected as part of the [base](#base) value. There is no separate `os` attribute for Ruby, choose one of:
+
+- `ubuntu/ruby@4.0`
+- `alpine/ruby@4.0`
+
+We are currently using following os version:
+
+- {data.alpine.default}
+- {data.ubuntu.default}
+
+:::caution
+The os version is fixed and cannot be customised.
+:::
+
+:::note
+Changing the OS prefix of your base will invalidate your build cache. See our [Build Cache Documentation](/features/build-cache) for details about cache behavior.
+:::
+
+### prepareCommands
+
+_OPTIONAL._ Customises the build environment by installing additional dependencies or tools to the base build environment.
+
+The base build environment contains:
+
+- {data.ubuntu.default} or {data.alpine.default} (depending on the chosen base prefix)
+- selected version of Ruby defined in the [base](#base) attribute
+- [Zerops command line tool](/references/cli)
+- `gem`, `bundler` and `git` tools
+
+To install additional packages or tools add one or more prepare commands:
+
+```yaml
+zerops:
+ # hostname of your service
+ - setup: app
+ # ==== how to build your application ====
+ build:
+ # REQUIRED. Set the base technology for the build environment:
+ base: ubuntu/ruby@4.0
+
+ # OPTIONAL. Customise the build environment by installing additional packages
+ # or tools to the base build environment.
+ prepareCommands:
+ - sudo apt-get update
+ - sudo apt-get install -y some-package
+ ...
+```
+
+On the `alpine/ruby@4.0` base use `sudo apk add --no-cache some-package` instead.
+
+When the first build is triggered, Zerops will
+
+1. create a build container
+2. download your application code from your repository
+3. run the prepare commands in the defined order
+
+The application code is available in `/build/source` before the prepare commands are triggered, so you can use any file from your repository in your prepare commands (e.g. a configuration file). The commands themselves run in the `/home/zerops` directory.
+
+:::note
+These commands are skipped when using cached environment. Modifying `prepareCommands` will invalidate your build cache. See our [Build Cache Documentation](/features/build-cache) for details about cache invalidation.
+:::
+
+#### Command exit code
+
+If any command fails, it returns an exit code other than 0 and the build is canceled. Read the [build log](/ruby/how-to/logs#build-log) to troubleshoot the error. If the command ends successfully, it returns the exit code 0 and Zerops triggers the following command. When all prepare commands are finished, your custom build environment is ready for the build phase.
+
+#### Single or separated shell instances
+
+You can configure your prepare commands to be run in a single shell instance or multiple shell instances. The format is identical to [build commands](#buildcommands).
+
+### buildCommands
+
+_OPTIONAL._ Defines build commands.
+
+```yaml
+zerops:
+ # hostname of your service
+ - setup: app
+ # ==== how to build your application ====
+ build:
+ # REQUIRED. Set the base technology for the build environment:
+ base: ubuntu/ruby@4.0
+
+ # OPTIONAL. Set the env variables for the build environment:
+ envVariables:
+ BUNDLE_PATH: vendor/bundle
+
+ # OPTIONAL. Build your application
+ buildCommands:
+ - bundle install
+ ...
+```
+
+Build commands are optional. Zerops triggers each command in the defined order in a dedicated build container, running from the `/build/source` directory.
+
+With `BUNDLE_PATH` set to `vendor/bundle`, Bundler installs your gems into the `vendor` folder inside your project, so they can be deployed together with your application and used at runtime via `bundle exec`.
+
+Before the build commands are triggered the build container contains:
+
+1. base environment defined by the [base](#base) attribute
+2. optional customisation of the base environment defined in the [prepareCommands](#preparecommands) attribute
+3. your application code
+
+#### Run build commands as a single shell instance
+
+Use following syntax to run all commands in the same environment context. For example, if one command changes the current directory, the next command continues in that directory. When one command creates an environment variable, the next command can access it.
+
+```yaml
+buildCommands:
+ - |
+ bundle install
+ bundle exec rake assets:precompile
+```
+
+#### Run build commands as a separate shell instances
+
+When the following syntax is used, each command is triggered in a separate environment context. For example, each shell instance starts in the home directory again. When one command creates an environment variable, it won't be available for the next command.
+
+```yaml
+buildCommands:
+ - bundle install
+ - bundle exec rake assets:precompile
+```
+
+#### Command exit code
+
+If any command fails, it returns an exit code other than 0 and the build is canceled. Read the [build log](/ruby/how-to/logs#build-log) to troubleshoot the error. If the error log doesn't contain any specific error message, try to run your build with the --verbose option.
+
+```yaml
+buildCommands:
+ - bundle install --verbose
+```
+
+If the command ends successfully, it returns the exit code 0 and Zerops triggers the following command. When all `buildCommands` are finished, the application build is completed and ready for the deploy phase.
+
+### deployFiles
+
+_REQUIRED._ Selects which files or folders will be deployed after the build has successfully finished. To filter out specific files or folders, use [`.deployignore`](#deployignore) file.
+
+```yaml
+# REQUIRED. Select which files / folders to deploy after
+# the build has successfully finished
+deployFiles:
+ - ./vendor
+ - ./Gemfile
+ - ./Gemfile.lock
+ - ./config.ru
+ - ./src
+```
+
+Determines files or folders produced by your build, which should be deployed to your runtime service containers.
+
+The path starts from the **root directory** of your project (the location of `zerops.yaml`). You must enclose the name in quotes if the folder or the file name contains a space.
+
+The files/folders will be placed into `/var/www` folder in runtime, e.g. `./src/assets/fonts` would result in `/var/www/src/assets/fonts`.
+
+#### Examples
+
+Deploys a folder, and a file from the project root directory:
+
+```yaml
+deployFiles:
+ - vendor
+ - Gemfile
+```
+
+Deploys the whole content of the build container:
+
+```yaml
+deployFiles: .
+```
+
+Deploys a folder, and a file in a defined path:
+
+```yaml
+deployFiles:
+ - ./path/to/file.txt
+ - ./path/to/dir/
+```
+
+#### How to use a wildcard in the path
+
+Zerops supports the `~` character as a wildcard for one or more folders in the path.
+
+Deploys all `file.txt` files that are located in any path that begins with `/path/` and ends with `/to/`
+
+```yaml
+deployFiles: ./path/~/to/file.txt
+```
+
+Deploys all folders that are located in any path that begins with `/path/to/`
+
+```yaml
+deployFiles: ./path/to/~/
+```
+
+Deploys all folders that are located in any path that begins with `/path/` and ends with `/to/`
+
+```yaml
+deployFiles: ./path/~/to/
+```
+
+:::note Example
+By default, `./src/assets/fonts` deploys to `/var/www/src/assets/fonts`, keeping the full path. Adding `~`, like `./src/assets/~fonts`, shortens it to `/var/www/fonts`
+:::
+#### .deployignore
+
+Add a `.deployignore` file to the root of your project to specify which files and folders Zerops should ignore during deploy. The syntax follows the same pattern format as [`.gitignore`](https://git-scm.com/docs/gitignore#_pattern_format).
+
+To ignore a specific file or directory path, start the pattern with a forward slash (`/`). Without the leading slash, the pattern will match files with that name in any directory.
+
+:::tip
+For consistency, it's recommended to configure both your `.gitignore` and `.deployignore` files with the same patterns.
+:::
+
+Examples:
+
+```yaml title="zerops.yaml"
+zerops:
+ - setup: app
+ build:
+ deployFiles: ./
+```
+
+```text title=".deployignore"
+/src/file.txt
+```
+The example above ignores `file.txt` only in the root src directory.
+```text title=".deployignore"
+src/file.txt
+```
+This example above ignores `file.txt` in ANY directory named `src`, such as:
+- `/src/file.txt`
+- `/folder2/folder3/src/file.txt`
+- `/src/src/file.txt`
+
+:::note
+`.deployignore` file also works with [`zcli service deploy`](/references/zcli/commands#deploy) command.
+:::
+
+### cache
+
+_OPTIONAL._ Defines which files or folders will be cached for the next build.
+
+```yaml
+# OPTIONAL. Which files / folders you want to cache for the next build.
+# Next builds will be faster when the cache is used.
+cache: vendor
+```
+
+The cache attribute helps optimize build times by preserving specified files between builds. Caching the `vendor` folder means the next `bundle install` only fetches gems that changed in your `Gemfile.lock`.
+
+The cache attribute supports the [~ wildcard character](#how-to-use-a-wildcard-in-the-path).
+
+Learn more about the [build cache system](/features/build-cache) in Zerops.
+
+### envVariables
+
+_OPTIONAL._ Defines the environment variables for the build environment.
+
+Enter one or more env variables in following format:
+
+```yaml
+zerops:
+ # define hostname of your service
+ - setup: app
+ # ==== how to build your application ====
+ build:
+ base: ubuntu/ruby@4.0
+ …
+
+ # OPTIONAL. Defines the env variables for the build environment:
+ envVariables:
+ BUNDLE_PATH: vendor/bundle
+ BUNDLE_WITHOUT: development
+```
+
+Read more about [environment variables](/ruby/how-to/env-variables) in Zerops.
+
+## Runtime configuration
+
+### base
+
+_OPTIONAL._ Sets the base technology for the runtime environment.
+If you don't specify the `run.base` attribute, Zerops keeps the current Ruby version for your runtime.
+
+Following options are available for Ruby runtimes:
+
+- `alpine/ruby@4.0`, `ubuntu/ruby@4.0`, `alpine/ruby@latest`, `ubuntu/ruby@latest`
+- `alpine/ruby@3.4`, `ubuntu/ruby@3.4`
+- `alpine/ruby@3.3`, `ubuntu/ruby@3.3`
+
+```yaml
+zerops:
+ # hostname of your service
+ - setup: app
+ # ==== how to build your application ====
+ build:
+ # REQUIRED. Sets the base technology for the build environment:
+ base: ubuntu/ruby@4.0
+ ...
+
+ # ==== how to run your application ====
+ run:
+ # OPTIONAL. Sets the base technology for the runtime environment:
+ base: ubuntu/ruby@4.0
+ ...
+```
+
+
+ The base runtime environment contains {data.ubuntu.default} or {data.alpine.default} (depending on the chosen base prefix), the
+ selected major version of Ruby, Zerops command line tool, `gem`, `bundler` and `git` tools. A full native-extension toolchain
+ (gcc, make, libpq-dev, libyaml-dev, libffi-dev, etc.) is included, so gems with C extensions work out of the box.
+
+
+:::info
+You can change the base environment when you need to. Just simply modify the `zerops.yaml` in your repository.
+:::
+
+If you need to install more technologies to the runtime environment, set multiple values as a yaml array. For example:
+
+```yaml
+zerops:
+ # hostname of your service
+ - setup: app
+ # ==== how to build your application ====
+ build:
+ # REQUIRED. Sets the base technology for the build environment:
+ base: ubuntu/ruby@4.0
+ ...
+
+ # ==== how to run your application ====
+ run:
+ # OPTIONAL. Sets the base technology for the runtime environment:
+ base:
+ - ubuntu/ruby@4.0
+ prepareCommands:
+ - zsc add nodejs@latest
+ ...
+```
+
+See the full list of supported [run base environments](/zerops-yaml/base-list).
+
+To customise your build environment use the `prepareCommands` attribute.
+
+### os
+
+The operating system for the runtime environment is selected as part of the [base](#base-1) value. There is no separate `os` attribute for Ruby, choose one of:
+
+- `ubuntu/ruby@4.0`
+- `alpine/ruby@4.0`
+
+We are currently using following os version:
+
+- {data.alpine.default}
+- {data.ubuntu.default}
+
+:::caution
+The os version is fixed and cannot be customised.
+:::
+
+### ports
+
+_OPTIONAL._ Specifies one or more internal ports on which your application will listen.
+
+Projects in Zerops represent a group of one or more services. Services can be of different types (runtime services, databases, message brokers, object storage, etc.). All services of the same project share a **dedicated private network**. To connect to a service within the same project, just use the service hostname and its internal port.
+
+For example, to connect to a Ruby service with hostname = "app" and port = 8080 from another service of the same project, simply use `app:8080`. Read more about [how to access a Ruby service](/references/networking/internal-access#basic-service-communication).
+
+Each port has following attributes:
+
+| parameter | description |
+| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| port | Defines the port number. You can set any port number between _10_ and _65435_. Ports outside this interval are reserved for internal Zerops systems. |
+| protocol | **Optional.** Defines the protocol. Allowed values are `TCP` or `UDP`. Default value is `TCP`. |
+| httpSupport | **Optional.** `httpSupport = true` is the default setting for TCP protocol. Set `httpSupport = false` if a web server isn't running on the port. Zerops uses this information for the configuration of [public access](/features/access). `httpSupport = true` is available only in combination with the TCP protocol. |
+| httpSupport | **Optional.** `httpSupport = true` is the default setting for TCP protocol. Set `httpSupport = false` if a web server isn't running on the port. Zerops uses this information for the configuration of [public access](/features/access). `httpSupport = true` is available only in combination with the TCP protocol. |
+
+### prepareCommands
+
+_OPTIONAL._ Customises the Ruby runtime environment by installing additional dependencies or tools to the runtime base environment.
+
+
+ The base Ruby environment contains {data.ubuntu.default} or {data.alpine.default} (depending on the chosen base prefix), the selected
+ major version of Ruby, [Zerops command line tool](/references/cli) and `gem`, `bundler` and `git` tools. To install
+ additional packages or tools add one or more prepare commands:
+
+
+```yaml
+zerops:
+ # hostname of your service
+ - setup: app
+ # ==== how to build your application ====
+ build:
+ ...
+
+ # ==== how to run your application ====
+ run:
+ # OPTIONAL. Customise the runtime environment by installing additional packages
+ # or tools to the base Ruby runtime environment.
+ prepareCommands:
+ - sudo apt-get update
+ - sudo apt-get install -y some-package
+ ...
+```
+
+On the `alpine/ruby@4.0` base use `sudo apk add --no-cache some-package` instead.
+
+When the first deploy with a defined prepare attribute is triggered, Zerops will
+
+1. create a prepare runtime container
+2. optionally: [copy selected folders or files from your build container](#copy-folders-or-files-from-your-build-container)
+3. run the `prepareCommands` commands in the defined order
+
+:::note
+`run.prepareCommands` run in the `/home/zerops` directory.
+:::
+
+#### Command exit code
+
+If any command fails, it returns an exit code other than 0 and the deploy is canceled. Read the [prepare runtime log](/ruby/how-to/logs#prepare-runtime-log) to troubleshoot the error. If the command ends successfully, it returns the exit code 0 and Zerops triggers the following command. When all `prepareCommands` commands are finished, your custom runtime environment is ready for the deploy phase.
+
+#### Cache of your custom runtime environment
+
+Some packages or tools can take a long time to install. Therefore, Zerops caches your custom runtime environment after the installation of your custom packages or tools is completed. When the second or following deploy is triggered, Zerops will use the custom runtime cache from the previous deploy if following conditions are met:
+
+1. Content of the [build.addToRunPrepare](#copy-folders-or-files-from-your-build-container) and `run.prepareCommands` attributes didn't change from the previous deploy
+2. The custom runtime cache wasn't invalidated in the Zerops GUI.
+
+To invalidate the custom runtime cache go to `yyy`
+
+When the custom runtime cache is used, Zerops doesn't create a prepare runtime container and executes the deployment of your application directly.
+
+#### Single or separated shell instances
+
+You can configure your prepare commands to be run in a single shell instance or multiple shell instances. The format is identical to [build commands](#buildcommands).
+
+### Copy folders or files from your build container
+
+
+ The prepare runtime container contains {data.ubuntu.default} or {data.alpine.default} (depending on the chosen base prefix), the
+ selected major version of Ruby, [Zerops command line tool](/references/cli) and `gem`, `bundler` and `git` tools.
+
+
+The prepare runtime container does not contain your application code nor the built application. If you need to copy some folders or files from the build container to the runtime container (e.g. a configuration file) use the `addToRunPrepare` attribute in the [build section](#build-pipeline-configuration).
+
+```yaml
+zerops:
+ # hostname of your service
+ - setup: app
+ # ==== how to build your application ====
+ build:
+ ...
+ addToRunPrepare: ./runtime-config.yaml
+
+ # ==== how to run your application ====
+ run:
+ # OPTIONAL. Customise the runtime environment by installing additional packages
+ # or tools to the base Ruby runtime environment.
+ prepareCommands:
+ - sudo apt-get update
+ - sudo apt-get install -y some-package
+ ...
+```
+
+In the example above Zerops will copy the `runtime-config.yaml` file from your build container **after the build has finished** into the new **prepare runtime** container. The copied files and folders will be available in the `/home/zerops` folder in the new prepare runtime container before the prepare commands are triggered.
+
+### initCommands
+
+_OPTIONAL._ Defines one or more commands to be run each time a new runtime container is started or a container is restarted.
+
+```yaml
+zerops:
+ # hostname of your service
+ - setup: app
+ # ==== how to build your application ====
+ build: ...
+
+ # ==== how to run your application ====
+ run:
+ # OPTIONAL. Run one or more commands each time a new runtime container
+ # is started or restarted. These commands are triggered before
+ # your Ruby application is started.
+ initCommands:
+ - rm -rf ./cache
+```
+
+These commands are triggered in the runtime container before your Ruby application is started via the [start command](#start).
+
+:::note
+`run.initCommands` run in the `/var/www` directory.
+:::
+
+Use init commands to clean or initialise your application cache or similar operations.
+
+:::caution
+The init commands will delay the start of your application each time a new runtime container is started (including the horizontal [scaling](/ruby/how-to/scaling) or when a runtime container is restarted).
+
+Do not use the init commands for customising your runtime environment. Use the [run:prepareCommands](#preparecommands-1) attribute instead.
+:::
+
+#### Command exit code
+
+If any of the `initCommands` fails, it returns an exit code other than 0, but deploy is **not** canceled. After all init commands are finished, regardless of the status code, the application is started. Read the [runtime log](/ruby/how-to/logs#runtime-log) to troubleshoot the error.
+
+#### Single or separated shell instances
+
+You can configure your `initCommands` to be run in a single shell instance or multiple shell instances. The format is identical to [build commands](#buildcommands).
+
+### envVariables
+
+_OPTIONAL._ Defines the environment variables for the runtime environment.
+
+Enter one or more env variables in following format:
+
+```yaml
+zerops:
+ # define hostname of your service
+ - setup: app
+ # ==== how to run your application ====
+ run:
+ # OPTIONAL. Defines the env variables for the runtime environment:
+ envVariables:
+ RACK_ENV: production
+ BUNDLE_PATH: vendor/bundle
+ DB_NAME: db
+ DB_HOST: db
+ DB_USER: db
+ DB_PASS: ${db_password}
+```
+
+Read more about [environment variables](/ruby/how-to/env-variables) in Zerops.
+
+### start
+
+_REQUIRED._ Defines the start command for your Ruby application.
+
+```yaml
+zerops:
+ # hostname of your service
+ - setup: app
+ # ==== how to build your application ====
+ build: ...
+
+ # ==== how to run your application ====
+ run:
+ # REQUIRED. Your Ruby application start command
+ start: bundle exec puma -p 8080 -b tcp://0.0.0.0
+```
+
+We recommend starting your Ruby application via `bundle exec`, so the gems vendored into `vendor/bundle` during the build are used at runtime. The `-b tcp://0.0.0.0` option makes Puma bind to all interfaces, not just localhost.
+
+### health check
+
+_OPTIONAL._ Defines a health check.
+
+`healthCheck` requires either one `httpGet` object or one `exec` object.
+
+#### httpGet
+
+Configures the health check to request a local URL using a HTTP GET method.
+
+Following attributes are available:
+
+
+
+
+
Parameter
+
Description
+
+
+
+
+
port
+
Defines the port of the HTTP GET request.
+The readiness check will trigger a GET request on {'http://127.0.0.1:{port}/{path}'}
+
+
+
path
+
Defines the URL path of the HTTP GET request.
+The readiness check will trigger a GET request on {'http://127.0.0.1:{port}/{path}'}
+
+
+
host
+
Optional. The readiness check is triggered from inside of your runtime container so it always uses the localhost 127.0.0.1. If you need to add a host to the request header, specify it in the host attribute.
+
+
+
scheme
+
Optional. The readiness check is triggered from inside of your runtime container so no https is required.
+If your application requires a https request, set scheme: https
+
+
+
+
+**Example:**
+
+```yaml
+zerops:
+ # hostname of your service
+ - setup: app
+ # ==== how to build your application ====
+ build: ...
+
+ # ==== how to run your application ====
+ run:
+ # REQUIRED. Your Ruby application start command
+ start: bundle exec puma -p 8080 -b tcp://0.0.0.0
+
+ # OPTIONAL. Define a health check with a HTTP GET request option.
+ # Configures the check on http://127.0.0.1:80/status
+ healthCheck:
+ httpGet:
+ port: 80
+ path: /status
+```
+
+#### exec
+
+Configures the health check to run a local command.
+Following attributes are available:
+
+| Parameter | Description |
+| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **command** | Defines a local command to be run.
+The command has access to the same [environment variables](/ruby/how-to/create#set-secret-environment-variables) as your Ruby application.
+A single string is required. If you need to run multiple commands create a shell script or, use a multiline format as in the example below. |
+
+**Example:**
+
+```yaml
+zerops:
+ # hostname of your service
+ - setup: app
+ # ==== how to build your application ====
+ build: ...
+
+ # ==== how to run your application ====
+ run:
+ # REQUIRED. Your Ruby application start command
+ start: bundle exec puma -p 8080 -b tcp://0.0.0.0
+
+ # OPTIONAL. Define a health check with a shell command.
+ healthCheck:
+ exec:
+ command: |
+ touch grass
+ rm -rf life
+ mv /outside/user /home/user
+```
+
+### crontab
+
+_OPTIONAL._ Defines cron jobs.
+
+Setup cron jobs in the following format:
+
+```yaml
+zerops:
+ # define hostname of your service
+ - setup: app
+
+ # ==== how to run your application ====
+ run:
+ crontab:
+ # REQUIRED. Sets the command to execute:
+ - command: ""
+ # REQUIRED. Sets the interval time to execute:
+ timing: "0 * * * *"
+```
+
+Read more about setting up [cron](/zerops-yaml/cron) in Zerops.
+
+## Deploy configuration
+
+### readiness check
+
+_OPTIONAL._ Defines a readiness check. Read more about how the [readiness check works](/ruby/how-to/deploy-process#readiness-checks) in Zerops.
+
+`readinessCheck` requires either one `httpGet` object or one `exec` object.
+
+#### httpGet
+
+Configures the readiness check to request a local URL using a http GET method.
+
+Following attributes are available:
+
+
+
+
+
Parameter
+
Description
+
+
+
+
+
port
+
Defines the port of the HTTP GET request.
+The readiness check will trigger a GET request on {'http://127.0.0.1:{port}/{path}'}
+
+
+
path
+
Defines the URL path of the HTTP GET request.
+The readiness check will trigger a GET request on {'http://127.0.0.1:{port}/{path}'}
+
+
+
host
+
Optional. The readiness check is triggered from inside of your runtime container so it always uses the localhost 127.0.0.1. If you need to add a host to the request header, specify it in the host attribute.
+
+
+
scheme
+
Optional. The readiness check is triggered from inside of your runtime container so no https is required.
+If your application requires a https request, set scheme: https
+
+
+
+
+**Example:**
+
+```yaml
+zerops:
+ # hostname of your service
+ - setup: app
+ # ==== how to build your application ====
+ build: ...
+
+ # ==== how to deploy your application ====
+ deploy:
+ # OPTIONAL. Define a readiness check with a HTTP GET request option.
+ # Configures the check on http://127.0.0.1:80/status
+ readinessCheck:
+ httpGet:
+ port: 80
+ path: /status
+
+ # ==== how to run your application ====
+ run: ...
+```
+
+Read more about how the [readiness check works](/ruby/how-to/deploy-process#readiness-checks) in Zerops.
+
+#### exec
+
+Configures the readiness check to run a local command.
+Following attributes are available:
+
+| Parameter | Description |
+| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **command** | Defines a local command to be run.
+The command has access to the same [environment variables](/ruby/how-to/create#set-secret-environment-variables) as your Ruby application.
+A single string is required. If you need to run multiple commands create a shell script or, use a multiline format as in the example below. |
+
+**Example:**
+
+```yaml
+zerops:
+ # hostname of your service
+ - setup: app
+ # ==== how to build your application ====
+ build: ...
+
+ # ==== how to deploy your application ====
+ deploy:
+ # OPTIONAL. Define a readiness check with a HTTP GET request option.
+ # Configures the check on http://127.0.0.1:80/status
+ readinessCheck:
+ exec:
+ command: |
+ touch grass
+ rm -rf life
+ mv /outside/user /home/user
+```
+
+Read more about how the [readiness check works](/ruby/how-to/deploy-process#readiness-checks) in Zerops.
+
+
+----------------------------------------
+
+# Ruby > How To > Build Process
+
+
+
+----------------------------------------
+
+# Ruby > How To > Controls
+
+
+
+----------------------------------------
+
+# Ruby > How To > Create
+
+
+Zerops provides a powerful Ruby runtime service with extensive build support. The Ruby runtime is highly scalable and customizable to suit your development and production needs. With just a few clicks or commands, you can have a production-ready Ruby environment up and running in no time.
+
+## Create a Ruby service using Zerops GUI
+
+First, set up a project in the Zerops GUI. Then go to the project dashboard page and choose **Add new service** in the left menu under the **Services** section. Pick **Ruby** from the list of runtime services, choose a version and operating system (Ubuntu or Alpine), set a hostname and confirm to create the service.
+
+### Choose a Ruby version
+
+Zerops supports the following Ruby versions:
+
+The service type is always specified together with the operating system, e.g. `ubuntu/ruby@4.0` or `alpine/ruby@4.0`. The `@latest` tag (e.g. `ubuntu/ruby@latest`) is an alias that points to Ruby 4.0.
+
+The Ruby runtime images include a full native-extension toolchain (gcc, make, libpq-dev, libyaml-dev, libffi-dev, etc.), so gems with C extensions build out of the box.
+
+:::info
+You can easily [upgrade](/ruby/how-to/upgrade) the major version at any time later.
+:::
+
+### Set a hostname
+
+Enter a unique service identifier like "app", "cache", "gui", etc. Duplicate services with the same name within the same project are not allowed.
+
+#### Limitations:
+
+- Maximum 25 characters
+- Must contain only lowercase ASCII letters (a-z) or numbers (0-9)
+
+:::caution
+The hostname is fixed after the service is created and cannot be changed later.
+:::
+
+### Set secret environment variables
+
+Add environment variables with sensitive data, such as passwords, tokens, salts, certificates, etc. These will be securely saved inside Zerops and added to your runtime service upon start.
+
+Setting secret environment variables is optional. You can always set them later in the Zerops GUI.
+
+Read more about the [different types of environment variables](/ruby/how-to/env-variables#service-env-variables) in Zerops.
+
+## Create a Ruby service using zCLI
+
+zCLI is the Zerops command-line tool. To create a new Ruby service via the command line, follow these steps:
+
+1. [Install & setup zCLI](/references/cli)
+2. [Create a project description file](/ruby/how-to/create#create-a-project-description-file)
+3. [Create a project with a Ruby and PostgreSQL service](#full-example)
+
+### Create a project description file
+
+Zerops uses a YAML format to describe the project infrastructure.
+
+#### Basic example:
+
+Create a directory called `my-project`. Inside the `my-project` directory, create a `description.yaml` file with the following content:
+```yaml
+# basic project data
+project:
+ # project name
+ name: my-project
+# array of project services
+services:
+ - # service name
+ hostname: app
+ # service type and version number in {os}/ruby@{version} format
+ type: ubuntu/ruby@4.0
+ # defines the minimum number of containers for horizontal autoscaling
+ minContainers: 1
+ # defines the maximum number of containers for horizontal autoscaling. Max value = 6.
+ maxContainers: 6
+ # optional: create env variables
+ envSecrets:
+ S3_ACCESS_KEY_ID: 'P8cX1vVVb'
+ S3_ACCESS_SECRET: 'ogFthuiLYki8XoL73opSCQ'
+```
+
+The yaml file describes your future project infrastructure. The project will contain one Ruby version 4.0 service with default [auto scaling](/ruby/how-to/scaling) configuration. Hostname will be set to "app", the internal port(s) the service listens on will be defined later in the [zerops.yaml](/ruby/how-to/build-pipeline#ports). Following secret env variables will be configured:
+
+```env
+S3_ACCESS_KEY_ID="P8cX1vVVb"
+S3_ACCESS_SECRET="ogFthuiLYki8XoL73opSCQ"
+```
+
+#### Full example:
+
+Create a directory my-project. Create an description.yaml file inside the my-project directory with following content:
+
+```yaml
+# basic project data
+project:
+ # project name
+ name: my-project
+ # optional: project description
+ description: A project with a Ruby and PostgreSQL database
+ # optional: project tags
+ tags:
+ - DEMO
+ - ZEROPS
+# array of project services
+services:
+ - # service name
+ hostname: app
+ # service type and version number in {os}/ruby@{version} format
+ type: ubuntu/ruby@4.0
+ # optional: vertical auto scaling customization
+ verticalAutoscaling:
+ cpuMode: DEDICATED
+ minCpu: 2
+ maxCpu: 5
+ minRam: 2
+ maxRam: 24
+ minDisk: 6
+ maxDisk: 50
+ startCpuCoreCount: 3
+ minFreeRamGB: 0.5
+ minFreeRamPercent: 20
+ # defines the minimum number of containers for horizontal autoscaling. Max value = 6.
+ minContainers: 2
+ # defines the maximum number of containers for horizontal autoscaling. Max value = 6.
+ maxContainers: 4
+ # optional: create env variables
+ envSecrets:
+ S3_ACCESS_KEY_ID: 'P8cX1vVVb'
+ S3_ACCESS_SECRET: 'ogFthuiLYki8XoL73opSCQ'
+ - # second service hostname
+ hostname: db
+ # service type and version number in postgresql@{version} format
+ type: postgresql@16
+ # mode of operation "HA"/"non_HA"
+ mode: NON_HA
+```
+
+The yaml file describes your future project infrastructure. The project will contain a Ruby service and a [PostgreSQL](/postgresql/overview) service.
+
+Ruby service with "app" hostname, the internal port(s) the service listens on will be defined later in the [zerops.yaml](/ruby/how-to/build-pipeline#ports). Ruby service will run on version 4.0 with a custom vertical and horizontal scaling. Following secret env variables will be configured:
+
+```env
+S3_ACCESS_KEY_ID="P8cX1vVVb"
+S3_ACCESS_SECRET="ogFthuiLYki8XoL73opSCQ"
+```
+
+The hostname of the PostgreSQL service will be set to "db". The [single container](/features/scaling#single-container-mode)(/features/scaling#deployment-modes-databases-and-shared-storage) mode will be chosen and the default auto [scaling configuration](/postgresql/how-to/scale#configure-scaling) will be set.
+
+#### Description of description.yaml parameters
+
+The `project:` section is required. Only one project can be defined.
+
+| Parameter | Description | Limitations |
+| --------------- | ------------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
+| **name** | The name of the new project. Duplicates are allowed. | |
+| **description** | **Optional.** Description of the new project. | Maximum 255 characters. |
+| **tags** | **Optional.** One or more string tags. Tags do not have a functional meaning, they only provide better orientation in projects. |
+| **tags** | **Optional.** One or more string tags. Tags do not have a functional meaning, they only provide better orientation in projects. |
+
+At least one service in `services:` section is required. You can create a project with multiple services. The example above contains Ruby and PostgreSQL services but you can create a `description.yaml` with your own combination of [services](/features/infrastructure).
+
+
+
+
+
Parameter
+
Description
+
+
+
+
+
+ hostname
+
+
+ The unique service identifier.
+
+
duplicate services with the same name in the same project are forbidden
+
maximum 25 characters
+
must contain only lowercase ASCII letters (a-z) or numbers (0-9)
+
+
+
+
+
+ type
+
+
+ Specifies the service type and version.
+
+ See what [Ruby service types](/references/import-yaml/type-list#runtime-services) are currently supported.
+
+
+
+
+ verticalAutoscaling
+
+
+ Optional. Defines [custom vertical auto scaling parameters](/ruby/how-to/create#set-auto-scaling-configuration).
+
+ All verticalAutoscaling attributes are optional. Not specified
+ attributes will be set to their default values.
+
+
+
+
+ - cpuMode
+
+
+ Optional. Accepts `SHARED`, `DEDICATED` values. Default is `SHARED`
+
+
+
+
+ - minCpu/maxCpu
+
+
+ Optional. Set the minCpu or maxCpu in CPU cores (integer).
+
+
+
+
+ - minRam/maxRam
+
+
+ Optional. Set the minRam or maxRam in GB (float).
+
+
+
+
+ - minDisk/maxDisk
+
+
+ Optional. Set the minDisk or maxDisk in GB (float).
+
+
+
+
+ minContainers
+
+
+ Optional. Default = 1. Defines the minimum number of containers
+ for [horizontal autoscaling](/ruby/how-to/create#horizontal-auto-scaling).
+
+ Limitations:
+
+ Current maximum value = 10.
+
+
+
+
+ maxContainers
+
+
+ Defines the maximum number of containers for [horizontal autoscaling](/ruby/how-to/create#horizontal-auto-scaling).
+
+ Limitations:
+
+ Current maximum value = 10.
+
+
+
+
+ envSecrets
+
+
+ Optional. Defines one or more secret env variables as a key value
+ map. See env variable [restrictions](/ruby/how-to/env-variables#env-variable-restrictions).
+
+
+
+
+
+### Create a project based on the description.yaml
+
+When you have your `description.yaml` ready, use the `zcli project project-import` command to create a new project and the service infrastructure.
+
+```sh
+Usage:
+ zcli project project-import importYamlPath [flags]
+
+Flags:
+ -h, --help Help for the project import command.
+ --org-id string If you have access to more than one organization, you must specify the org ID for which the
+ project is to be created.
+ --working-dir string Sets a custom working directory. Default working directory is the current directory. (default "./")
+```
+
+Zerops will create a project and one or more services based on the `description.yaml` content.
+
+Maximum size of the `description.yaml` file is 100 kB.
+
+You don't specify the project name in the `zcli project project-import` command, because the project name is defined in the `description.yaml`.
+
+If you have access to more than one client, you must specify the client ID for which the project is to be created. The `clientID` is located in the Zerops GUI under the client name on the project dashboard page.
+
+### Add Ruby service to an existing project
+
+#### Example:
+
+Create a directory `my-project` if it doesn't exist. Create an `import.yaml` file inside the `my-project` directory with following content:
+
+```yaml
+# basic project data
+project:
+ # project name
+ name: my-project
+# array of project services
+services:
+ - # service name
+ hostname: app
+ # service type and version number in {os}/ruby@{version} format
+ type: ubuntu/ruby@4.0
+ # defines the minimum number of containers for horizontal autoscaling
+ minContainers: 1
+ # defines the maximum number of containers for horizontal autoscaling. Max value = 6.
+ maxContainers: 6
+ # optional: create env variables
+ envSecrets:
+ S3_ACCESS_KEY_ID: 'P8cX1vVVb'
+ S3_ACCESS_SECRET: 'ogFthuiLYki8XoL73opSCQ'
+```
+
+The yaml file describes the list of one or more services that you want to add to your existing project. In the example above, one Ruby service version 4.0 with default [auto scaling](/ruby/how-to/scaling) configuration will be added to your project. Hostname of the new service will be set to `app`. Following secret env variables will be configured:
+
+```env
+S3_ACCESS_KEY_ID="P8cX1vVVb"
+S3_ACCESS_SECRET="ogFthuiLYki8XoL73opSCQ"
+```
+
+The content of the `services:` section of `import.yaml` is identical to the project description file. The `import.yaml` never contains the `project:` section because the project already exists.
+
+When you have your `import.yaml` ready, use the `zcli project service-import` command to add one or more services to your existing Zerops project.
+
+```sh
+Usage:
+ zcli project service-import importYamlPath [flags]
+
+Flags:
+ -h, --help Help for the project service import command.
+ -P, --project-id string If you have access to more than one project, you must specify the project ID for which the
+ command is to be executed.
+```
+
+zCLI commands are interactive, when you press enter after `zcli project service-import importYamlPath`, you will be given a list of your projects to choose from.
+
+Maximum size of the import.yaml file is 100 kB.
+
+
+----------------------------------------
+
+# Ruby > How To > Customize Runtime
+
+
+
+----------------------------------------
+
+# Ruby > How To > Deploy Process
+
+
+
+----------------------------------------
+
+# Ruby > How To > Env Variables
+
+
+
+----------------------------------------
+
+# Ruby > How To > Filebrowser
+
+
+
+----------------------------------------
+
+# Ruby > How To > Logs
+
-```bash
-git remote set-url origin https://github.com/your-username/your-repo.git
-git push -u origin main
-```
-Then move on to Step 6 to connect it to Zerops.
+----------------------------------------
-:::note Want to build something else instead?
-Skip the feedback app. Clone the repo, keep the `zerops.yml` as-is, and replace `src/app.ts` with your own routes. The DB connection, build pipeline, and env variables all stay the same.
-:::
+# Ruby > How To > Scaling
----
-## 6. Connect GitHub and auto-deploy
-1. Click into your **app** service
-2. Scroll down to **Pipelines & CI/CD settings**
-3. Click **GitHub** to connect your repo
-4. Select your repo and set **Trigger on** to **Push to Branch**, pick `main`
-5. In the **"Which `setup` from zerops.yml to use"** field, type `app`
-6. Click **Activate pipeline trigger**
+----------------------------------------
-That's it. Every push to main now builds and deploys automatically. Zero downtime, Zerops runs the new version alongside the old one, waits for a health check, then switches traffic over.
+# Ruby > How To > Shared Storage
-You can also trigger deploys manually with the Zerops CLI: `zcli push`.
----
-## You're live 🎉
+----------------------------------------
-Open your app URL. You should see the feedback button app: a big button, a live click count pulled from the database, all running on Zerops.
+# Ruby > How To > Trigger Pipeline
-Here's what's running:
-- A real app on production infrastructure
-- A managed PostgreSQL database with no setup, no connection strings, no SDK
-- Auto-deploy on every git push with zero downtime
-- All services on a private network, secure by default
-- Full Linux containers you can SSH into and install anything on
-None of the infrastructure needed manual configuration.
+----------------------------------------
----
+# Ruby > How To > Upgrade
-## If something breaks
-Got a 502 or an app crash on startup? Start here.
-**Check the runtime logs first.** Dashboard, click your app service, click the three-dot menu, then **Runtime log**. The error will be there, usually in the last few lines.
+----------------------------------------
-Two things come up most often on a first deploy:
+# Ruby > Overview
-:::tip Debug locally with VPN
-Install zcli first (see [CLI reference](/references/cli)), then run `zcli vpn up [your-project-id]` and your laptop joins the project's private network. You can connect to `db:5432` directly from your local machine using TablePlus, psql, or any database client. Disable SSL when connecting over VPN, security is handled by the tunnel itself. If `db` doesn't resolve, try `db.zerops` instead.
-:::
----
+[Ruby ↗](https://www.ruby-lang.org/en/) is a dynamic, object-oriented programming language with a focus on simplicity and programmer happiness.
-## What's next
+As said, there is no need for coding yet, we have created a [Github repository ↗](https://github.com/zerops-recipe-apps/ruby-hello-world-app), a **_recipe_**, containing a simple Ruby (Sinatra) web application served by Puma. The repo will be used as a source from which the app will be built.
-- **[SSH into your container](/references/networking/ssh)**: `zcli service shell [service-name]` for full Linux access
-- **[Custom domain](/references/networking/public-access)**: add your domain, SSL is automatic
-- **[Autoscaling](/features/scaling)**: set min and max CPU and RAM, Zerops scales within that range automatically
-- **[Add more services](/features/infrastructure)**: queues, search engines, object storage, just add them to your project
-- **[Try ZCP](/zcp/quickstart)**: Zerops' AI agent that can deploy, debug, and operate your project
+1. Log in/sign up to [Zerops GUI ↗](https://app.zerops.io)
-:::note Stuck?
-Jump into the [Zerops Discord](https://docs.zerops.io/discord). The community is active and the team is there.
+2. In the **Projects** box click on **Import a project** and paste in the following YAML config:
+
+```yaml
+project:
+ name: recipe-ruby
+ tags:
+ - zerops-recipe
+
+services:
+ - hostname: app
+ type: ubuntu/ruby@4.0
+ zeropsSetup: prod
+ enableSubdomainAccess: true
+ buildFromGit: https://github.com/zerops-recipe-apps/ruby-hello-world-app
+
+ - hostname: db
+ type: postgresql@16
+ mode: NON_HA
+ priority: 1
+```
+
+3. Click on **Import project** and wait until all pipelines have finished.
+
+**That's it, your application is now up and running! :star: Let's check it works:**
+
+1. A _subdomain_ should have been enabled and visible in the project's **IP addressed & Public Routing Overview** box. Its format should look similar to this `https://app-808-8080.prg1.zerops.app`.
+2. Click or the `subdomain` URL to open it in a browser and you should see
+
+```
+{"type":"ruby","greeting":"Hello from Zerops!","status":{"database":"OK"}}
+```
+
+:::tip
+Do you have any questions? Check the step-by-step tutorial, browse the documentation and join our **[Discord](https://discord.com/invite/WDvCZ54)** community to get help from our team and other members.
:::
+## How to start
+
+It doesn't matter whether it's your first curious introduction to Zerops, you have already mastered the basics and are looking for a tiny detail or inspiration. Below, choose a section that fits your needs:
+
+- [Care for details?](/ruby/how-to/create) — Dive in all Zerops has to offer for your Ruby application.
+- [Ruby recipes](https://github.com/zeropsio?q=ruby&type=all&language=&sort=) — Get inspired by already existing repositories, ready to be imported to Zerops.
+
+## Feature Highlights
+
+- [Create Ruby service](/ruby/how-to/create) — Start with creating a Ruby service using GUI or zCLI.
+- [Zerops.yaml](/ruby/how-to/build-pipeline#add-zeropsyaml-to-your-repository) — See a full example of zerops.yaml file to create your own app.
+- [Scaling configuration](/ruby/how-to/scaling) — Set up scaling of your Ruby application so that it runs smoothly while using only necessary resources.
+
+{" "}
+
+- [Customize build environment](/ruby/how-to/build-process#customize-build-environment)
+- [Customize runtime environment](/ruby/how-to/customize-runtime)
+
+## When in doubt, reach out
+
+Don't know how to start or got stuck during the process? You might not be the first one, visit the FAQ section to find out.
+
+In case you haven't found an answer (and also if you have), we and our community are looking forward to hearing from you on Discord.
+
+Have you build something that others might find useful? Don't hesitate to share your knowledge!
+
+- [FAQ](/ruby/faq) — Most common questions in one place.
+- [Discord](https://discord.com/invite/WDvCZ54) — Join our core team and Zerops community on Discord. Ask questions and share your tips with other members.
+
+## Popular Guides
+
+- [zCLI](/references/cli) — Get even more out of Zerops with the zCLI command line tool.
+- [Zerops VPN](/references/networking/vpn) — Connect to your services easily with Zerops VPN.
+
----------------------------------------
@@ -30129,60 +31575,60 @@ Have you build something that others might find useful? Don't hesitate to share
----------------------------------------
-# Shared Storage > How To > Backup
-
-
-Zerops provides automated data backup for Shared Storage services with full encryption and flexible management options.
-
-For general backup information including configuration, scheduling, and management options, see the [Zerops Backups](/features/backup) documentation, which covers:
-- Backup scheduling and retention policies
-- Tagging system and storage quotas
-- Manual backup creation and CLI tools
-- Security and encryption details
+# Shared Storage > How To > Connect
-## Shared Storage Backup Format
-Shared Storage backups are created using tar archival:
+This page covers how to connect an existing shared storage to runtime services, how to disconnect it, and how the mount behaves inside your containers.
-- **Format**: `.tar.gz` (archive of directory contents)
-- **Tooling**: `tar`
-- **Content**: Compresses the entire shared volume (`/mnt/`)
-- **Storage**: Encrypted and stored in isolated object storage
+## How the mount behaves
-### Restoring Backups
+Once a shared storage is connected to a runtime service, Zerops mounts it at `/mnt/` in the runtime containers. For example, `/mnt/teststorage` for a `teststorage` shared storage:
-To restore a Shared Storage backup: Download the backup file (`.tar.gz`) from the Zerops UI, extract the archive, and upload the contents back to your Shared Storage.
+- The content is shared among **all containers** of the connected runtime service; if you connect multiple runtime services, they all see the same content
+- Multiple storages can be mounted to a single service (e.g. `/mnt/files1`, `/mnt/files2`)
+- The mount point is owned by the `zerops` user and group (no sudo required)
+- Mounting removes any existing content of the mount directory
+- The mount is only available in runtime containers, not during build and prepare runtime phases
+- The mount process logs its standard output and error to the runtime service logs (tagged `zerops-mount-`); individual file operations are not logged
-## Best Practices
+:::note
+The `df` command may show incorrect or misleading information for shared storage mounts. Refer to the Zerops GUI for accurate storage metrics.
+:::
-For Shared Storage backups:
-- Regularly clean up unnecessary files from your Shared Storage to reduce backup size
-- Create manual backups with protected tags before major file operations or migrations
-- Consider adjusting your backup frequency for optimal storage usage
-- Test your restore process periodically to ensure data integrity
+## Connect in Zerops GUI
-For additional best practices and troubleshooting, refer to the [main backup documentation](/features/backup).
+When creating a new shared storage service, you can directly select which runtime services it should be connected to. See [Create shared storage](/shared-storage/how-to/create) for the creation process.
-----------------------------------------
+For existing storage, go to the shared storage service detail page and select **Shared storage connections**. Toggle ON any runtime services you wish to connect to this storage.
-# Shared Storage > How To > Connect
+To disconnect storage, toggle OFF the desired runtime service in the same place.
+:::note
+Connecting and disconnecting a shared storage is applied live; the runtime containers are not restarted.
+:::
-This page covers how to connect an existing shared storage to runtime services and how to disconnect services when needed.
+## Mount and unmount using zsc
-## In Zerops GUI
+You can also mount and unmount a shared storage from within a runtime container using the `zsc` command:
-### Connect a new shared storage
+```sh
+# Mount a shared storage
+zsc shared-storage mount
-When creating a new shared storage service, you can directly select which runtime services it should be connected to. See [Create Shared Storage](/shared-storage/how-to/create) for details about the creation process.
+# Unmount a shared storage
+zsc shared-storage unmount
-### Connect an existing shared storage
+# Wait until the storage mount is ready
+zsc shared-storage wait
+```
-For existing storage, go to the shared storage service detail page and select **Shared storage connections**. Toggle ON any runtime services you wish to connect to this storage.
+Run these commands from your runtime container via web terminal or SSH, or as part of your `zerops.yaml` commands. For more details, see the [zsc reference](/references/zsc#shared-storage).
-## Disconnect a shared storage in Zerops GUI
+:::caution
+The shared storage must be mounted (through the GUI connection toggle or via `zsc shared-storage mount`) before your application can access the `/mnt/` directory.
-To disconnect storage, access the shared storage service detail page, select **Shared storage connections**, and toggle OFF the desired runtime service.
+Before stopping or deleting a shared storage service, you must first disconnect (unmount) it from all runtime services.
+:::
----------------------------------------
@@ -30190,22 +31636,19 @@ To disconnect storage, access the shared storage service detail page, select **S
# Shared Storage > How To > Create
-Shared Storage provides persistent file storage that can be mounted as a POSIX-compatible filesystem to your runtime services. Built on [SeaweedFS ↗](https://github.com/seaweedfs/seaweedfs), it enables reliable data persistence and sharing across services in your infrastructure.
-
## Create Using Zerops GUI
-First, set up a project in Zerops GUI and add a runtime service. Then go to the project dashboard page and choose **Add new service** in the left menu in the **Services** block. Then add a new Shared Storage service:
+Go to your project dashboard and choose **Add new service** in the left menu in the **Services** block. Then add a new Shared Storage service:
[Video: /vids/services/shared-storage.webm](/vids/services/shared-storage.webm)
### Set a Hostname
-Enter a unique service identifier like "storage", "files" etc. Duplicate services with the same name in the same project are forbidden.
-
-#### Hostname Limitations:
+Enter a unique service identifier like "storage", "files" etc.
- Maximum 25 characters
- Must contain only lowercase ASCII letters (a-z) or numbers (0-9)
+- Must be unique within the project
:::note
The hostname is fixed after the service is created. It can't be changed later.
@@ -30213,41 +31656,52 @@ The hostname is fixed after the service is created. It can't be changed later.
### Connect to Services
-Select one or more project's runtime services in the Share with Services block:
-
-The new Shared Storage will be connected to the selected runtimes.
+Select one or more of the project's runtime services in the **Share with Services** block:
-:::note
-Runtime services can be connected and disconnected at any time even after the shared storage is created.
-:::
+Runtime services can be [connected and disconnected](/shared-storage/how-to/connect) at any time, even after the shared storage is created.
### Choose Deployment Mode
-Choose between **Highly Available** (recommended for production) or **Single Container** (suitable for development) deployment.
+Choose between **Highly Available** (recommended for production) and **Single Container** (suitable for development). See the [mode comparison](/shared-storage/overview#deployment-modes) and [technical details](/shared-storage/tech-details#deployment-modes).
:::warning
-The Shared Storage deployment mode is fixed after the service is created. It can't be changed later.
-
-See [Technical Details](/shared-storage/tech-details#deployment-modes) for more information about deployment modes.
+The deployment mode is fixed after the service is created. It can't be changed later.
:::
### Set Auto Scaling Configuration
-Configure vertical auto scaling parameters to control resource allocation and costs.
+Configure vertical auto scaling parameters to control resource allocation and costs:
:::note
-For detailed information about auto scaling capabilities and recommendations, see [Technical Details](/shared-storage/tech-details#auto-scaling-configuration).
+Raising the disk limits does not raise the usable data capacity, which is capped at 60GB. See [Storage Capacity](/shared-storage/tech-details#storage-capacity) and [Auto Scaling Configuration](/shared-storage/tech-details#auto-scaling-configuration).
:::
-## Create Using zCLI
+## Create Using Import YAML
-zCLI is the Zerops command-line tool. To create a new Shared Storage service via the command-line, follow these steps:
+The deployment mode is part of the service type:
-1. [Install & setup zCLI](/references/cli)
-2. Create a project description file
-3. Create a project with a runtime and a Shared Storage service
+```yaml title="zerops-import.yaml"
+services:
+ - # Shared storage service in highly available mode
+ hostname: teststorage
+ type: shared-storage:ha
+```
+
+Use `shared-storage:single` for the single container mode.
+
+:::note
+You can also pin the underlying engine version directly with `seaweedfs:ha@3` or `seaweedfs:single@3`.
+:::
+
+Import the file with the [zCLI](/references/cli):
+
+```sh
+zcli project service-import zerops-import.yaml
+```
-### Choose Your Runtime
+To mount the storage into a runtime service in the same import, add the `mount:` parameter to the runtime service. See the [import reference](/references/import#service-mount-shared-storage) for details and requirements.
+
+### Runtime-Specific Guides
export const languages = [
{ name: "Node.js", link: "/nodejs/how-to/shared-storage#create-nodejs-service-with-a-shared-storage-using-zcli" },
@@ -30258,29 +31712,32 @@ export const languages = [
{ name: "Rust", link: "/rust/how-to/shared-storage#create-rust-service-with-a-shared-storage-using-zcli" }
]
+
----------------------------------------
# Shared Storage > How To > Manage
-Zerops Shared Storage provides several web interfaces to manage, monitor, and troubleshoot your storage. These interfaces are accessible through the [Zerops VPN](/references/networking/vpn) and offer different capabilities for managing your data and monitoring system performance.
+Zerops Shared Storage provides several web interfaces to manage, monitor, and troubleshoot your storage, and automated encrypted backups of your data.
-## Access Web Interfaces
+## Web interfaces
+
+The web interfaces are accessible through the [Zerops VPN](/references/networking/vpn).
### Filer UI
* `http://.zerops:8888`
-The Filer UI provides a web-based interface for managing files and directories in your Shared Storage:
+A web-based interface for managing files and directories:
- Browse the directory structure and create new directories
-- Upload new files (up to 64MB) and download existing files
+- Upload new files (up to 64MB per file) and download existing files
- Rename and delete files and directories
### Master UI
-* `http://node-stable-1.db..zerops:9333`
-The Master UI provides system status and monitoring information:
+* `http://node-stable-1.db..zerops:9333`
+System status and monitoring information:
- View cluster topology
- Monitor volume servers
- Check system status and health
@@ -30290,8 +31747,7 @@ The Master UI provides system status and monitoring information:
* `http://node-stable-.db..zerops:8080/ui/index.html`
-The Volume UI allows you to monitor individual storage volumes:
-
+Monitoring of individual storage volumes:
- View volume status
- Check disk usage
- Monitor I/O operations
@@ -30301,90 +31757,138 @@ The Volume UI allows you to monitor individual storage volumes:
Several options are available to help you monitor your Shared Storage:
-### Runtime Service Logs
-* Navigate to your runtime service detail page → **Runtime Logs** section → filter using the tag `zerops-mount-`
+- **Runtime service logs**: navigate to your runtime service detail page, open the **Runtime Logs** section, and filter using the tag `zerops-mount-` (standard output and error of the mount process)
+- **Shared Storage logs**: access from the Shared Storage service detail page, **Runtime Logs** tab
+- **System and volume status**: monitor replication status, disk usage, and performance metrics through the Master UI and Volume UI
-### Shared Storage Logs
-* Access from the Shared Storage service detail page → **Runtime Logs** tab → browse or search for relevant information
+## Backups
-### System and Volume Status
-* Monitor replication status, disk usage, and performance metrics through the Master UI and Volume UI
+Zerops provides automated data backup for Shared Storage services with full encryption.
+For general backup information including configuration, scheduling, retention, tagging, quotas, and CLI tools, see the [Zerops Backups](/features/backup) documentation.
-----------------------------------------
+### Backup format
+
+Shared Storage backups are created using tar archival:
-# Shared Storage > How To > Use
+- **Format**: `.tar.gz` (archive of the directory contents)
+- **Content**: the entire shared volume (`/mnt/`)
+- **Storage**: encrypted and stored in isolated object storage
+### Restoring backups
-Once a Shared Storage is [connected](/shared-storage/how-to/connect) to a runtime service, Zerops will create a new folder `/mnt/[shared storage name]` in the runtime service's filesystem.
+To restore a Shared Storage backup:
-For example, `/mnt/teststorage` for a `teststorage` Shared Storage:
+1. Download the backup file (`.tar.gz`) from the Zerops UI.
+2. Transfer the archive into a runtime service that has the shared storage [connected](/shared-storage/how-to/connect), for example over the [Zerops VPN](/references/networking/vpn).
+3. Extract the archive into the mount directory from within the runtime container:
-:::note
-The content of this folder is shared among all containers of the connected runtime service.
+```sh
+tar -xzf backup.tar.gz -C /mnt/
+```
-If you connect multiple runtimes, the content of the folder will be shared among all containers of these services.
+:::note
+Do not restore by uploading files through the Filer UI. Uploads there are limited to 64MB per file, so extracting the archive directly into the mounted directory is the reliable path.
:::
-## Mount Points and Multiple Volumes
+### Best practices
-- Multiple storage volumes can be mounted to a single service (e.g., `/mnt/files1`, `/mnt/files2`, etc.)
-- Shared storage mount is only available in runtime containers, not during build and prepare runtime phases
-- All filesystem operations are automatically logged to runtime logs
+- Regularly clean up unnecessary files from your Shared Storage to reduce backup size
+- Create manual backups with protected tags before major file operations or migrations
+- Test your restore process periodically to ensure data integrity
-For technical details about mount behavior and filesystem capabilities, see the [Technical Details](/shared-storage/tech-details#mount-integration) page.
-## Use Cases
+----------------------------------------
-Shared Storage is ideal for:
+# Shared Storage > Overview
-- **Persistent filesystem-based databases**: SQLite, Prometheus DB, etc.
-- **Configuration sharing**: Deploy configurations once and share across multiple services
- - Example: Deploy Apache Airflow configurations and DAG files once and share with all worker nodes
-- **Alternative to object storage**: For applications that require filesystem semantics rather than object storage
-- **Application data**: Store and serve images, documents, and other assets
-## Performance Considerations
+Zerops **Shared Storage** is a managed disk volume that mounts as a POSIX-compatible filesystem into your runtime services. Files written by one container are immediately visible to every other container that mounts the same storage, whether they belong to the same service or to different services in the project.
-When using Shared Storage, keep in mind:
+Under the hood it runs [SeaweedFS ↗](https://github.com/seaweedfs/seaweedfs), a distributed filesystem optimized for high-volume storage. See [Technical details](/shared-storage/tech-details) for the architecture.
-- For write-heavy workloads, consider batching operations
-- Minimize operations with many small files for better performance
+## Quick start
-For more detailed information about performance constraints and limitations, see the [Technical Details](/shared-storage/tech-details#performance-considerations) page.
+Add a shared storage to your project and mount it into a runtime service with a `zerops-import.yaml`:
-## Troubleshooting
+```yaml title="zerops-import.yaml"
+services:
+ - hostname: teststorage
+ # type is shared-storage:{mode}
+ type: shared-storage:ha
-### Common Issues
+ - hostname: app
+ type: nodejs@22
+ buildFromGit: https://github.com/myorg/myapp
+ # mount the storage into /mnt/teststorage
+ mount:
+ - teststorage
+```
-- The `df` command may show incorrect or misleading information when used with shared storage mounts. Please refer to the Zerops GUI for accurate storage metrics.
+The storage appears in the runtime containers at `/mnt/teststorage`. See [Create shared storage](/shared-storage/how-to/create) for the GUI flow and configuration details, and [Connect & mount](/shared-storage/how-to/connect) for how the mount behaves.
-----------------------------------------
+## Use cases
-# Shared Storage > Overview
+Shared Storage is a good fit for:
+- **Configuration sharing**: deploy configuration once and share it across services (e.g. Apache Airflow configuration and DAG files shared with all worker nodes)
+- **Application data**: user uploads, images, documents, and other assets shared between containers (avoid huge trees of tiny files, see [performance considerations](/shared-storage/tech-details#performance-considerations))
+- **Fallback to object storage**: applications that strictly require filesystem semantics and cannot work with [object storage](/object-storage/overview)
-# Shared Storage
+:::caution Not suitable for databases
+Do **not** run databases on Shared Storage. This includes SQLite, Prometheus TSDB, and other filesystem-based databases.
-Zerops provides a fully managed and scaled **Shared Storage** service, which can be mounted to your runtime services. It offers:
-- Persistent file sharing between containers of the same service or different services
-- Standard filesystem operations through a POSIX-compatible interface
-- Built-in high-availability configuration
+- File locks are enforced **per mount only**: a lock held in one container does not prevent another container from writing to the same file, which leads to data corruption.
+- The underlying storage engine is an append-only chunk store: every modification writes new chunks, so the frequent small writes typical for databases cause heavy write amplification.
-## Documentation Sections
+Use [managed databases](/postgresql/overview) instead. For storing database *backups*, prefer [object storage](/object-storage/overview): it is cheaper and better suited for write-once files.
+:::
-- [Connect to Services](/shared-storage/how-to/connect) — Connect your Shared Storage to runtime services.
-- [Usage & Limitations](/shared-storage/how-to/use) — Learn how to save and access data, along with use cases and technical limits.
-- [Management & Access](/shared-storage/how-to/manage) — Access UI components and troubleshoot common issues.
-- [Backups](/shared-storage/how-to/backup) — Configure automatic backups of your data.
-- [Technical Details](/shared-storage/tech-details) — Explore the technical architecture and deployment options.
+## Deployment modes
-*Need help? Join our [Discord community](https://discord.gg/zeropsio).*
+Choose the reliability model when you create the service. It is set through the service `type` and is **fixed for the life of the service**.
-## Popular Guides
+
+
+
+
Mode
+
type
+
What it is
+
+
+
+
+
Highly Available
+
shared-storage:ha
+
Two containers with data and metadata replicated 1:1 across both, with automatic repair when a node fails. Recommended for production.
+
+
+
Single container
+
shared-storage:single
+
One container, so lower cost but no redundancy. All data is lost if the container fails. Good for development and non-critical data.
+
+
+
-- [zCLI](/references/cli) — Get even more out of Zerops with the zCLI command line tool.
-- [Zerops VPN](/references/networking/vpn) — Connect to your services easily with Zerops VPN.
+## Key limits
+
+- The storage holds at most **60GB of data**, regardless of the disk resource in autoscaling. See [storage capacity](/shared-storage/tech-details#storage-capacity).
+- File locks work **only within a single container's mount**, not across containers. See [filesystem capabilities](/shared-storage/tech-details#filesystem-capabilities).
+- The mount is available only in runtime containers, not during build and prepare phases.
+
+## Next steps
+
+- [Create shared storage](/shared-storage/how-to/create) — Create a service in the GUI or with zerops-import.yaml.
+- [Connect & mount](/shared-storage/how-to/connect) — Connect runtime services and understand how the mount behaves.
+- [Manage & backups](/shared-storage/how-to/manage) — Web interfaces, monitoring, backups, and restore.
+- [Technical details](/shared-storage/tech-details) — Architecture, storage engine behavior, limits, and autoscaling.
+
+## Need help?
+
+Stuck, or want to share what you built? Our core team and community are on Discord.
+
+- [Discord](https://discord.com/invite/WDvCZ54) — Join the Zerops community on Discord. Ask questions and share your tips.
+- [zCLI](/references/cli) — Get more out of Zerops with the command-line tool.
----------------------------------------
@@ -30392,25 +31896,21 @@ Zerops provides a fully managed and scaled **Shared Storage** service, which can
# Shared Storage > Tech Details
-Zerops Shared Storage is built on [SeaweedFS](https://github.com/seaweedfs/seaweedfs), a distributed filesystem optimized for high-volume storage with efficient retrieval.
+Zerops Shared Storage is built on [SeaweedFS ↗](https://github.com/seaweedfs/seaweedfs), a distributed filesystem optimized for high-volume storage with efficient retrieval.
## Architecture
Shared Storage consists of three main components:
-- **Master Server**: Manages metadata and coordinates volume servers
-- **Volume Servers**: Store the actual file data
-- **Filer**: Provides a POSIX-compatible interface for file operations
-An **automatic vacuum process** helps maintain optimal storage performance by reclaiming space from deleted files. This process is triggered when the size of deleted content exceeds 15% (reduced from the default 30%).
+- **Master Server**: manages metadata and coordinates volume servers
+- **Volume Servers**: store the actual file data
+- **Filer**: provides a POSIX-compatible interface for file operations
+
+### Storage Engine Behavior
-### Mount Integration
+SeaweedFS stores file data in append-only volumes. Files are split into chunks, and when a file is modified, new chunks are written while the old ones remain on disk until they are reclaimed. Frequent small modifications of existing files therefore cause significant write amplification, which is one of the reasons Shared Storage is [not suitable for databases](/shared-storage/overview#use-cases).
-When connected to a runtime service:
-- Storage is mounted at `/mnt/`
-- Mount point is owned by the `zerops` user and group (no sudo required)
-- All filesystem operations are logged to runtime logs (tagged as `zerops-mount-`)
-- Mounting will overwrite any existing content in the mount directory
-- Shared storage mount is only available in runtime containers, not during build and prepare runtime phases
+An **automatic vacuum process** reclaims the space occupied by deleted and superseded chunks. This process is triggered when the size of deleted content exceeds 15% (reduced from the SeaweedFS default of 30%).
## Deployment Modes
@@ -30420,8 +31920,8 @@ Zerops provides Shared Storage in two deployment modes:
Recommended for production environments where data reliability is critical.
-- **Architecture**: 2 volume servers with the master located on one of them
-- **Data Durability**: Data and filer metadata are replicated 1:1 across nodes
+- **Architecture**: Two containers, each running its own filer and volume server; the master server runs on the first container
+- **Data Durability**: Data and filer metadata are replicated 1:1 across both containers
- **Fault Tolerance**:
- If a node fails, an automatic repair process begins
- A new container replaces the failed one
@@ -30434,7 +31934,6 @@ Suitable for development environments or non-critical data storage.
- **Architecture**: Master, volume, and filer server all located on a single container
- **Data Durability**: All data is lost if the container fails
-- **Recommended For**: Development environments or temporary data storage
:::warning
The deployment mode is fixed after the service is created and cannot be changed later.
@@ -30446,45 +31945,51 @@ Shared Storage supports standard POSIX filesystem operations:
- Create, read, update, and delete files and directories
- Set permissions (with some limitations)
-- Standard file locking operations
+- File locking (per mount only, see the warning below)
- Hard and symbolic links
- Directory listing and traversal
-For a complete list of supported features, see the [SeaweedFS FUSE documentation](https://github.com/seaweedfs/seaweedfs/wiki/FUSE-Mount#supported-features).
+For a complete list of supported features, see the [SeaweedFS FUSE documentation ↗](https://github.com/seaweedfs/seaweedfs/wiki/FUSE-Mount#supported-features).
-## Resource Constraints
+:::caution File locks are per-mount only
+File locks (`flock`, POSIX locks) are enforced only within a single container's mount. A process in another container can freely write to a file you hold locked. Do not rely on file locking for coordination between containers, and do not run [lock-dependent workloads such as databases](/shared-storage/overview#use-cases) on Shared Storage.
+:::
-### Storage Limits
+## Storage Capacity
-- Maximum storage space: 60GB (can be increased via support request)
-- Maximum file size: Unlimited within the 60GB total storage constraint
-- Maximum upload size via Filer UI: 64MB
+:::caution The 60GB data limit is independent of disk scaling
+A Shared Storage service holds at most **60GB of data**, no matter how much disk the service scales to. The disk resource (up to 250GB) provides headroom for the vacuum process and filesystem metadata; raising the disk limits in autoscaling does **not** increase the amount of data you can store.
-### Memory Usage
+If you need more than 60GB, contact support.
+:::
-- Base memory consumption: ~60MB when idle
-- Peak memory usage: ~150MB under higher filesystem loads
-- Optimized for low RAM usage (may trade off some performance)
+- Maximum storage space: 60GB (can be increased via support request)
+- Maximum file size: no fixed limit through the mount (files are stored in chunks), up to the available storage space
+- Maximum upload size via Filer UI: 64MB per file
-### Performance Considerations
+## Performance Considerations
-- **Latency**: Higher latency compared to local storage due to network-based distributed architecture
-- **Write Performance**: For write-heavy workloads, consider batching operations
-- **Small Files**: Minimize operations with many small files for better performance
+- **Latency**: higher latency compared to local storage due to the network-based distributed architecture
+- **Write performance**: for write-heavy workloads, batch operations where possible
+- **Small files**: minimize operations with many small files for better performance
+- **Mount process memory**: the mount process runs inside each connected runtime container and consumes its memory, roughly 60MB when idle and around 150MB under higher filesystem loads; it is optimized for low RAM usage, which may trade off some performance
## Auto Scaling Configuration
-Zerops scales Shared Storage services automatically by raising or lowering the hardware resources of each database container.
+Zerops scales Shared Storage services automatically by raising or lowering the hardware resources of each container.
Vertical auto scaling has the following default configuration:
+:::note
+Disk size only gives the storage engine working space; the usable data capacity stays capped at 60GB. See [Storage Capacity](#storage-capacity).
+:::
+
For most cases, the default parameters will work without issues. If you need to limit the cost of the Shared Storage service, lower the maximal resources. Zerops will never scale above the selected maximums.
-When you are experiencing problems with insufficient Shared Storage performance or capacity, increase the minimal resources. Zerops will never scale below the selected minimums.
+When you are experiencing problems with insufficient Shared Storage performance, increase the minimal resources. Zerops will never scale below the selected minimums.
+
+You can change the auto scaling parameters at any time.
-:::note
-You can change the auto scaling parameters later.
-:::
----------------------------------------
@@ -32581,39 +34086,166 @@ Import configuration version:
Zerops offers Valkey in two deployment configurations to meet different availability requirements.
-### Non-HA Setup
+### Single Setup
- Single node deployment on port `6379` (non-TLS) and `6380` (TLS)
-- No backup mechanism beyond Zerops infrastructure reliability
-- Data persists unless the hardware node fails
- Suitable for development or non-critical workloads
+See [Persistence](#persistence) for how data is stored and recovered.
+
### HA (High Availability) Setup
-Our HA implementation uses a unique approach to ensure high availability while maintaining compatibility with all Redis clients:
-
-- 3-node configuration (1 master + 2 replicas)
-- Access ports:
- - `6379` - read/write operations (non-TLS, routed to master)
- - `6380` - read/write operations over TLS (routed to master)
- - `7000` - read-only operations (non-TLS)
- - `7001` - read-only operations over TLS
-- Implementation details:
- - All nodes are configured identically and listen on standard ports
- - First node in the cluster is designated as the master
- - On replica nodes, ports `6379`/`6380` traffic is forwarded to the master
- - Ports `7000`/`7001` are mapped locally to each node for direct replica access
- - When a master fails, a replica is promoted and routing is updated automatically
- - DNS entries are updated for seamless client connection
- - This implementation provides traffic forwarding to master (not natively supported by Valkey)
+The HA deployment is a 3-node cluster with automatic failover, fronted by an HAProxy load balancer on every node.
+
+- 3-node configuration: 1 primary + 2 replicas
+- Client-facing ports (available on every node):
+ - `6379` — read/write (non-TLS), routed to the current primary
+ - `6380` — read/write over TLS, routed to the current primary
+ - `7000` — read-only (non-TLS), load-balanced across replicas
+ - `7001` — read-only over TLS, load-balanced across replicas
+- Failover is handled by a built-in [Sentinel](https://valkey.io/topics/sentinel/) cluster. When the primary becomes unreachable, a replica is promoted automatically and HAProxy starts routing writes to it.
+- TLS is terminated at HAProxy.
+- Connect your application to the standard ports — the address never changes when the primary moves.
:::note
-Be aware that replica data may lag slightly behind the master due to asynchronous replication.
+Replica reads (ports `7000`/`7001`) can lag slightly behind the primary due to asynchronous replication.
:::
+**Failover client impact:** expect roughly 10–15 seconds of write unavailability while a new primary is elected and HAProxy reconverges. Read traffic on surviving replicas is unaffected.
+
:::tip Trusting the TLS certificate
-The certificates served on the TLS ports (`6380` and `7001`) are signed by the Zerops Certificate Authority. To verify them from outside Zerops, download and trust the [Zerops CA](/references/networking/zerops-ca) — e.g. `redis-cli --tls --cacert ./zerops-ca.pem -h -p 6380`.
+The certificates served on the TLS ports (`6380` and `7001`) are signed by the Zerops Certificate Authority. To verify them from outside Zerops, download and trust the [Zerops CA](/references/networking/zerops-ca) — e.g. `redis-cli --tls --cacert ./zerops-ca.pem -h -p 6380 -a `.
+:::
+
+## Connecting
+
+Zerops generates the connection details as environment variables on the Valkey service. Reference them from another service in the same project as `${_}` — for a service named `db`, the connection string is `${db_connectionString}`. The examples below assume the hostname `db`.
+
+| Variable | Example value | Notes |
+|---|---|---|
+| `hostname` | `db` | Service hostname; reachable as `db.zerops` inside the project |
+| `port` | `6379` | Plain (non-TLS) port |
+| `portTls` | `6380` | TLS port |
+| `password` | *(generated)* | Password for the `default` user (sensitive) |
+| `connectionString` | `redis://default:@db.zerops:6379` | Ready-to-use non-TLS URL |
+| `connectionTlsString` | `rediss://default:@db.zerops:6380` | Ready-to-use TLS URL |
+| `connectionTlsString` | `rediss://default:@db.zerops:6380` | Ready-to-use TLS URL |
+
+In **HA mode** four additional variables expose the read-only replica endpoints (load-balanced across replicas):
+
+| Variable | Example value | Notes |
+|---|---|---|
+| `portReplicas` | `7000` | Read-only plain port |
+| `portTlsReplicas` | `7001` | Read-only TLS port |
+| `connectionStringReplicas` | `redis://default:@db.zerops:7000` | Read-only non-TLS URL |
+| `connectionTlsStringReplicas` | `rediss://default:@db.zerops:7001` | Read-only TLS URL |
+| `connectionTlsStringReplicas` | `rediss://default:@db.zerops:7001` | Read-only TLS URL |
+
+The connection string format is `redis://default:@.zerops:` (or `rediss://` for TLS). The username is always `default`.
+
+:::note Authentication
+Valkey requires a password. It is generated automatically, exposed as the sensitive `${db_password}` variable, and already embedded in the `connectionString` variables above. Connect with it directly — e.g. `redis-cli -h db.zerops -p 6379 -a "$db_password"`.
+
+Services created **without** a `password` variable (older deployments) keep working without authentication and are unaffected. **All deployments created since this release require the password.**
+:::
+
+### Idle connection timeout
+
+Valkey closes connections that stay **idle for 5 minutes** (`timeout 300`). This is intentional on the managed instances — we avoid keeping infinite idle connections open. Older Valkey services ran with no timeout (`timeout 0`); if you connected before this change, your connections used to stay open indefinitely.
+
+"Idle" means **no commands sent on the connection** — the server resets the timer on every command, so a busy connection is never closed. The connections most likely to be affected are long-lived ones that sit waiting rather than sending commands, typically **pub/sub subscribers** and **blocking reads** (`BLPOP`, `XREAD`, …). Most clients reconnect automatically, so you may only see log lines such as `Redis subscriber socket closed; reconnecting if possible.` — but the reconnect churn can drop pub/sub messages published in the gap.
+
+To keep idle connections open, send an application-level **`PING` on an interval shorter than 300s**. A TCP keep-alive alone is **not** enough — keepalive packets live below the application layer and don't count as Valkey commands, so they don't reset the idle timer.
+
+Many clients have a built-in option for this. For example, [node-redis](https://github.com/redis/node-redis):
+
+```js
+const redisClient = createClient({
+ url: redisURL,
+ pingInterval: 10000, // send PING every 10s; keeps the connection under the 300s idle limit
+});
+```
+
+If your client has no equivalent option, run your own heartbeat on every long-lived connection:
+
+```js
+const heartbeat = setInterval(() => {
+ publisher.ping().catch(() => {});
+ subscriber.ping().catch(() => {});
+}, 60000); // any interval under 300s
+```
+
+For ordinary request/response traffic, a [connection pool](https://valkey.io/topics/clients/) that recycles connections handles this transparently.
+
+## Persistence
+
+Valkey persists data to disk with **AOF (append-only file)**, so the dataset survives restarts and is rebuilt automatically on startup.
+
+- **AOF is enabled** (`appendonly yes`) and synced to disk **every second** (`appendfsync everysec`). After an unclean crash you lose at most ~1 second of the most recent writes.
+- **RDB snapshots are disabled** (`save ""`) — durability relies on AOF, not periodic snapshots.
+
+**Durability by mode:**
+- **Single:** the AOF lives on the node's local disk. Data survives service restarts but is lost if the underlying hardware node fails and no backup exists.
+- **HA:** writes are additionally replicated to two replicas, so the dataset survives the loss of any single node via automatic failover.
+
+:::note Backups
+Platform-managed encrypted backups are available for both Single and HA setups. They are **disabled by default** — enable them on the service if you need point-in-time recovery beyond AOF and replication.
+:::
+
+## Memory and Autoscaling
+
+You don't set `maxmemory` directly. Zerops sizes it at **80% of the container's available RAM** — precisely 80% of the *smaller* of your configured maximum RAM and the cgroup-allocated RAM. It is re-evaluated and adjusted automatically about every 30 seconds, so `maxmemory` tracks the container as it scales vertically. The remaining 20% covers Valkey's internal overhead (fork on AOF rewrite / replica sync, fragmentation) and the OS.
+
+:::warning Keep minimum free RAM above 20% when customizing autoscaling
+If you edit the autoscaling configuration, keep the **minimum free RAM above 20%**. Zerops caps `maxmemory` at 80% of available RAM, so the dataset alone can never push free RAM below 20%. If your minimum free RAM threshold is at or below 20%, the scale-up trigger may **never fire at all** — free RAM never crosses it, so the service stays stuck at its current size and starts evicting keys (or rejecting writes under `noeviction`) instead of scaling up. Setting the threshold above 20% lets the dataset's growth toward the 80% cap cross the trigger, so the service scales up in time and keeps headroom for the fork during an AOF rewrite or replica sync. The built-in profiles all keep this threshold above 20%.
+:::
+
+:::note Check the logs for OOM events
+Watch the service's runtime logs for out-of-memory events — typically the kernel OOM-killer terminating and restarting Valkey when a fork during an AOF rewrite or replica sync briefly inflates memory. Recurring OOMs mean the reserved headroom isn't enough for your workload's peaks. Raise the **minimum free RAM** (more headroom) or the **minimum RAM** (a higher floor) until they stop.
+:::
+
+## Tunable Parameters
+
+The `maxmemory-policy` Valkey setting is exposed as an **autoscaling profile override**. In the GUI, open the service's **Automatic scaling configuration**, click **Adjust scaling** and set them under **Overrides**. Zerops applies the change live — **no service restart**, no client reconnect. In HA mode the change is rolled out to every node.
+
+To set the parameters at creation time, use `profileOverrides` in your import YAML (a `profile` must be selected to use overrides — available profiles are `hobby`, `staging` and `production`):
+
+```yaml
+services:
+ - hostname: redis
+ type: valkey:ha@7.2
+ profile: staging
+ profileOverrides:
+ maxmemory-policy: noeviction
+```
+
+:::note Migrating from environment variables
+Services created before profile overrides existed configure this setting via the `VALKEY_MAXMEMORY_POLICY` environment variable. It keeps working, but once a profile override is set it takes precedence over the environment variable.
:::
+### `maxmemory-policy`
+
+Default: `allkeys-lru`. Controls what Valkey does when the dataset reaches `maxmemory`.
+
+| Value | Behavior | When to use |
+|---|---|---|
+| `noeviction` | Reject writes with an OOM error | Datasets where every key must be preserved (session storage without TTL, job queues). Requires careful capacity planning. |
+| `allkeys-lru` | Evict least-recently-used keys | General-purpose caching — the safe default |
+| `allkeys-lfu` | Evict least-frequently-used keys | Hot/cold workloads where access frequency matters more than recency |
+| `allkeys-random` | Evict random keys | Uniform access patterns (rare) |
+| `volatile-lru` | Evict LRU keys *with a TTL set* | Mixed workloads: persistent keys without TTL are protected, cache keys with TTL are evictable |
+| `volatile-lfu` | Evict LFU keys with a TTL | Same as `volatile-lru`, frequency-based |
+| `volatile-random` | Evict random keys with a TTL | Rarely appropriate |
+| `volatile-ttl` | Evict keys with the shortest remaining TTL | When TTL reflects priority |
+| `volatile-ttl` | Evict keys with the shortest remaining TTL | When TTL reflects priority |
+
+:::warning `noeviction` and memory pressure
+With `noeviction`, Valkey cannot free memory on its own — once the dataset reaches `maxmemory`, writes fail with OOM errors until the service scales up or keys are deleted. Make sure your autoscaling limits (maximum RAM) leave enough room for the dataset's growth.
+:::
+
+## Metrics
+
+Prometheus-compatible metrics are exported by default for scraping, on the port given by the `ZEROPS_PROMETHEUS_PORT` variable (`db:9121`).
+
## Learn More
- [Official Valkey Documentation](https://valkey.io/docs) - Comprehensive guide to Valkey features
@@ -32686,11 +34318,11 @@ repeated failure"]
reachability -->|needs human| blocker
behavior -->|needs human| blocker
- classDef user fill:#f7fbff,stroke:#2d72d9,stroke-width:1.5px,color:#172033;
- classDef zcpbox fill:#eef7f1,stroke:#32845a,stroke-width:1.5px,color:#172033;
- classDef work fill:#fffaf0,stroke:#c47f17,stroke-width:1.5px,color:#172033;
- classDef done fill:#f5f3ff,stroke:#7157d9,stroke-width:1.5px,color:#172033;
- classDef stop fill:#fff1f2,stroke:#d33f49,stroke-width:1.5px,color:#172033;
+ classDef user stroke:#2d72d9,stroke-width:1.5px;
+ classDef zcpbox stroke:#32845a,stroke-width:1.5px;
+ classDef work stroke:#c47f17,stroke-width:1.5px;
+ classDef done stroke:#7157d9,stroke-width:1.5px;
+ classDef stop stroke:#d33f49,stroke-width:1.5px;
class intent user;
class state,scope zcpbox;
@@ -33087,7 +34719,7 @@ To start, add remote setup in Zerops or initialize local setup beside your edito
**Zerops token.** The MCP server connects through `ZCP_API_KEY`, a Zerops token limited to one project. Remote setup gets it from the platform; local setup reads it from `.mcp.json`. Token details live in [Tokens and credentials](/zcp/security/tokens-and-project-access).
-**Workspace freedom.** The `zcp@1` service is still a normal Zerops service. You can install another agent CLI, add private MCP servers or helper tools, edit `CLAUDE.md`, add team dotfiles, and adapt the workspace. Details live in [What remote workspace gives you](/zcp/setup/hosted-workspace#make-customization-persistent).
+**Workspace freedom.** The `zcp@1` service is still a normal Zerops service. You can install another agent CLI, add private MCP servers or helper tools, edit `CLAUDE.md`, add team dotfiles, and adapt the workspace. Details live in [What remote workspace gives you](/zcp/setup/hosted-workspace).
:::caution Production boundary
Use this setup for development or staging work. Production should stay in a separate Zerops project and receive released work through your CI or release process; see [Promote to production](/zcp/workflows/promote-to-production) for the practical flow and [Production boundary](/zcp/security/production-policy) for the policy.
diff --git a/apps/docs/static/llms.txt b/apps/docs/static/llms.txt
index f11f42c6..058e6f5f 100644
--- a/apps/docs/static/llms.txt
+++ b/apps/docs/static/llms.txt
@@ -4,8 +4,11 @@
## Docs
-- [Full Docs](https://docs.zerops.io/llms-full.txt) Full documentation of Zerops. (without examples)
-- [Tiny Docs](https://docs.zerops.io/llms-small.txt): Tiny documentation of Zerops. (includes only description of core)
+- [Full Docs](https://docs.zerops.io/llms-full.txt): The complete documentation as a single file (349 pages, no images).
+- [Core Docs](https://docs.zerops.io/llms-small.txt): The same, trimmed to the core platform — excludes API/CLI references, company and help pages.
+
+Every page below is also reachable in its rendered form by dropping the `.md`
+suffix, and any docs.zerops.io URL can be turned into clean markdown by adding it.
## Optional
@@ -140,6 +143,7 @@
- [Guides > Deployment Lifecycle](https://docs.zerops.io/guides/deployment-lifecycle.md)
- [Guides > Environment Variables](https://docs.zerops.io/guides/environment-variables.md)
- [Guides > Firewall](https://docs.zerops.io/guides/firewall.md)
+- [Guides > Local Development](https://docs.zerops.io/guides/local-development.md)
- [Guides > Logging](https://docs.zerops.io/guides/logging.md)
- [Guides > Metrics](https://docs.zerops.io/guides/metrics.md)
- [Guides > Networking](https://docs.zerops.io/guides/networking.md)
@@ -147,7 +151,9 @@
- [Guides > Php Tuning](https://docs.zerops.io/guides/php-tuning.md)
- [Guides > Production Checklist](https://docs.zerops.io/guides/production-checklist.md)
- [Guides > Public Access](https://docs.zerops.io/guides/public-access.md)
+- [Guides > Readiness Health Checks](https://docs.zerops.io/guides/readiness-health-checks.md)
- [Guides > Scaling](https://docs.zerops.io/guides/scaling.md)
+- [Guides > Shared Storage Integration](https://docs.zerops.io/guides/shared-storage-integration.md)
- [Guides > Smtp](https://docs.zerops.io/guides/smtp.md)
- [Guides > Verify Web Agent Protocol](https://docs.zerops.io/guides/verify-web-agent-protocol.md)
- [Guides > Vpn](https://docs.zerops.io/guides/vpn.md)
@@ -239,12 +245,9 @@
- [Php > How To > Trigger Pipeline](https://docs.zerops.io/php/how-to/trigger-pipeline.md)
- [Php > How To > Upgrade](https://docs.zerops.io/php/how-to/upgrade.md)
- [Php > Overview](https://docs.zerops.io/php/overview.md)
-- [Postgresql > Faq](https://docs.zerops.io/postgresql/faq.md)
-- [Postgresql > How To > Backup](https://docs.zerops.io/postgresql/how-to/backup.md)
- [Postgresql > How To > Connect](https://docs.zerops.io/postgresql/how-to/connect.md)
- [Postgresql > How To > Control](https://docs.zerops.io/postgresql/how-to/control.md)
- [Postgresql > How To > Create](https://docs.zerops.io/postgresql/how-to/create.md)
-- [Postgresql > How To > Export Import Data](https://docs.zerops.io/postgresql/how-to/export-import-data.md)
- [Postgresql > How To > Manage](https://docs.zerops.io/postgresql/how-to/manage.md)
- [Postgresql > How To > Scale](https://docs.zerops.io/postgresql/how-to/scale.md)
- [Postgresql > Overview](https://docs.zerops.io/postgresql/overview.md)
@@ -263,7 +266,8 @@
- [Python > How To > Upgrade](https://docs.zerops.io/python/how-to/upgrade.md)
- [Python > Overview](https://docs.zerops.io/python/overview.md)
- [Qdrant > Overview](https://docs.zerops.io/qdrant/overview.md)
-- [Quickstart](https://docs.zerops.io/quickstart.md)
+- [Quickstart > Quickstart Going Further](https://docs.zerops.io/quickstart/quickstart-going-further.md)
+- [Quickstart > Quickstart](https://docs.zerops.io/quickstart/quickstart.md)
- [References > Api](https://docs.zerops.io/references/api.md)
- [References > Cli](https://docs.zerops.io/references/cli.md)
- [References > Github Integration](https://docs.zerops.io/references/github-integration.md)
@@ -287,6 +291,20 @@
- [References > Zcli > Commands](https://docs.zerops.io/references/zcli/commands.md)
- [References > Zcli > Configuration](https://docs.zerops.io/references/zcli/configuration.md)
- [References > Zsc](https://docs.zerops.io/references/zsc.md)
+- [Ruby > How To > Build Pipeline](https://docs.zerops.io/ruby/how-to/build-pipeline.md)
+- [Ruby > How To > Build Process](https://docs.zerops.io/ruby/how-to/build-process.md)
+- [Ruby > How To > Controls](https://docs.zerops.io/ruby/how-to/controls.md)
+- [Ruby > How To > Create](https://docs.zerops.io/ruby/how-to/create.md)
+- [Ruby > How To > Customize Runtime](https://docs.zerops.io/ruby/how-to/customize-runtime.md)
+- [Ruby > How To > Deploy Process](https://docs.zerops.io/ruby/how-to/deploy-process.md)
+- [Ruby > How To > Env Variables](https://docs.zerops.io/ruby/how-to/env-variables.md)
+- [Ruby > How To > Filebrowser](https://docs.zerops.io/ruby/how-to/filebrowser.md)
+- [Ruby > How To > Logs](https://docs.zerops.io/ruby/how-to/logs.md)
+- [Ruby > How To > Scaling](https://docs.zerops.io/ruby/how-to/scaling.md)
+- [Ruby > How To > Shared Storage](https://docs.zerops.io/ruby/how-to/shared-storage.md)
+- [Ruby > How To > Trigger Pipeline](https://docs.zerops.io/ruby/how-to/trigger-pipeline.md)
+- [Ruby > How To > Upgrade](https://docs.zerops.io/ruby/how-to/upgrade.md)
+- [Ruby > Overview](https://docs.zerops.io/ruby/overview.md)
- [Rust > How To > Build Pipeline](https://docs.zerops.io/rust/how-to/build-pipeline.md)
- [Rust > How To > Build Process](https://docs.zerops.io/rust/how-to/build-process.md)
- [Rust > How To > Controls](https://docs.zerops.io/rust/how-to/controls.md)
@@ -301,11 +319,9 @@
- [Rust > How To > Trigger Pipeline](https://docs.zerops.io/rust/how-to/trigger-pipeline.md)
- [Rust > How To > Upgrade](https://docs.zerops.io/rust/how-to/upgrade.md)
- [Rust > Overview](https://docs.zerops.io/rust/overview.md)
-- [Shared Storage > How To > Backup](https://docs.zerops.io/shared-storage/how-to/backup.md)
- [Shared Storage > How To > Connect](https://docs.zerops.io/shared-storage/how-to/connect.md)
- [Shared Storage > How To > Create](https://docs.zerops.io/shared-storage/how-to/create.md)
- [Shared Storage > How To > Manage](https://docs.zerops.io/shared-storage/how-to/manage.md)
-- [Shared Storage > How To > Use](https://docs.zerops.io/shared-storage/how-to/use.md)
- [Shared Storage > Overview](https://docs.zerops.io/shared-storage/overview.md)
- [Shared Storage > Tech Details](https://docs.zerops.io/shared-storage/tech-details.md)
- [Static > Overview](https://docs.zerops.io/static/overview.md)
diff --git a/apps/docs/static/robots.txt b/apps/docs/static/robots.txt
index 26e44dc7..dd42986c 100644
--- a/apps/docs/static/robots.txt
+++ b/apps/docs/static/robots.txt
@@ -2,3 +2,11 @@ User-agent: *
Allow: /
Sitemap: https://docs.zerops.io/sitemap-docs.xml
+
+# Machine-readable documentation for LLMs and coding agents.
+# Every docs page is also available as clean markdown by appending .md
+# to its URL, e.g. https://docs.zerops.io/references/cli.md
+#
+# https://docs.zerops.io/llms.txt index + link to every page as .md
+# https://docs.zerops.io/llms-full.txt entire documentation, single file
+# https://docs.zerops.io/llms-small.txt core documentation only
diff --git a/zerops-llm-script.ts b/zerops-llm-script.ts
index 2c872438..e804c15c 100644
--- a/zerops-llm-script.ts
+++ b/zerops-llm-script.ts
@@ -197,8 +197,11 @@ async function generateLLMDocs() {
'',
'## Docs',
'',
- '- [Full Docs](https://docs.zerops.io/llms-full.txt) Full documentation of Zerops. (without examples)',
- '- [Tiny Docs](https://docs.zerops.io/llms-small.txt): Tiny documentation of Zerops. (includes only description of core)',
+ `- [Full Docs](https://docs.zerops.io/llms-full.txt): The complete documentation as a single file (${optionalFiles.length} pages, no images).`,
+ '- [Core Docs](https://docs.zerops.io/llms-small.txt): The same, trimmed to the core platform — excludes API/CLI references, company and help pages.',
+ '',
+ 'Every page below is also reachable in its rendered form by dropping the `.md`',
+ 'suffix, and any docs.zerops.io URL can be turned into clean markdown by adding it.',
'',
'## Optional',
'',
diff --git a/zerops.yml b/zerops.yml
index e140d716..d7f0b477 100644
--- a/zerops.yml
+++ b/zerops.yml
@@ -19,6 +19,27 @@ zerops:
initCommands:
- sudo sed -i 's/^\(\s*text\/plain\s\+\)txt;$/\1txt md;/' /etc/nginx/mime.types
- printf 'charset utf-8;\ncharset_types text/plain;\n' | sudo tee /etc/nginx/conf.d/charset.conf
+ # Advertise the markdown twin of every docs page as an RFC 8288 Link
+ # header, so agents that fetch over plain HTTP (or HEAD) discover it
+ # without parsing the HTML . Mirrors the
+ # emitted by src/theme/DocItem/Layout. The regex only matches
+ # extensionless paths, so assets and the .md files themselves are
+ # skipped; an empty value makes nginx omit the header entirely.
+ - printf 'map $request_uri $zerops_md_alternate {\n default "";\n /discord "";\n "~^(/[^.?#]*[^./?#])$" "<$1.md>; rel=alternate; type=text/markdown";\n}\nadd_header Link $zerops_md_alternate always;\n' | sudo tee /etc/nginx/conf.d/md-alternate.conf
+ # The static base falls back to /index.html for anything unmatched,
+ # which makes a bogus /foo.md answer 200 with the 260kB HTML shell --
+ # agents read that as a successful fetch. Narrow the .md location to a
+ # real 404. Self-reverting: if the config does not look the way we
+ # expect, the substitution simply no-ops, and a failed `nginx -t`
+ # restores the original file.
+ - |
+ F=$(sudo grep -rl 'location[^{]*\.md' /etc/nginx/ 2>/dev/null | head -1)
+ if [ -n "$F" ]; then
+ sudo cp "$F" /tmp/nginx-md.bak
+ sudo perl -0777 -i -pe 's!(location[^{\n]*\.md[^{\n]*\{[^{}]*?)try_files[^;]*;!$1try_files \$uri =404;!' "$F"
+ sudo nginx -t || sudo cp /tmp/nginx-md.bak "$F"
+ fi
+ true
- sudo service nginx reload
routing:
headers: