--- name: zerops-yaml description: Write and fix zerops.yaml build, deploy, and runtime pipelines for Zerops services. Use when choosing runtime bases, packaging artifacts, configuring startup and checks, or adapting setups for monorepos and environments. --- # Write the application pipeline ## 1. Identify the application contract Inspect the existing `zerops.yaml`/`zerops.yml`, package manifests, lockfiles, entrypoint, build output, listener configuration, and required variable **names**. Do not read credential files or copy secrets into YAML, logs, or chat. Preserve working application commands; adapt a reference to the actual repository. Determine the existing service type, service ID, setup name, and upload root. Create missing infrastructure with the `zerops-import` skill; use `zerops-setup` for CLI installation or authentication. This file configures the application pipeline; project/service creation and scaling belong to import or operations. The workflow works from a local shell or cloud sandbox; it needs no VPN or SSH. Choose the matching reference; read only the relevant families: | App | Reference | | --- | --- | | Node.js / TypeScript | [node](references/node.md) | | Python | [python](references/python.md) | | Go | [go](references/go.md) | | PHP with Nginx or Apache | [php](references/php.md) | | Java | [java](references/java.md) | | .NET | [dotnet](references/dotnet.md) | | Rust | [rust](references/rust.md) | | Bun | [bun](references/bun.md) | | Deno | [deno](references/deno.md) | | Elixir | [elixir](references/elixir.md) | | Ruby | [ruby](references/ruby.md) | | Static assets, SPA, or Nginx | [static](references/static.md) | | Existing Docker image or Compose app | [docker](references/docker.md) | Use a supported base compatible with the app's language version and existing service. Check the [base list](https://docs.zerops.io/zerops-yaml/base-list) and [current YAML schema](https://api.app-prg1.zerops.io/api/rest/public/settings/zerops-yml-json-schema.json). Prefer explicit OS-prefixed bases. Avoid deprecated `build.os`/`run.os`; `latest` and `stable` are moving aliases, not reproducible version pins. Keep build/run OS and native-library ABI compatible; never assume a binary or native dependency built on Ubuntu will run on Alpine. ## 2. Place each operation in the right phase Make the root a `zerops` list of named setups. Each deployable setup has a `run` section, with optional `build` and `deploy` sections. Use `build.base` and `build.deployFiles` when building. Without `run.base`, the current service base is maintained. See [fields](references/fields.md) for optional keys and types. | Phase | Field and location | Put here | | --- | --- | --- | | Build image preparation | `build.prepareCommands`; docs specify `/home/zerops` | Build OS packages/tools; use absolute paths if location matters | | Build | `build.buildCommands`; `/build/source` | Install dependencies and compile/package the app | | Select artifacts | `build.deployFiles` | Every file and dependency required at runtime | | Runtime image preparation | `run.prepareCommands`; `/home/zerops` | Runtime OS packages/tools; files supplied by `build.addToRunPrepare` | | Deploy artifacts | Selected paths land under `/var/www` | App code and assets become available after runtime preparation | | Container initialization | `run.initCommands`; `/var/www` | Idempotent runtime configuration, coordinated migrations, path-dependent caches | | Start and readiness | `run.start` or `run.startCommands`; then `deploy.readinessCheck` | Foreground processes, followed by a real readiness witness | Treat build and runtime as separate containers. Global packages installed during build do not appear at runtime. Ship production dependencies or install them in the runtime image. Never read deployed `/var/www` files in `run.prepareCommands`; copy the necessary manifests/scripts with `build.addToRunPrepare` first. Keep commands sharing `cd` or shell exports in one YAML block scalar (`- |`); separate command-list entries use separate shell instances. Use the package manager for the selected OS: Ubuntu `apt-get`, Alpine `apk`, with `sudo` where required. Do not install packages at every container start. Match startup paths to artifact placement: | `build.deployFiles` | Runtime result | | --- | --- | | `dist` | `/var/www/dist/...` | | `dist/~` | `/var/www/...` | | `target/release/~app` | `/var/www/app` | The tilde is Zerops path transformation syntax, not shell home expansion. Include runtime assets, manifests, templates, and native libraries as needed. Do not blindly copy `.gitignore` patterns into `.deployignore`: excluding build output or dependencies can remove exactly what runtime needs. Upload filtering also differs between Git and `--no-git`; use `zerops-deploy` for that choice. Keep startup in the foreground; do not append `&` or daemonize. For multiple supervised processes, use `run.startCommands` instead of shell background jobs. Omit an explicit start command for built-in webservers as the runtime reference shows. Initialization runs on restart and scale-out, so do not assume a migration executes only once. For coordinated initialization, consult the documented [`zsc exec-once`](https://docs.zerops.io/references/zsc) container helper. ## 3. Wire configuration and listeners Put build-only values in `build.envVariables`, and runtime configuration in `run.envVariables`. Build values do not automatically travel to runtime. Reference stored values explicitly under the names the app reads: ```yaml # Merge into the selected setup; db is the actual managed service hostname. run: envVariables: DB_HOST: ${db_hostname} DATABASE_URL: ${db_connectionString} APP_KEY: ${APP_KEY_SECRET} ``` Use `zerops-import` to provision the project/service vault and `zerops-operate` for subsequent variable changes. Store sensitive values there, never as YAML literals. `${KEY}` resolves the service vault then Shared; `${hostname_varname}` references another service. Use distinct destination names: `KEY: ${KEY}` can self-shadow. Missing references can remain literal `${...}` strings. Verify names without printing secret values. Explicit wiring avoids dependence on compatibility injection of unreferenced project/service values. For build references, use Shared or cross-service values; do not depend on the target service's own vault being available there. A YAML runtime mapping takes effect on deployment; changed stored values require a process restart. Do not expose secrets through frontend build variables or static file substitution. For a custom HTTP server, inspect the actual listener before changing code. Change its bind address only when it explicitly uses `localhost`, `127.0.0.1` or `::1`, or its framework has a verified loopback-only default; use `0.0.0.0` in that case. Otherwise leave the listener code alone. Node's `listen(port)` without a host already listens on all interfaces (`::` when IPv6 is available, otherwise `0.0.0.0`); see the [Node listener docs](https://nodejs.org/api/net.html#serverlistenport-host-backlog-callback). Declare the exact listener in `run.ports`, and set `httpSupport: true`. Declaring a port does not configure the application, and does not automatically set its `PORT` variable. Use a matching env value only if the app reads it. Valid documented port numbers are 10–65435. Omit `protocol` for TCP; if explicit, use the live schema's lowercase `tcp`/`udp`. HTTP support applies only to TCP. Public routing/subdomains are separate infrastructure configuration; follow the post-push public-access step in `zerops-deploy`. ## 4. Add readiness and health checks Use `deploy.readinessCheck` to gate a new deployment and `run.healthCheck` to monitor running containers. There is no `run.readinessCheck`. Select **one** of `httpGet` or `exec` per check. Add checks only against a route or command the application actually implements; change the example port/path to that contract: ```yaml # Merge into the same setup, preserving its other deploy/run fields. deploy: readinessCheck: httpGet: { port: 3000, path: /health } run: healthCheck: httpGet: { port: 3000, path: /health } ``` HTTP checks originate inside the container at localhost; optional `host` sets the Host header, not the destination. Use internal HTTP unless the app itself requires HTTPS. HTTP 2xx or an `exec.command` exit status of zero is success. An exec check must use tools present in the runtime image. Keep `deploy.temporaryShutdown` false (default) for new-before-old replacement; true intentionally stops old containers first. Design rolling deployments so old and new app versions can coexist. Checks on an intentionally idle development container would probe an absent server; configure them for the actual running app. **Unresolved source conflict (checked 2026-10-21):** the [check guide](https://docs.zerops.io/guides/readiness-health-checks) requires duration strings for timeout/period fields, while the live JSON schema types them as integers without units. Omit optional timing overrides initially. Do not guess units or defaults; verify current server acceptance before tuning. ## 5. Select setups and monorepo boundaries explicitly Use one setup per deployable service/environment. A setup commonly matches the service hostname, but explicit `--setup` can select a differently named entry. Derive `dev` and `prod` from a common setup with `extends` when they really share configuration. Maps merge recursively; child scalars override; lists **replace** the whole inherited list. A null env value removes an inherited variable. See [setups and monorepos](references/setups.md) for a complete example. Choose the upload root that contains all workspace packages and lockfiles needed by the build. `--working-dir` controls packing and config lookup, not a persistent `cd` inside build command lists. `--zerops-yaml-path` is relative to that root unless absolute. Start paths and `deployFiles` must describe the same runtime layout, even if the app lives under `apps/api` locally. ## 6. Validate before an authorized push Attach the published schema to an editor or use an available local YAML + JSON Schema validator. The docs describe SchemaStore auto-detection for `zerops.yaml` and `zerops.yml`; this explicit modeline also works: ```yaml # yaml-language-server: $schema=https://api.app-prg1.zerops.io/api/rest/public/settings/zerops-yml-json-schema.json ``` Check syntax, unknown fields, field types, and base enums locally. Schema success does not prove commands, files, inheritance semantics, or service compatibility. The schema also has the timing conflict noted above; keep that caveat in reports. Before handing off to `zerops-deploy`, check: - Target service type/ID and selected setup are known; setup exists in the file. - Build/run bases are supported and match the app's language and native ABI. - Upload includes workspace inputs; `deployFiles` includes all runtime dependencies. - Every startup/config path matches the deployed layout under `/var/www`. - Preparation does not access artifacts too early; init is safe on every start. - Secret references are explicit and distinct; no credential files are uploaded. - Listener, port declaration, HTTP support, and real probe route agree. - Static/PHP use the correct web root, routing, and implicit start behavior. - Local schema issues are fixed or a concrete upstream conflict is reported. When deployment is part of the user's request, use `zerops-deploy`; the explicit command shape is: ```sh zcli push --project-id PROJECT_ID --service-id SERVICE_ID --setup prod --working-dir . --zerops-yaml-path zerops.yaml ``` Replace the IDs and setup with verified targets. A service ID determines its project; supplying both IDs does not cross-check them. `zcli push` performs server-side YAML validation for that service **before upload**, then builds and deploys. It is not a dry-run validator. The checked CLI has no standalone local schema validation command or push dry-run flag; check `zcli push --help` for a newer installed version. Do not push merely to validate an editing-only request. Keep the push exit status: run it without a pipe, or enable `set -o pipefail` before piping output in a shell that supports it. Deployment success requires push exit 0 and the URL check in `zerops-deploy`. Diagnose build/start failures by phase using `zerops-operate`. ## Sources Checked 2026-10-21: [field specification](https://docs.zerops.io/zerops-yaml/specification), [pipeline](https://docs.zerops.io/features/pipeline), [environment variables](https://docs.zerops.io/guides/environment-variables), [live YAML schema](https://api.app-prg1.zerops.io/api/rest/public/settings/zerops-yml-json-schema.json), and [zcli v1.1.3 push source](https://github.com/zeropsio/zcli/blob/v1.1.3/src/cmd/servicePush.go). Vault/isolation and artifact traps were also checked against `zeropsio/zcp: internal/knowledge/themes/core.md` (environment and build/deploy sections).