Skip to content

Environments vs Branches

Two questions come up the moment a flow leaves your laptop:

  1. Where should it run — against the sandbox API or the live one?
  2. Which version of the definition should run — the one in production, or the edit I am still reviewing?

Zenvara answers these with two independent mechanisms that are easy to confuse and important to keep apart. Environments answer where. Branches answer which version. They never overlap, and reaching for the wrong one is the single most common source of dev→prod confusion.

NounAxisAnswersPriced?
Connectionthe atomwhich single external service — one host, one credentialYes — the only counted unit
EnvironmentWHEREwhich set of services a run talks toNo
BranchWHEN / versionwhich version of the definitions runsNo

A connection is the atomic unit: one database, one SFTP, one API, with its own credential. It is also the only axis Zenvara meters — environments and branches are free structure you layer on top. That framing matters: you never create a second connection just to get a second environment, and you never split a flow across branches to reach two systems.

An environment maps a flow’s logical aliases (db, sftp, pim) onto concrete connections. The same product-sync flow runs against dev and prod with no YAML edits — the environment chosen at run time decides which real services db and sftp resolve to.

An environment carries a kind:production, staging, development, or sandbox. kind is a guard tier, not a copy of your infrastructure. It drives real behaviour: it gates ephemeral inline runs, feeds kind-aware authorization, and powers cross-environment validation (a development environment cannot pull from a production connection). It does not imply “make a second copy of everything” — a starter setup is one environment mapping a handful of aliases, extended later by adding aliases, not by cloning.

A branch is a staging area for definition changes. With git-backed versioning on, you stage an edit on a branch, review the diff, and promote it into production storage. Production flows are untouched until you promote. The URL carries the branch as a path segment: /api/v1/main/flows/... runs the production version; /api/v1/edi-rework/flows/... runs the version staged on the edi-rework branch.

A branch answers which version of the definitions runs. It says nothing about which systems those definitions talk to — that is still the environment’s job.

The axes are orthogonal by construction:

graph TD
    subgraph WHEN["Branch — which version"]
      main["main (production)"]
      edi["edi-rework (staged edit)"]
    end
    subgraph WHERE["Environment — which systems"]
      dev["dev → sandbox services"]
      prod["prod → live services"]
    end
    main -. run against .-> dev
    main -. run against .-> prod
    edi -. run against .-> dev
    edi -. run against .-> prod

Any branch can run against any environment. You can run the production main branch against your dev environment (a safe rehearsal of the live definition against sandbox systems), or the staged edi-rework branch against prod (a final check of the new definition against live systems, before promotion). Four combinations, two knobs — because version and target are genuinely independent decisions.

They intersect in exactly one place: storage. A branch is a git overlay over the same connection and environment definitions, so an edit staged on a branch can change an environment’s alias map for that branch only, until promoted. That is the whole overlap — a versioning overlay over the config, not a second routing dimension.

When you have a second deployment target — a distinct set of real systems you want the same flows to run against. Sandbox vs live. A per-customer production estate. A read-only audit region.

Never to get a second connection. If the only difference is one more database or one more API, that is a new connection added to an existing environment’s alias map — not a new environment. The wizard scaffolds one shared starter environment for exactly this reason: most setups need one environment per deployment target, not one per connection.

Why does on= take an environment, not a connection?

Section titled “Why does on= take an environment, not a connection?”

When you run a flow you select the environment — on=prod, --env prod, or the branch/env combination — never a connection directly. That is deliberate (ZEN-1446):

  • Aliases keep flows portable. A flow names db, not warehouse-prod. Binding to a connection at run time would hard-wire the flow to one host and defeat the whole point of the alias.
  • The guard tier stays in the loop. Routing through the environment is what lets kind cross-validation catch a dev run pointed at a production connection. A direct connection binding would bypass that guard.
  • You can still pin one connection when you must. on=<env>/<alias> selects a specific alias inside a declared environment — precise without re-coupling. If an environment binds two connections of the same connector type and you don’t qualify, you get a structured ambiguousBinding error listing the aliases.

So on= is environment-scoped on purpose: it keeps the flow portable and keeps the guard tier enforceable.

I want to…Reach forNot
Run the same flow against sandbox, then liveAn environment per target; on=<env> at run timeA branch per target
Add one more database to a running setupA new connection in the existing environment’s alias mapA new environment
Edit a flow and review before it goes liveA branch: stage → diff → promoteEditing production directly
Rehearse the live definition against sandboxProduction branch × dev environmentA copy of the flow
Keep a permanent test systemAn environment (kind: development/sandbox)A long-lived branch
Pin one step to a specific connectionon=<env>/<alias>Binding a connection by name