ADR-0002: How services get deployed¶
Status: Accepted · Date: 2026-08-13
Context¶
"ArgoCD or Helm?" kept being asked because the answer was implicit. In practice the platform already used ArgoCD ApplicationSets, but nothing said so, and the evidence pointed both ways:
- The golden-path chart could render an Argo Rollout, but only one service in the
entire repo opted in (
hello-service, and only locally). - The canary steps were hardcoded 20/50/100 in the chart, and the rollback thresholds hardcoded at 1% / 500ms inside the ClusterAnalysisTemplate.
- The Enable Canary Deployments scaffolder collected
errorRateThresholdandlatencyThresholdMsfrom the user and interpolated them into the pull request description. They never reached the chart. Every canary ran on the same 1% / 500ms regardless of what was typed. - The ten AI/MCP services were excluded from the ApplicationSets entirely and
deployed by
helm upgradefrombootstrap-ai.sh— the platform's own services did not take the path it sells.
Decision¶
ArgoCD ApplicationSets are the only deploy mechanism. Helm is a packaging format, never a deploy verb in CI.
Argo Rollouts is a first-class, fully parameterised chart feature with three
strategies: rolling (a plain Deployment), canary, and blueGreen.
Analysis stays one cluster-scoped ClusterAnalysisTemplate taking arguments,
not a per-service template rendered by Helm. Its PromQL carries three hard-won
corrections — the status_code label name, the or vector(0) guard, and
clamp_min — and duplicating that into every service release is how they
silently regress. Thresholds are per-service; the query is not.
Why the template params never reached the chart¶
Not an oversight — a structural limit. A scaffolder skeleton cannot merge into
an existing file, so the template could only emit a .patch for a human to
apply by hand, and nobody did.
The fix is to give every ApplicationSet a second, optional values file that a template can generate wholesale:
1 2 3 4 | |
ignoreMissingValueFiles is mandatory. Without it, every service that has no
such file puts the entire ApplicationSet into ComparisonError.
Consequences¶
- Image promotion stays GitOps-by-
yq: CI writes the tag intohelm-values-<env>.yamland ArgoCD syncs. CI never runshelm install. values.schema.jsonconstrains the rollout block, becausestrategy: blue-greenproduces a Rollout with no strategy — which Argo accepts and then never progresses. It now fails athelm lint.- Blue-green doubles the pod count during promotion. On a single-node local
cluster
blueGreen.previewReplicaCount: 1is required or the preview stack evicts platform components. - Unverified and load-bearing: that Argo Rollouts substitutes
{{args.*}}insidefailureConditionspecifically. It does so in provider queries. If a controller version does not do it in conditions, the gate compares against a literal string and never fails — a silently broken safety net. Test on a cluster with a deliberately broken image before relying on the thresholds.
Still outstanding¶
Resolved (2026-08). This section previously recorded that the ten AI/MCP
services remained excluded from the ApplicationSets, behind a four-step plan:
(1) CI build matrix reaches ECR, (2) invert bootstrap-ai.sh to argocd app
sync when an Application exists, (3) move the KAgent policy ConfigMap into
GitOps, (4) delete the exclusions.
All four are done. Both aws/argocd/app-of-apps.yaml and
local/argocd/app-of-apps-local.yaml now discover services/* with no
exclusions, so the AI/MCP services take the same GitOps path as everything
else and the platform's own services no longer bypass the model it sells.
Two constraints keep that true, and re-adding exclusions without reverting them will break a core bootstrap:
bootstrap-local.shmust buildidp-mcp-serverandqa-mcp-server. ECR images outlive a cluster, but the local registry is recreated with it, so ArgoCD would otherwise deploy images never pushed tolocalhost:5003.approval-service's policy ConfigMap volume must stayoptional, since onlybootstrap-ai.sh --adpcreates it.
References¶
helm/service-template/—values.yaml,templates/rollout.yaml,values.schema.jsonkubernetes/argo-rollouts/analysis-template.yamlaws/argocd/app-of-apps.yaml,local/argocd/app-of-apps-local.yamlbackstage/catalog/templates/canary-deployment/docs/golden-path.md— when to choose canary vs blue-green