From bcb810b8d65dbb47d87d559344dfbebf62085ef5 Mon Sep 17 00:00:00 2001 From: Karlos <73657806+krls2020@users.noreply.github.com> Date: Tue, 2 Jun 2026 19:03:54 +0200 Subject: [PATCH] chore(guides): update 21 guides --- apps/docs/content/guides/backup.mdx | 20 +-- apps/docs/content/guides/cdn.mdx | 9 +- apps/docs/content/guides/choose-cache.mdx | 9 +- apps/docs/content/guides/choose-database.mdx | 16 +-- apps/docs/content/guides/choose-queue.mdx | 1 - .../content/guides/choose-runtime-base.mdx | 15 +- apps/docs/content/guides/choose-search.mdx | 6 +- apps/docs/content/guides/ci-cd.mdx | 10 +- apps/docs/content/guides/cloudflare.mdx | 8 +- .../content/guides/deployment-lifecycle.mdx | 10 +- .../content/guides/environment-variables.mdx | 14 +- .../docs/content/guides/local-development.mdx | 132 ++++++++++++++++++ apps/docs/content/guides/logging.mdx | 7 +- apps/docs/content/guides/metrics.mdx | 17 +-- apps/docs/content/guides/php-tuning.mdx | 32 +++-- .../content/guides/production-checklist.mdx | 32 +++-- apps/docs/content/guides/public-access.mdx | 4 +- apps/docs/content/guides/scaling.mdx | 6 +- apps/docs/content/guides/smtp.mdx | 26 +++- apps/docs/content/guides/vpn.mdx | 4 +- .../content/guides/zerops-yaml-advanced.mdx | 8 +- 21 files changed, 276 insertions(+), 110 deletions(-) create mode 100644 apps/docs/content/guides/local-development.mdx diff --git a/apps/docs/content/guides/backup.mdx b/apps/docs/content/guides/backup.mdx index fca47ed3..58909177 100644 --- a/apps/docs/content/guides/backup.mdx +++ b/apps/docs/content/guides/backup.mdx @@ -8,6 +8,8 @@ Zerops auto-backs up databases and storage daily (00:00-01:00 UTC) with X25519 e ## Supported Services MariaDB, PostgreSQL, Qdrant, Elasticsearch, NATS, Meilisearch, Shared Storage. +**ClickHouse**: not on the standard auto-backup path — back it up with the native `BACKUP ALL ...` SQL command (super user), stored as `.tar.gz`. + **Not supported**: Runtimes, Object Storage (use S3 lifecycle policies), Valkey/KeyDB (in-memory). ## Schedule Options @@ -40,15 +42,15 @@ End-to-end with X25519 per-project keys. Decrypted only on download. 7 days after service or project deletion before backups are permanently removed. ## Backup Formats by Service -| Service | Format | -|---------|--------| -| PostgreSQL | pg_dump | -| MariaDB | mysqldump | -| Elasticsearch | elasticdump (.gz) | -| Meilisearch | .dump | -| Qdrant | .snapshot | -| NATS | .tar.gz | -| Shared Storage | filesystem archive | +| Service | Tool → Format | +|---------|---------------| +| PostgreSQL | `pg_dump` → `.zip` (per-schema custom-format `-Fc` dumps) | +| MariaDB | `mariabackup` → `.xb.gz` (xbstream + gzip) — **not** `mysqldump` (that is the manual-export tool, a different operation) | +| Elasticsearch | elasticdump → `.gz` | +| Meilisearch | `.dump` | +| Qdrant | `.snapshot` | +| NATS | `.tar.gz` | +| Shared Storage | tar → `.tar.gz` | ## Gotchas 1. **Object Storage has no Zerops backup**: Use S3 lifecycle policies or external backup diff --git a/apps/docs/content/guides/cdn.mdx b/apps/docs/content/guides/cdn.mdx index 3b9d2cb8..8000e880 100644 --- a/apps/docs/content/guides/cdn.mdx +++ b/apps/docs/content/guides/cdn.mdx @@ -48,13 +48,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/*` +4. **CDN URLs are project-scoped env vars**: `${storageCdnUrl}`, `${staticCdnUrl}`, `${apiCdnUrl}` are referenced directly with no hostname prefix (unlike service vars like `${storage_apiUrl}`) diff --git a/apps/docs/content/guides/choose-cache.mdx b/apps/docs/content/guides/choose-cache.mdx index 1debb17d..ec325bb8 100644 --- a/apps/docs/content/guides/choose-cache.mdx +++ b/apps/docs/content/guides/choose-cache.mdx @@ -9,15 +9,16 @@ description: "**Use Valkey.** KeyDB development has stalled and is effectively d | Need | Choice | Why | |------|--------|-----| -| **Any caching need** | **Valkey** (default) | Active development, full HA, Redis-compatible | +| **Any caching need** | **Valkey** (default) | Active development, optional HA, Redis-compatible | | Legacy KeyDB apps | KeyDB | Only if migrating existing KeyDB deployment | ## Valkey (Default Choice) - Redis-compatible drop-in replacement -- HA: 3 nodes (1 master + 2 replicas) with automatic failover -- Ports: 6379 (non-TLS), 6380 (TLS), 7000 (read replica non-TLS), 7001 (read replica TLS) -- Connection: `redis://${user}:${password}@${hostname}:6379` +- Version: use `valkey@7.2` — the platform rejects `valkey@8` at import (`serviceStackTypeNotFound`) +- Mode: default **NON_HA** (single node), immutable after creation. HA (3 nodes — 1 master + 2 replicas, automatic failover) is opt-in via `mode: HA` +- Ports: 6379 (non-TLS), 6380 (TLS); **HA-only**: 7000 (read replica non-TLS), 7001 (read replica TLS) +- Connection: `redis://${hostname}:6379` — Valkey runs **unauthenticated** on Zerops; there are NO `user`/`password` env vars. Do **not** template `${cache_user}`/`${cache_password}` — they don't exist and produce a broken DSN - HA detail: Ports 6379/6380 on replicas forward traffic to current master (Zerops-specific, not native Valkey) ## KeyDB (Deprecated) diff --git a/apps/docs/content/guides/choose-database.mdx b/apps/docs/content/guides/choose-database.mdx index fd6231e4..63852a5b 100644 --- a/apps/docs/content/guides/choose-database.mdx +++ b/apps/docs/content/guides/choose-database.mdx @@ -3,35 +3,35 @@ title: "Choosing a Database on Zerops" description: "**Use PostgreSQL** for everything unless you have a specific reason not to. It's the best-supported database on Zerops with full HA, read replicas, and pgBouncer." --- -**Use PostgreSQL** for everything unless you have a specific reason not to. It's the best-supported database on Zerops with full HA, read replicas, and pgBouncer. +**Use PostgreSQL** for everything unless you have a specific reason not to — the best-supported database on Zerops, with optional HA, read replicas, and pgBouncer. Default mode is **NON_HA** (single node); HA is opt-in and immutable after creation. ## Decision Matrix | Need | Choice | Why | |------|--------|-----| -| **General-purpose** | **PostgreSQL** (default) | Full HA, read replicas, pgBouncer, best Zerops support | +| **General-purpose** | **PostgreSQL** (default) | Optional HA, read replicas, pgBouncer, best Zerops support | | MySQL compatibility | MariaDB | MaxScale routing, async replication | | Analytics / OLAP | ClickHouse | Columnar storage, ReplicatedMergeTree, 4 protocol ports | ## PostgreSQL (Default Choice) -- HA: 3 nodes (1 primary + 2 replicas) -- Ports: 5432 (primary), 5433 (read replicas), 6432 (external TLS via pgBouncer) -- Connection: `postgresql://${user}:${password}@${hostname}:5432/${db}` -- Read scaling: Use port 5433 for read-heavy workloads +- Mode: default **NON_HA** (single node), immutable. HA (3 nodes: 1 primary + 2 replicas) is opt-in via `mode: HA` +- Ports: 5432 (primary / RW); **HA-only**: 5433 (read replicas); 6432 (external TLS via pgBouncer) +- 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`) +- Read scaling: port 5433 routes to read replicas — **HA mode only** (a NON_HA single node has no 5433) ## MariaDB - HA: MaxScale routing with async replication - Port: 3306 -- Connection: `mysql://${user}:${password}@${hostname}:3306/${db}` +- Connection: the generated `${connectionString}` is `mysql://${user}:${password}@${hostname}:3306` (no database path); append `/${dbName}` if your driver needs one - Use when: Application requires MySQL wire protocol ## ClickHouse - HA: 3 data nodes, replication factor 3 - Ports: 9000 (native), 8123 (HTTP), 9004 (MySQL), 9005 (PostgreSQL) -- Requires `ReplicatedMergeTree` engine in HA mode +- HA: replicated databases use a `Replicated(...)` engine `ON CLUSTER`; tables use a `Replicated*MergeTree` engine (without `ON CLUSTER`) - Use when: Analytics, time-series, OLAP workloads ## Gotchas diff --git a/apps/docs/content/guides/choose-queue.mdx b/apps/docs/content/guides/choose-queue.mdx index eb06b130..c984640a 100644 --- a/apps/docs/content/guides/choose-queue.mdx +++ b/apps/docs/content/guides/choose-queue.mdx @@ -43,7 +43,6 @@ NATS exposes **two distinct messaging shapes**. Pick ONE per recipe and write ya - 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 diff --git a/apps/docs/content/guides/choose-runtime-base.mdx b/apps/docs/content/guides/choose-runtime-base.mdx index cbb9faff..2a7264b4 100644 --- a/apps/docs/content/guides/choose-runtime-base.mdx +++ b/apps/docs/content/guides/choose-runtime-base.mdx @@ -3,30 +3,29 @@ title: "Choosing a Runtime Base on Zerops" description: "**Use Alpine** as the default base for all services. Use Ubuntu only when you need system packages not available in Alpine. Use Docker only for pre-built images." --- -**Use Alpine** as the default base for all services. Use Ubuntu only when you need system packages not available in Alpine. Use Docker only for pre-built images. +**Use Alpine** as the default base for all services. Switch to Ubuntu only for **glibc** needs (musl incompatibility): CGO-enabled Go, glibc-built Python/C-extension wheels, or the **Deno** runtime (no Alpine build). Needing a package is NOT itself a reason — both bases install packages (`sudo apk add` / `sudo apt-get install`). Use Docker only for pre-built images. ## Decision Matrix | Need | Choice | Why | |------|--------|-----| | **Any standard app** | **Alpine** (default) | ~5MB, fast, secure, sufficient for 95% of apps | -| System packages (apt) | Ubuntu | Full Debian ecosystem, ~100MB | +| glibc / CGO / C-extensions / Deno | Ubuntu | musl-incompatible binaries; Deno has no Alpine build (~100MB) | | Pre-built Docker images | Docker | VM-based, bring your own image | -| CGO / native libs | Ubuntu | Better glibc compatibility than Alpine's musl | ## Alpine (Default) - Size: ~5MB base -- Package manager: `apk add` -- Best for: All runtimes (Node.js, Python, Go, Rust, Java, PHP, etc.) +- Package manager: `sudo apk add --no-cache ` (sudo required — containers run as the `zerops` user) +- Best for: nearly all runtimes (Node.js, Python, Go, Rust, Java, PHP, Gleam, etc.). Exception: **Deno** has no Alpine build — it requires `os: ubuntu` - 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 diff --git a/apps/docs/content/guides/choose-search.mdx b/apps/docs/content/guides/choose-search.mdx index 88425b86..3a047b72 100644 --- a/apps/docs/content/guides/choose-search.mdx +++ b/apps/docs/content/guides/choose-search.mdx @@ -26,8 +26,8 @@ description: "**Use Meilisearch** for simple full-text search. Use **Elasticsear - 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%) +- Plugins via `PLUGINS` (set in `envSecrets`, comma-separated) +- JVM heap: `HEAP_PERCENT` (in `envSecrets`, default 50%) - Min RAM: 0.25 GB ## Typesense (Fast Autocomplete) @@ -49,4 +49,4 @@ description: "**Use Meilisearch** for simple full-text search. Use **Elasticsear 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 +4. **Elasticsearch plugins require restart**: Changing `PLUGINS` (in `envSecrets`) needs service restart diff --git a/apps/docs/content/guides/ci-cd.mdx b/apps/docs/content/guides/ci-cd.mdx index a9b8b2b7..31015598 100644 --- a/apps/docs/content/guides/ci-cd.mdx +++ b/apps/docs/content/guides/ci-cd.mdx @@ -22,14 +22,15 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: zeropsio/actions@main + - uses: zeropsio/actions@v1.0.2 with: access-token: ${{ secrets.ZEROPS_TOKEN }} - service-id: + service-id: ${{ 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) @@ -40,15 +41,16 @@ 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 +2. **`[ci skip]` in commit message**: Prevents pipeline trigger — useful for docs-only changes (the square brackets are required; bare `ci skip` does not match) 3. **Service ID not obvious**: Find it in service URL or three-dot menu → Copy Service ID +4. **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 diff --git a/apps/docs/content/guides/cloudflare.mdx b/apps/docs/content/guides/cloudflare.mdx index fecbeb49..287ef1ea 100644 --- a/apps/docs/content/guides/cloudflare.mdx +++ b/apps/docs/content/guides/cloudflare.mdx @@ -3,7 +3,7 @@ title: "Cloudflare Integration with Zerops" description: "Always use **Full (strict)** SSL mode in Cloudflare — \"Flexible\" causes redirect loops. Shared IPv4 with Cloudflare proxy is not recommended." --- -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 @@ -34,7 +34,7 @@ ACME: CNAME _acme-challenge. .zerops.zone ``` ## SSL/TLS Settings (Cloudflare Dashboard) -- **Encryption mode: Full (strict)** — mandatory +- **Encryption mode: Full (strict)** for production (Full is acceptable for testing) — **never "Flexible"** - **Never use "Flexible"** — causes infinite redirect loops - Enable "Always Use HTTPS" - WAF exception: Skip rule for `/.well-known/acme-challenge/` (ACME validation) @@ -55,7 +55,7 @@ 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. +**Important**: On first deploy, `zerops_deploy` **auto-enables** the L7 subdomain for eligible modes (dev/stage/simple/standard/local-stage) and waits HTTP-ready — you usually never call `zerops_subdomain enable`. Use it only for explicit recovery, or set `enableSubdomainAccess: true` in import YAML to pre-declare intent. The enable call rejects with `serviceStackIsNotHttp` only when the service has no HTTP-shaped port (a worker, or a port missing `httpSupport: true`) — that is about port shape, not deploy state. Internal service-to-service communication must always use `http://` — never `https://`. SSL terminates at the Zerops L7 balancer. @@ -64,4 +64,4 @@ Internal service-to-service communication must always use `http://` — never `h 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 +5. **"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. diff --git a/apps/docs/content/guides/deployment-lifecycle.mdx b/apps/docs/content/guides/deployment-lifecycle.mdx index 0336490c..027b31c3 100644 --- a/apps/docs/content/guides/deployment-lifecycle.mdx +++ b/apps/docs/content/guides/deployment-lifecycle.mdx @@ -108,9 +108,9 @@ Use `temporaryShutdown: true` only when you cannot run two versions simultaneous 1. Application starts via `start` command 2. Readiness check runs (httpGet or exec) -3. If **fails** -- wait `retryPeriod` seconds (default 5s), retry +3. If **fails** -- wait `retryPeriod` seconds (set it explicitly; platform default applies if unset, not schema-pinned), retry 4. If **succeeds** -- container marked active, receives traffic -5. If still failing after `failureTimeout` (default 300s / 5 min) -- container deleted, new one created +5. If still failing after `failureTimeout` (configurable — commonly ~5 min; set it explicitly, it is not a fixed schema default) -- 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 @@ -123,7 +123,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:** @@ -154,7 +154,7 @@ Zerops keeps **10 most recent versions**. Older auto-deleted. Any archived versi 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 +3. **initCommands failures DO fail the deploy** -- a non-zero `initCommands` exit aborts the deploy (`RUN.INIT COMMANDS FINISHED WITH ERROR`); the new appVersion is not activated and the app does not start. Keep init commands idempotent and exit 0 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` @@ -163,7 +163,7 @@ Zerops keeps **10 most recent versions**. Older auto-deleted. Any archived versi 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):** diff --git a/apps/docs/content/guides/environment-variables.mdx b/apps/docs/content/guides/environment-variables.mdx index c3aaff61..bdbdd07b 100644 --- a/apps/docs/content/guides/environment-variables.mdx +++ b/apps/docs/content/guides/environment-variables.mdx @@ -30,10 +30,10 @@ 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}` | +| 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. | ```yaml zerops: @@ -66,7 +66,7 @@ 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. @@ -161,7 +161,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 @@ -209,7 +209,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}`. +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}`. ## Common Mistakes diff --git a/apps/docs/content/guides/local-development.mdx b/apps/docs/content/guides/local-development.mdx new file mode 100644 index 00000000..939428b7 --- /dev/null +++ b/apps/docs/content/guides/local-development.mdx @@ -0,0 +1,132 @@ +--- +title: Local Development with Zerops +description: "Guide: Local Development with Zerops" +--- + +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`. + +How to load: +| Runtime | Method | +|---------|--------| +| Node.js 20+ | `node --env-file .env app.js` | +| Next.js, Vite, Nuxt | Automatic (reads `.env`) | +| PHP/Laravel | Automatic (reads `.env`) | +| Python | `python-dotenv` or `django-environ` | +| Go | `godotenv.Load()` or `source .env && go run .` | +| Java/Spring | `spring-dotenv` or `application.properties` | + +### 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"` | + +### 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: +```bash +zcli vpn up # work on project A +zcli vpn up # auto-disconnects A, connects B +``` + +--- + +## Gotchas + +1. **VPN = network only**: Env vars must come from `.env` file, not VPN connection +2. **`.env` contains secrets**: Add to `.gitignore` immediately — never commit +3. **Deploy = new container**: Local files on Zerops are lost on every deploy. Only `deployFiles` content persists +4. **One VPN project at a time**: Connecting to project B disconnects project A +5. **Object storage (S3)**: Uses HTTPS apiUrl — may work without VPN but not fully verified. Include VPN as fallback +6. **zcli must be installed**: `zerops_deploy` requires zcli in PATH. Error message includes install link if missing diff --git a/apps/docs/content/guides/logging.mdx b/apps/docs/content/guides/logging.mdx index 80d28e18..3a98c8a1 100644 --- a/apps/docs/content/guides/logging.mdx +++ b/apps/docs/content/guides/logging.mdx @@ -18,9 +18,10 @@ Zerops captures stdout/stderr as logs; use syslog output format for severity fil ### 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. @@ -57,7 +58,7 @@ Certificate paths: ## 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 +2. **Build logs separate**: Use `--show-build-logs` (kebab-case) 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` diff --git a/apps/docs/content/guides/metrics.mdx b/apps/docs/content/guides/metrics.mdx index 851d73f9..cac1d8ea 100644 --- a/apps/docs/content/guides/metrics.mdx +++ b/apps/docs/content/guides/metrics.mdx @@ -18,12 +18,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 @@ -37,12 +38,12 @@ 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 diff --git a/apps/docs/content/guides/php-tuning.mdx b/apps/docs/content/guides/php-tuning.mdx index 8f6973ba..fd250766 100644 --- a/apps/docs/content/guides/php-tuning.mdx +++ b/apps/docs/content/guides/php-tuning.mdx @@ -3,13 +3,13 @@ title: "PHP Runtime Tuning on Zerops" description: "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_*`. 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 @@ -43,7 +43,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. @@ -64,12 +64,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 @@ -77,11 +78,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: @@ -129,6 +131,6 @@ 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`. diff --git a/apps/docs/content/guides/production-checklist.mdx b/apps/docs/content/guides/production-checklist.mdx index 152270ed..ef8300f0 100644 --- a/apps/docs/content/guides/production-checklist.mdx +++ b/apps/docs/content/guides/production-checklist.mdx @@ -34,16 +34,26 @@ Before going to production: (1) databases to HA mode, (2) minContainers: 2 on ap ## Dev Services to Remove ### Mailpit → Production SMTP + +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 -- 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 +services: + - hostname: app + envSecrets: + SMTP_PASSWORD: your-production-key ``` ### Adminer → Remove or Restrict @@ -127,7 +137,7 @@ Remove entirely or disable `enableSubdomainAccess`. Use VPN + pgAdmin/DBeaver lo | 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 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 @@ -149,7 +159,7 @@ zerops: start: node server.js ``` -Readiness check gates traffic during deploy. Health check runs continuously — unhealthy containers are restarted after 5-minute retry window. +Readiness check gates traffic during deploy. Health check runs continuously — an unhealthy container is restarted after its configurable `failureTimeout`/`execPeriod` (set them explicitly; there is no fixed schema default), not a fixed 5-minute window. ## Gotchas 1. **HA is immutable**: Must delete and recreate service to switch modes diff --git a/apps/docs/content/guides/public-access.mdx b/apps/docs/content/guides/public-access.mdx index 991b7f8e..7689ba18 100644 --- a/apps/docs/content/guides/public-access.mdx +++ b/apps/docs/content/guides/public-access.mdx @@ -21,7 +21,7 @@ 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 +- 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: | IP Type | Cost | Protocol | Notes | @@ -45,5 +45,5 @@ Point your domain to the project's IP: ## 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 +3. **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 4. **Ports 80/443 reserved**: Your app cannot bind to these — Zerops uses them for SSL termination diff --git a/apps/docs/content/guides/scaling.mdx b/apps/docs/content/guides/scaling.mdx index d612324a..b4af6d9b 100644 --- a/apps/docs/content/guides/scaling.mdx +++ b/apps/docs/content/guides/scaling.mdx @@ -22,9 +22,9 @@ Zerops autoscales vertically (CPU/RAM/disk) and horizontally (container count). | **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 | +| **Shared storage** | Yes (cpu/ram/disk configurable) | No (fixed: NON_HA=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) | Manual only (change VM count, triggers restart) | No automatic autoscaling | ## Vertical Autoscaling @@ -191,7 +191,7 @@ zerops_scale serviceHostname="worker" cpuMode="SHARED" minCpu=1 maxCpu=8 minRam= ## 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** add `verticalAutoscaling` to **object-storage** services in import.yml -- causes import failure (object storage has a fixed `objectStorageSize` only). **Shared storage**, by contrast, DOES accept a `verticalAutoscaling` block (cpu/ram/disk) in import.yml -- the import schema permits it. **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. diff --git a/apps/docs/content/guides/smtp.mdx b/apps/docs/content/guides/smtp.mdx index dac7476a..e54a777f 100644 --- a/apps/docs/content/guides/smtp.mdx +++ b/apps/docs/content/guides/smtp.mdx @@ -24,15 +24,29 @@ 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 +zerops: + - setup: app + run: + envVariables: + SMTP_HOST: smtp.sendgrid.net + SMTP_PORT: "587" + SMTP_USER: apikey +``` + ```yaml -envVariables: - SMTP_HOST: smtp.sendgrid.net - SMTP_PORT: "587" - SMTP_USER: apikey -envSecrets: - SMTP_PASSWORD: +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 diff --git a/apps/docs/content/guides/vpn.mdx b/apps/docs/content/guides/vpn.mdx index 4799a7b4..0a9b47ae 100644 --- a/apps/docs/content/guides/vpn.mdx +++ b/apps/docs/content/guides/vpn.mdx @@ -8,7 +8,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 ``` @@ -31,7 +31,7 @@ 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 | diff --git a/apps/docs/content/guides/zerops-yaml-advanced.mdx b/apps/docs/content/guides/zerops-yaml-advanced.mdx index 8fb7980a..caf55e10 100644 --- a/apps/docs/content/guides/zerops-yaml-advanced.mdx +++ b/apps/docs/content/guides/zerops-yaml-advanced.mdx @@ -43,7 +43,7 @@ deploy: **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. +> **Dev/stage distinction**: In dev+stage pairs, healthCheck and readinessCheck belong ONLY on the stage entry. **Dynamic-runtime dev services OMIT `run.start` entirely** — the runtime container idles and the agent controls the server lifecycle via SSH. (A `start: zsc noop --silent` placeholder is the retired convention and is now rejected by a blocking recipe gate.) Adding healthCheck to dev causes unwanted container restarts during iteration. --- @@ -69,7 +69,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. @@ -90,7 +90,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`. --- @@ -178,7 +178,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