Orchestrator Helm chart versioning policy
This document records the decisions behind how we version and deploy the rtf-orchestrator Helm
chart for the RTF Orchestrator Service, the rationale for the phased approach, and the options
that were considered and rejected.
When a phase is completed, update the Current phase section to reflect actual state. The phase descriptions below are a permanent historical record of what was done and why — they should not be edited after the fact.
Current phase
Phase 1 + 2 — chart/image link fixed; production uses
edgefor fast iterationThe chart is published with version
0.0.0+<git-sha>andappVersionset to the git SHA. A pinned chart version guarantees a pinned image via theappVersionfallback in the Deployment template. Production overridesimage.tag: edgeandpullPolicy: Alwaysin the ArgoCDvaluesObject, so pods pick up the latest image on every rollout without a chart bump PR. Production is still updated by manually opening a PR onkanaveralto bumptargetRevision.
Design intent
The chart and image are published together on every code merge by design. The intent is that updating the Helm chart is the canonical way to update the Orchestrator — a chart version change implies a software change, and a software change produces a new chart version. This keeps deployment straightforward: there is one thing to bump (the chart), and it brings everything with it.
Options considered
Before settling on the phased approach, three alternatives were evaluated:
Option A — rolling tag (prod / latest)
Publish the chart under a mutable tag and point ArgoCD at it so it auto-tracks.
Rejected. ArgoCD’s OCI Helm source requires an exact targetRevision string; it cannot track a
mutable tag or a semver range. It would pull the tag once at install time and never update. Making
this work would require ArgoCD Image Updater, an additional operational dependency. More
fundamentally, with no staging environment between main and production, a mutable tag means any
bad merge silently redeploys with no human checkpoint — unacceptable as we approach live consumers.
Option B — immediate move to semver
Replace 0.0.0+sha with incrementing semver (0.1.0, 0.1.1, …) from the outset.
Rejected for now. ArgoCD OCI still requires an exact version; semver ranges do not work, so we gain no auto-tracking benefit. During early active development (several merges per day) we would accumulate hundreds of patch versions with no semantic distinction between them.
Option C — SHA pinning with phased improvement (chosen)
Keep SHA-based pinning but fix its problems in layers, making the process progressively more automated and semantically correct as the service matures.
Phase history
Phase 0 — Baseline
State:
- Chart published as
0.0.0+<git-sha>on every merge tomainthat touchescrates/**. - Image published with tags: full git SHA,
main-<short-sha>, and mutableedge. values.yamlhardcodesimage.tag: edge— every chart version deploys whateveredgepoints to at rollout time, regardless of the chart’s own SHA.- Production updated by manually opening a PR on
kanaveralto bumptargetRevision.
Problems that motivated moving to Phase 1:
- Chart and image are decoupled. Pinning a chart version does not guarantee a pinned image. The SHA in the chart version identifies the templates, not the running binary, contradicting the lockstep intent.
0.0.0+shais semantically unordered. Semver build metadata (the+segment) has no defined precedence —0.0.0+abcand0.0.0+defare equal. This rules out any tooling that relies on version ordering (including Renovate).- Manual PRs at high velocity. With several merges per day, repeatedly bumping
targetRevisionby hand is a significant source of toil.
Phase 1 — fix the chart/image version link
What changed:
- The
helm packagestep passes--app-version ${{ github.sha }}, embedding the git SHA intoChart.appVersion. - The Helm
Deploymenttemplate usesappVersionas the fallback image tag:image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" values.yamlclearsimage.tag(previously hardcoded toedge) and setspullPolicy: IfNotPresentas the chart default. SHA tags are immutable;Alwaysis wasteful when the image cannot have changed.
Result: A pinned chart version guarantees a pinned image. image.tag remains overridable via
values, which is used in Phase 2.
Phase 2 — production uses edge for fast iteration
What changed:
- The ArgoCD
valuesObjectinkanaveraladds:image: tag: edge pullPolicy: Always
Result: Production picks up the latest image on every pod restart or rollout without requiring a chart bump PR. With several merges per day and no live consumers at this stage, this removes the manual PR toil during the highest-velocity period of development.
Trade-off: The edge override is a known, intentional deviation from the lockstep intent. The
deployed image is not pinned. This is acceptable while there are no consumers; it must be removed
before the service takes on production traffic (Phase 3).
Note: Phases 1 and 2 are a single implementation effort, listed separately because Phase 1 is a change in this repo and Phase 2 is a values change in
kanaveral.
Phase 3 — remove edge override (enforce lockstep in production)
Trigger: The service has external consumers and a bad deploy has real consequences.
What changed:
- The
image.tag: edgeandpullPolicy: Alwaysoverrides are removed from the productionvaluesObjectinkanaveral. - The chart’s
appVersion(the git SHA baked in at publish time) becomes the live image tag. - Deploying a new image now requires a chart bump PR to
kanaveral.
Result: The lockstep intent is fully enforced in production. Every code change produces a new
chart version; bumping to that version in kanaveral is the single operation that updates the
running Orchestrator.
Summary
| Phase | Image tag in production | Lockstep enforced? | Automation |
|---|---|---|---|
| 0 | edge (hardcoded in values) | No | None — manual PRs to kanaveral |
| 1 + 2 | edge (values override) | No — intentional deviation | None — manual PRs to kanaveral |
| 3 | Git SHA (appVersion) | Yes | None — manual PRs to kanaveral |