Skip to content

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 loopsame 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 loopsame 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.

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:

Terminal window
zen run-flow catalog-reindex --env pim-test
zen run-flow catalog-reindex --env pim-prod

The 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.

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:

flows/catalog-reindex.flow.yaml
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 in using: (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 flow input:. with: { full-reload: true } is accepted because full-reload is 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.)

  1. 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.
  2. 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 resource branches:///edi-rework/diff, or the Studio Branches page.
  3. 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.

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:

Terminal window
{ "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.

Terminal window
# Dry run — see what would merge, change nothing
curl -X POST http://localhost:5000/api/v1/platform/branches/promote \
-H "Content-Type: application/json" \
-d '{ "branch": "edi-rework", "preview": true }'
# Promote for real
curl -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:promote is required. Authoring permission alone cannot promote; admins hold it by default.
I want to…LoopMechanism
Run the finished flow against live systemsRun--env pim-prod / on=pim-prod
Schedule the same flow against two targetsRunTwo triggers, each with on: + with:
Edit a flow and ship the new version safelyChangeStage on a branch → diff → promote
Rehearse a staged edit against sandboxBothBranch (version) × low-tier environment (target)