Skip to content

Environments

An environment is a named set of aliases pointing at connections. A flow asks for an alias; the environment decides which real connection that resolves to today.

Environments live under config/environments/{name}/definition.yaml:

kind: production
description: "Production environment for the nightly archive pipeline."
connections:
sftp: archive-sftp
http: zenvara-default

Three fields:

  • kind: — restriction level used for cross-validation (see Cross-environment validation).
  • extends: — optional list of parent environments composed in order; the child can override individual aliases. Single-level only — no transitive chains.
  • connections: — the alias map. Each entry binds a short alias (the name a flow uses) to a connection name (a file under config/connections/).

A flow references the alias, never the connection name directly:

- $deploy:
create: sftp.file
on: sftp # the alias from the environment
with:
Path: "/htdocs/index.html"
Input: "${build.dist-path}/index.html"

Same flow, different environments — dev maps sftp to a sandbox SFTP, prod maps it to the live one. (on: replaced the older connection: keyword.)

A single product-sync flow runs against dev, staging, and prod without YAML edits. Three environment files, one flow, three behaviours:

config/environments/dev/definition.yaml
kind: development
connections: { db: warehouse-dev, sftp: drop-zone-dev }
# config/environments/staging/definition.yaml
kind: staging
connections: { db: warehouse-staging, sftp: drop-zone-staging }
# config/environments/prod/definition.yaml
kind: production
connections: { db: warehouse-prod, sftp: drop-zone-prod }
Terminal window
zen invoke-flow product-sync --env dev
zen invoke-flow product-sync --env staging
zen invoke-flow product-sync --env prod

The flow is the same file in storage; the environment chosen at invocation routes it to a different set of services.

Validation is level-based, not symmetric: a higher-kind environment may use lower-kind connections (a production env can read from a staging connection — useful for read-only audit scrapes), but a lower-kind env cannot pull from a higher-kind one (a staging env asking for a production connection fails validation). Default levels:

KindLevel
production4
staging3
development2
sandbox1

This is a guard against the classic “ran dev flow against prod DB” mistake. Two opt-outs: omit kind: from the connection (validation only triggers when both sides declare a kind), or set EnvironmentKinds.RequireKind: false (the default) so untyped connections aren’t forced into a tier.

Sometimes a flow needs a specific connection by name regardless of environment — an audit pipeline that always goes to the same audit DB is the canonical case:

using:
connections:
audit: audit-postgres-prod # always this connection, regardless of env

Inside the flow, on: audit resolves to audit-postgres-prod no matter which environment runs it. Use this sparingly — it deliberately breaks portability. The using: block accepts both shapes together: the operator list (using: [zenvara/http]) and this connection-bypass map are independent.