Helm, Kustomize and GitOps with Argo CD · GitOps with Argo CD
Promotion Between Environments by Copy and Paste
Hand-edited image tags per environment let untested versions reach production; promote the same artefact through gates.
Scenario
Example company: to release, an engineer edits the image tag in overlays/prod to "the version that's in staging". One Friday they copy the tag from the wrong service's overlay.
What you see in production
Production runs a build that never ran in staging. Rolling back is easy, but nobody can answer "which version passed which tests" without digging through CI logs.
The naive code
sed -i 's/newTag: .*/newTag: 2.9.0/' overlays/prod/kustomization.yaml # by hand, from memory
git commit -am "prod release" && git push
Reproduce it
A promotion pipeline that only moves versions which passed the previous stage's checks:
stages = ["dev", "staging", "prod"]
deployed = {"dev": "2.9.1", "staging": "2.9.0", "prod": "2.8.4"}
checks = {("2.9.0", "staging"): {"smoke": True, "e2e": True, "soak_2h": True},
("2.9.1", "dev"): {"smoke": True, "e2e": False}}
def promote(version, frm, to):
if stages.index(to) != stages.index(frm) + 1:
return f"refused: {frm} -> {to} skips a stage"
if deployed[frm] != version:
return f"refused: {version} is not what runs in {frm} ({deployed[frm]})"
results = checks.get((version, frm), {})
if not results or not all(results.values()):
failed = [k for k, ok in results.items() if not ok] or ["no results"]
return f"refused: {version} failed {failed} in {frm}"
deployed[to] = version
return f"promoted {version}: {frm} -> {to} (pull request opened with the new tag)"
print(promote("2.9.1", "staging", "prod")) # the copy-paste mistake
print(promote("2.9.1", "dev", "staging"))
print(promote("2.9.0", "staging", "prod"))
Output
refused: 2.9.1 is not what runs in staging (2.9.0) refused: 2.9.1 failed ['e2e'] in dev promoted 2.9.0: staging -> prod (pull request opened with the new tag)
Root cause
When promotion is a manual edit, nothing guarantees that the version reaching production is the one that passed earlier stages. Tags typed by hand can come from the wrong place, and the record of what was tested lives in people's heads.
The fix
- Promote the same artefact (image digest) from stage to stage; never rebuild per environment.
- Automate promotion: a pipeline or tool (Kargo, Argo CD Image Updater with write-back, a CI job) opens a pull request bumping the next environment's tag only when the previous stage's checks passed.
- Make checks explicit and recorded: smoke tests, end-to-end tests, soak time, approvals for production.
- Keep promotion in Git so every change is reviewable and revertible.
Trade-offs
- Automated promotion needs reliable tests; flaky tests block releases.
- Extra tooling (Kargo, image updaters) is another system to learn and operate.
Detect it in production
- Versions running in production that never ran in staging (compare deployment history).
- Promotion PRs without linked test results.
Key points
- Manual tag edits let untested builds reach production.
- Promote the same digest through stages, gated on recorded checks.
- Do it through pull requests so promotion is auditable and reversible.
Further reading: Argo CD Image Updater · Kargo
Practice questions
What does 'promote the same artefact' mean?
Answer
The exact image (by digest) that passed earlier stages is deployed to the next one; nothing is rebuilt for production.
How should promotion to production be gated?
Answer
On recorded results from the previous stage (smoke, end-to-end, soak tests) plus required approvals, enforced by the promotion tool or pipeline rather than by people remembering.
Why perform promotion through pull requests in a GitOps repository?
Answer
Every promotion is reviewed, recorded and revertible in Git, and the GitOps controller applies it like any other change.
Open in the app to mark lessons complete, take notes and track your progress.