Docs · Glidepath
ADR-0019: Environments are one config model, defined once in cicd.yaml
Status: proposed (2026-10-04); plan approved by the owner. Most open questions are answered (see requirements §6). Q3 (a Ground env on another cluster) is proposed and awaiting confirmation, so the status stays Proposed until it is.
Migration done (2026-10-07). Every app moved to deploy.environments (each change proven
render-identical first), then the old lowerEnvironments / upperEnvironments /
promotionOrder fields were removed: the schema, validate-cicd-config and the chart refuse
them, and Backstage and Tower read only the new list.
Context
An environment is not an object today. It is whatever deploy.lowerEnvironments,
deploy.upperEnvironments, deploy.promotionOrder, a pipeline step’s env, the
existence of platform/envs/<env>.yaml, the existence of a gitops directory, and (for
cloud targets) deploy-run history happen to agree on. Nothing checks that they do.
The consequences were found on real apps: a declared env rendered as a ghost card beside the working one; a Lambda function received a Kubernetes namespace and an Application it cannot use; a cloud app can deploy to only one resource however many envs it names; a cloud env cannot be gated because Flight means a gitops release PR and functions have no gitops repo; and Ground and Flight environments are edited in two different tabs with a form for one and raw YAML for the other. Details and evidence: envs-overhaul-requirements.md.
Decision (proposed)
deploy.environments[]incicd.yamlis the single definition of the environments. Each entry has aname, atier(groundorflight), and where relevantcluster,target, target config andapproval. List order is promotion order.- Pipeline steps reference environments by name; an undefined name fails validation.
deploy.targetand its config block remain the app default. An environment may override the target and the fields of its config (function name, region, ECS service, Container App), so one app can deploy different environments to different resources.- Cluster artifacts (the env file, namespace, RBAC, ApplicationSet entry, gitops
directory) are derived from the definition and exist only for
k8s-rolloutenvironments. A cloud environment creates none. - Both shapes are read during a window.
deploy.environmentswins when present; the old fields are otherwise mapped in memory. Glidepath opens a migration PR per app. The old shape is removed last, after every app has been checked. - Tower manages environments in one flow for both tiers (a dedicated Environments tab is the recommended shape), with changes expressed as pull requests and a preview of every file touched, including for deletion.
- Decided with the owner: a cloud Flight environment is approved by a PR on the source
repo changing a per-env release pin (no gitops repo); cloud credentials are one set per
app for now; an environment’s target cannot change (delete and re-add); the product keeps
the words Ground and Flight; App Configuration stays a separate tab; the Environments
tab is a table with in-place expansion and a pending-changes panel; Tower opens both PRs
for a Flight environment (the
cicd.yamlPR, and the existing ApplicationEnvironment template through the scaffolder API), while a Ground environment is onecicd.yamlPR and the onboarding resync scaffolds its env file. - This builds on, and does not change, ADR-0018 (the
glidepath/folder and the chart contract). The folder move anddeploy.chartare phases of the same work.
Consequences
- One place to add, reorder or remove an environment, and one check that it is consistent. The ghost-env class of bug has no source.
- Cloud apps get real multi-environment support, and a defined path to approval gates (open question 1).
- A schema change means a toolbox rebuild and bump; the in-memory mapping must be checked
against every real
cicd.yamlbefore any app depends on it. - A longer compatibility window than a one-shot migration, deliberately: a bad migration would affect every app at once.
- Adding a Flight environment touches two repos (source
cicd.yaml; the tenants-repo request that creates the gitops directory). Tower opens both and tracks them as one change set, showing the order to merge and when either is still open.