From Dev to Prod
“Getting to production” is really two different loops, and Zenvara keeps them on two different axes. Conflating them is where the classic dev→prod confusion comes from — so name them explicitly:
- The run loop — same flow, different environment. You already wrote the flow; now you want it to run against live systems instead of sandbox ones. Nothing about the definition changes. You select a different environment at run time.
- The change loop — same environment, different definitions. You want to edit a flow (or a connection, or an environment) and get the new version live safely. The systems don’t change; the definitions do. You stage the edit on a branch, review the diff, and promote.
See Environments vs Branches for why these two axes never overlap. This page walks each loop end to end.
The run loop — one flow, many targets
Section titled “The run loop — one flow, many targets”A flow is portable: it names logical aliases (db, pim), and the environment chosen at run time decides which real connections those resolve to. The same file runs against dev, staging, and prod with no edits:
zen run-flow catalog-reindex --env pim-testzen run-flow catalog-reindex --env pim-prodThe Inheritance & Worked Example page shows this in full. The key point for this page: switching target is a runtime choice (--env on the CLI, on=<env> over MCP/REST), never an edit to the flow.
Pinning a scheduled run to an environment
Section titled “Pinning a scheduled run to an environment”You are not limited to picking the environment by hand. A trigger can pin the run to a specific environment with on:, and supply static inputs with with:. That means one flow can carry several schedules, each firing against a different target — the exact shape a real PIM sync needs.
Here is one flow with two cron triggers, each pinned to its own environment, each with its own static inputs:
status: active
using: - zenvara/akeneo - environment/pim-prod # first env entry = the flow default - environment/pim-test # declared → usable as a trigger on: target
input: full-reload: !bool? false # incremental by default
triggers: - name: prod-incremental type: cron expression: "*/15 * * * *" # every 15 min against production on: pim-prod with: full-reload: false
- name: test-nightly-full type: cron expression: "0 2 * * *" # nightly full reload against test on: pim-test with: full-reload: true
output: status: !int
steps: - log: "Reindex fired by ${source}" # source = "schedule" on a cron fire
- $reindex: invoke: akeneo.request on: pim # the alias each environment maps to its PIM with: Method: "POST" Path: "/api/rest/v1/jobs/export/catalog_reindex" Body: fullReload: "${full-reload}" rollback: none - return: status: "${reindex.statusCode}"Two environments, pim-prod and pim-test, each map the alias pim to a different PIM connection. The flow body is written once; the two triggers route it to two targets on two schedules. This is the pattern that a real engagement (ZEN-1711) mistakenly believed impossible and worked around by forking the flow — per-trigger environment selection removes the fork.
Two validators keep this honest at validate-flow time:
- V1317 — a trigger’s
on:must name an environment the flow declares inusing:(environment/pim-prod,environment/pim-test). A typo or an undeclared env is rejected before the trigger ever fires. - V1067 — a
with:key must be a declared flowinput:.with: { full-reload: true }is accepted becausefull-reloadis declared; a stray key is rejected. (V1069 rejects a value of the wrong type or a non-literal —with:takes static literals only, no expressions or${secret:…}.)
The change loop — stage on a branch, then promote
Section titled “The change loop — stage on a branch, then promote”When you need to edit definitions, you don’t touch production storage directly. With git-backed versioning enabled, you stage the change on a branch, review the diff, and promote it. Production is untouched until promotion.
Give branches feature-style names for the change they carry — edi-rework, add-nightly-reindex — not tier names. (A branch named staging invites confusion with the staging environment kind, which is a different thing entirely.)
- Stage. Point your edits at the branch by putting its name in the URL segment:
/api/v1/edi-rework/flows/catalog-reindex/.... Studio’s branch switcher does this for you. Production URLs (/api/v1/main/...) keep serving the old definition. - Review the diff. Compare what the branch changed against production before anything ships — over REST
GET /api/v1/platform/branches/edi-rework/diff, the MCP read resourcebranches:///edi-rework/diff, or the Studio Branches page. - Promote. Merge the reviewed changes into production storage (see below).
Rehearsing against sandbox first? Run the branch against a low-tier environment — POST /api/v1/edi-rework/flows/catalog-reindex/runs with on=pim-test exercises the new definition against test systems. Version and target are independent, so you can validate one at a time.
Promoting a branch
Section titled “Promoting a branch”Promotion merges a branch into the production branch as a no-fast-forward merge. It is a distinct release capability — flow:promote, separate from authoring (flow:write) and admin-gated by default (ZEN-2501) — so a non-admin author cannot ship to production without the release permission. Three surfaces, one operation:
Studio — Branches page. Select the branch, review the diff, and click Promote. The confirm dialog restates that the staged changes merge into production storage.
MCP — promote-branch. Preview first, then promote:
{ "branch": "edi-rework", "preview": true }Set preview: false (or omit it) to perform the merge. preview: true reports exactly what would be promoted without changing anything.
REST — POST /api/v1/platform/branches/promote.
# Dry run — see what would merge, change nothingcurl -X POST http://localhost:5000/api/v1/platform/branches/promote \ -H "Content-Type: application/json" \ -d '{ "branch": "edi-rework", "preview": true }'
# Promote for realcurl -X POST http://localhost:5000/api/v1/platform/branches/promote \ -H "Content-Type: application/json" \ -d '{ "branch": "edi-rework", "message": "Ship EDI rework" }'Semantics on every surface:
- Preview is a true dry run. It computes the merge and reports it without writing.
- Conflicts abort. A promotion that would conflict with production is rejected whole — there is no half-merged state.
flow:promoteis required. Authoring permission alone cannot promote; admins hold it by default.
Which loop am I in?
Section titled “Which loop am I in?”| I want to… | Loop | Mechanism |
|---|---|---|
| Run the finished flow against live systems | Run | --env pim-prod / on=pim-prod |
| Schedule the same flow against two targets | Run | Two triggers, each with on: + with: |
| Edit a flow and ship the new version safely | Change | Stage on a branch → diff → promote |
| Rehearse a staged edit against sandbox | Both | Branch (version) × low-tier environment (target) |
Where to go next
Section titled “Where to go next”- The conceptual split, with the anti-patterns to avoid: Environments vs Branches.
- Trigger reference, including
on:andwith:: Triggers & Scheduling. - Enabling and configuring branches: Git-backed Versioning.