Skip to main content
RunBook Academy

Git, CI/CD & GitOpsLXXVI · GitOps Repository ArchitectureRepo Models

Application versus environment repositories — the two-repo pattern

Advanced⏱ ~22 mingit

What you'll learn

  • Distinguish the application repository from the environment repository in a GitOps design
  • Explain why the two-repo pattern outlasts the one-repo shortcut in production
  • Identify the trust boundary that the environment repo creates between CI and the cluster
  • Wire a Flux GitRepository and an Argo CD Application to the environment repo

Prerequisites

None — start here.

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.

The application repository holds the code that produces an artifact. The environment repository holds the desired state that the cluster converges to. Mixing the two in one repository is the most common short-cut in a young GitOps design, and it is the short-cut that produces the worst incidents later. This lesson establishes the two-repo pattern, shows why it outlasts every other choice, and wires both sides to a controller.

The two responsibilities, split

In a GitOps system there are two kinds of change that need to be version-controlled. The first is a change to the code that builds the artifact: a new feature, a bug fix, a Helm chart edit, a container definition. The second is a change to the desired state of a cluster: which image tag is pinned, which replica count is set, which secret is referenced, which environment is being promoted. Conflating them in one repository means a developer with a pull request against main can drift production by accident.

flowchart LR
    A["Application repo: code + CI"] --> B["OCI artifact"]
    B --> C["Environment repo: desired state"]
    C --> D["Argo CD / Flux"]
    D --> E["Cluster"]
    A -. "PR reviewed by code owners" .- C
    C -. "PR reviewed by platform team" .- A

The two-repo pattern puts a trust boundary between the two responsibilities. The application repo is owned by the team that writes the code; the environment repo is owned by the platform or SRE team that owns the cluster. A pull request that crosses the boundary is reviewed by both teams.

Why the split outlasts the shortcut

New GitOps teams routinely start with one repo: “the application manifests live next to the application code, in the same repo, under deploy/.” This works for a single environment, a single team, and a single cluster. It fails on at least three dimensions in production:

  • Promotion. A single repo cannot express “this image is deployed to dev but not to prod” without an environment branch. Once you have main, staging, and production branches, you have three times the merge work and three times the audit surface.
  • Authorization. Code owners and platform owners are usually different groups. In one repo they share a CODEOWNERS file and can overrule each other. In two repos each group reviews only the changes that belong to them.
  • Auditability. When the question is “what is running in production?”, the answer must come from a repo whose only changes are desired-state changes. An application repo pollutes that history with code commits that have nothing to do with production state.

Wiring the two repos to a controller

Flux reads the environment repo with a GitRepository and materialises its manifests with a Kustomization. The application repo is irrelevant to Flux; the artifact it produces is referenced by digest in the environment repo’s manifests.

flux create source git env-repo \
  --url=https://github.com/example/env-repo \
  --branch=main \
  --interval=5m

Argo CD reads the environment repo with an Application. The artifact reference lives in the environment repo’s manifests as well; Argo CD does not care which repo produced the image.

argocd app set payment-api \
  --repo https://github.com/example/env-repo \
  --path clusters/production/payment-api \
  --revision-policy tracked

Under the hood

The two-repo pattern is not a GitOps invention. It is the same shape as the classic “build artifact” / “deploy manifest” split that has existed in release engineering for decades. What GitOps adds is that the deploy manifest is not consumed by a human operator running a deploy script; it is consumed by a controller in the cluster. The controller is the operator, and the deployment is reduced to a pull request.

This reduction is what makes the trust boundary worth the cost. A cluster admin who reads the environment repo can answer “what is running here?” without asking the application team. A security auditor who reads the environment repo can answer “who changed this and when?” without combinatorially searching the application repo’s history. The application repo keeps its own contract (reproducible builds, signed commits, CI tests) and the environment repo keeps its own contract (reviewed desired state, pinned digests, signed commits).

Production discipline

The production rules for the two-repo pattern are:

  1. The environment repo is owned by the platform team. The CODEOWNERS file on the environment repo names the platform team, not the application team. Pull requests from CI bots are allowed; pull requests from application developers are reviewed by the platform team.
  2. The artifact digest, not the tag, is what the environment repo pins. A tag is mutable; a digest is not. The CI pipeline that builds the artifact opens a pull request against the environment repo with the new digest pinned.
  3. The application repo’s main branch is not the same Git ref as the environment repo’s main branch. They are independent refs with independent histories. Promotion is not “merge to main”; promotion is “open a PR against the environment repo”.

Cross-course references

  • Git, CI/CD & GitOps for Infrastructure Engineers - Part LXXIV (GitOps Controllers) introduces Argo CD and Flux; this part is the repository architecture decision that feeds them.
  • Kubernetes for Production Sysadmins - Part XII (Manifests) covers the manifest shape that lives in the environment repo.
  • Terraform for Production Sysadmins - Part XV (Modules) covers the module split that is the Terraform analogue of the application/env repo split.

Quiz

Knowledge check · 4 questions

  1. Q1. In a two-repo GitOps design, which repository is the cluster's source of truth for what is running in production?

  2. Q2. Putting application code and Kubernetes manifests in the same repository under a `deploy/` directory is a fine starting point that scales without changes to production.

  3. Q3. Name the two repositories in the two-repo pattern and identify what artifact is written by CI into the environment repo to record a new release.

  4. Q4. Diagnose why a production change was applied without an audit trail and recommend the architectural fix.

    A team ships a hotfix by editing the Kubernetes manifests directly in the application repo's `main` branch and having the CI pipeline `kubectl apply` from the same repo. Six months later, a security review asks who changed the production replica count and the team cannot answer because the change is buried in 4,000 application commits and was never tag-pinned.

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