Skip to main content
RunBook Academy

Git, CI/CD & GitOpsCV · GitOps Anti-PatternsEnvironmentSeparation

No environment separation — one controller, one repo, every cluster

Advanced⏱ ~26 mingitargocdflux

What you'll learn

  • Identify the four failure modes a single-controller, single-repo architecture produces
  • Design per-environment AppProjects with their own source repositories or their own paths
  • Apply the per-cluster controller pattern for the strongest environment boundary
  • Distinguish promotion-by-ref from promotion-by-merge as the GitOps-native promotion flow

Prerequisites

Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x

Not yet marked complete on this device.

Environment separation is the architecture that says “this change has not yet earned the right to run in production”. The architecture is the boundary that prevents the staging change from reaching production without the explicit act of promotion.

The four failure modes of no separation

A single-controller, single-repo architecture produces four failure modes that recur at every stage.

    flowchart LR
        A["PR to staging"] --> B["Single repo"]
        B --> C["Single controller"]
        C --> D["Every cluster syncs"]
        D --> E["Production sees the change"]
        B -.->|"no boundary"| F["Repo permissions unified"]
        C -.->|"no boundary"| G["Controller credentials unified"]
        D -.->|"no boundary"| H["Drift visible everywhere"]
  • Promotion-by-merge. A change is merged to main and immediately appears in every cluster.
  • Repository permissions unified. The repo’s permission model is the union of every cluster’s read.
  • Controller credentials unified. Broad enough for every cluster, or narrow enough for one.
  • Drift visible everywhere. An out-of-band change in staging is a kubectl diff in production.

The team that has no separation pays for the simplicity of one repo and one controller with the cost of distinguishing environments by directory rather than by architecture.

Per-environment AppProjects

The first boundary is per-environment AppProjects. Argo CD’s AppProject scopes an Application to source repositories and destination clusters.

apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: payments-staging
  namespace: argocd
spec:
  sourceRepos:
    - https://github.com/example/payments-staging
  destinations:
    - namespace: payments
      server: https://staging-cluster.example.com
  clusterResourceWhitelist:
    - group: ""
      kind: Namespace

The sourceRepos field names the only repositories the AppProject’s Applications may read from. The destinations field names the only clusters and namespaces the Applications may deploy to. The boundary is in the controller’s admission path.

Per-cluster controllers

The strongest boundary is a controller per cluster. Argo CD’s application controller runs once per cluster; Flux’s controllers run once per tenant namespace.

kubectl config use-context staging
kubectl get applications -n argocd
kubectl config use-context production
kubectl get applications -n argocd

A compromise of one is not a compromise of the other. The cost is operational: more controllers to maintain.

Promotion-by-ref

The third boundary is promotion-by-ref. A team that promotes by merging to main and waiting for every cluster to sync has no promotion flow; every merge is a production deploy. A team that promotes by Git ref has a promotion flow that crosses each boundary explicitly.

    flowchart LR
        A["PR merged to main"] --> B["Staging Application syncs"]
        B --> C["Staging healthy"]
        C --> D["Update production Application spec.sourceRef"]
        D --> E["Production Application syncs"]
        E --> F["Production healthy"]
        B -.->|"fail"| G["Staging never promoted"]
        C -.->|"regress"| H["Production spec not updated"]

The promotion is the explicit update of the production Application’s spec.sourceRef to the SHA staging proved healthy.

Production discipline

  1. One AppProject per environment, with sourceRepos and destinations enforced.
  2. One controller per cluster.
  3. Promotion by ref, not by merge.
  4. Credentials are scoped per controller.

Cross-course references

  • This course, Part LXXXIII (GitOpsRBAC) - AppProject roles.
  • This course, Part LXXXIV (Promotion) - promotion by ref.

Quiz

Knowledge check · 4 questions

  1. Q1. A team runs a single Argo CD controller against staging and production clusters, reading from a single repository with environment-labelled directories. What is the strongest environment-separation boundary in place?

  2. Q2. Per-environment AppProjects with distinct `sourceRepos` provide an admission-level boundary between staging and production.

  3. Q3. Name the three boundaries that together turn environment separation from a convention into an architecture.

  4. Q4. Diagnose a missing-environment-separation incident and recommend the architecture that prevents it.

    A team runs one Argo CD controller reading one repository. A developer opens a PR that renames a ConfigMap in `kustomize/overlays/production`. The PR is merged. The staging cluster syncs the same manifest because it watches the same repo with an overlay that resolves the same ConfigMap. Staging's downstream Deployment crashes because the ConfigMap the staging pods depend on is gone.

Passing score: 75%. Answers are checked in this browser.