Git, CI/CD & GitOpsLXXII · GitOps FoundationsFoundations
GitOps versus traditional CD — the push model, the pull model, and the trust difference
What you'll learn
- Compare GitOps and traditional CI/CD across credentials, blast radius, drift, and audit
- Identify the failure modes that each model handles well and handles poorly
- Recognise when a push-based CD pipeline is the right tool (and when it is not)
- Place GitOps as one operational model among several, not as a universal replacement
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
This lesson places GitOps next to traditional CI/CD and names the differences that matter in production. They are not competitors; they are different operational models with different trade-offs. A team that runs both is not being inconsistent - it is picking the right tool for each system.
The two models in one picture:
flowchart LR
subgraph CD["Traditional CI/CD (push)"]
A1["Developer"] -->|"push commit"| A2["CI runner"]
A2 -->|"hold cluster credentials"| A3["Cluster API"]
end
subgraph GO["GitOps (pull)"]
B1["Developer"] -->|"push commit"| B2["Git repository"]
B2 -->|"read by"| B3["Controller in cluster"]
B3 -->|"apply via in-cluster identity"| B4["Cluster API"]
end
In the traditional model, the CI runner reaches out to the cluster with write credentials. In the GitOps model, the controller inside the cluster reaches out to Git on a read path and writes to the cluster with an in-cluster identity. The arrows point in opposite directions; the trust boundaries move accordingly.
Dimensions of comparison
Seven dimensions make the differences concrete. The framing is intentionally practical - what does each model do well, and what does each one handle poorly?
| Dimension | Traditional CI/CD | GitOps |
|---|---|---|
| Credentials on CI runner | Cluster write credentials | Git write credentials only |
| Blast radius of CI compromise | Cluster write access | Git write access (read-only by controller) |
| Drift correction | None (one-shot apply) | Continuous reconcile |
| Audit trail | CI logs + Git history | Git history + controller events |
| Deployment latency | Seconds to minutes (push) | Polling interval (seconds to minutes) |
| Failure mode | Silent drift after the run | Non-empty diff as a signal |
| Operational learning curve | CI pipeline + secret management | CI pipeline + controller + repo model |
The dimensions are not “traditional is bad, GitOps is good”. They are different trade-offs.
Where traditional CI/CD is the right tool
Three cases where a push-based pipeline is the better fit:
- One-shot data migrations. A pipeline that runs
psql -c "ALTER TABLE..."once and exits is not a candidate for a loop. The change is not described declaratively, the diff is not computable, and the operation is not idempotent in the sense the loop needs. A CI job that runs once and exits is the right model. - External-system writes that have no loop. A pipeline that updates a SaaS API (DNS, CDN, support ticket) cannot be reconciled by a controller inside the cluster - the state lives in someone else’s system. A push-based job that records the new state in Git afterwards is the only model.
- Latency-sensitive hot paths. A pipeline that pushes a fix to production within 30 seconds of a merge has lower latency than a controller that polls every 60 seconds. For some changes, push is faster.
Where GitOps is the right tool
Three cases where a pull-based controller is the better fit:
- Long-lived stateful systems. A cluster running workloads that must match a description at all times - not just at deploy time - needs the loop. A push that runs once does not protect against drift between deploys.
- High-blast-radius environments. A production cluster
that holds PII, payments, or anything where a manual
kubectl editis a security incident benefits from the controller as the only writer. The cluster becomes a sealed system. - Audit-heavy regulated environments. Industries that must answer “what ran in production on date X?” benefit from the GitOps model because the answer is always the commit at the production ref on that date. No reconciliation with separate state files is needed.
Failure modes each model handles poorly
Both models have failure modes they handle badly. Naming them helps pick the right tool:
Traditional CI/CD:
- Silent drift. A
kubectl applysucceeds and the cluster drifts minutes later. There is no loop to correct. - Lost credentials on CI. A long-lived kubeconfig in CI is rotated; the next run fails until the secret is updated. During the window, deploys are blocked.
- Audit gap between runs. The CI log records the apply but not the cluster’s state between applys. The auditor cannot answer “what was running at 14:00?”.
GitOps:
- Repository compromise. An attacker who can write to the production ref can drive the controller to apply arbitrary state. The pull model does not protect against a writable repository.
- Reconciliation storm. A buggy change that fails to converge can produce a tight loop of retries. The cluster is healthy but the controller is producing errors.
- Slow latency for hot-fixes. A 3-minute polling interval is a 3-minute window where a hot-fix is not yet applied. The push model is better for hot-fixes; the GitOps model is better for everything else.
What the migration path looks like
A team moving from push to GitOps does not switch on a Friday. The path is incremental:
- Dual-write. The CI pipeline continues to push, but the GitOps controller is also installed and watches the same manifests. The controller reports diffs; the CI pipeline applies. The team compares what each applied.
- Controller-owned, CI observes. The controller applies; CI removes the imperative apply step and instead observes the controller. CI’s role shrinks to “produce the manifests, sign the tag”.
- CI removed. The controller is the only writer. CI’s remaining role is to test the manifests and produce the signed tag. The CI pipeline no longer holds cluster credentials at all.
The migration is a reduction of CI’s blast radius, not a replacement of CI. The CI pipeline remains as the test, build, and sign step. The apply step moves from CI to the controller.
Production discipline
- Pick the model per system, not per team. A cluster running 200 services benefits from GitOps; a SaaS API write that runs once a month does not.
- Reduce CI’s blast radius as you adopt GitOps. Every cluster credential removed from CI is a win; the goal is CI that holds Git credentials only.
- Run the loop even in environments that do not need it. A dev cluster with a controller and an automated self-heal policy is a training environment for the on-call rotation. The production cluster is not the place to learn the controller’s behaviour.
Cross-course references
- Ansible for Production Sysadmins - Parts XXXVIII-XXXIX
cover the move from push-based
ansible-playbookto pull-mode AWX; the trade-offs are the same. - Terraform for Production Sysadmins - Parts XXVIII- XXX cover the move from push-based CI applies to the operator-pattern pull mode; the trade-offs are the same.
- Kubernetes for Production Sysadmins - Parts on controllers cover the cluster-side loop that the GitOps controller relies on.
Quiz
Knowledge check · 4 questions
Q1. Which dimension of the push/pull comparison is the strongest reason to adopt a pull-based GitOps model?
Q2. A GitOps model eliminates the need for a CI pipeline because the controller applies changes directly from Git.
Q3. Name three cases where a push-based CI/CD pipeline is the better fit than a GitOps controller.
Q4. Diagnose why a team's GitOps migration is stalling at the dual-write step and identify what is missing.
Team T is migrating from a push-based CI pipeline to Flux. The CI pipeline still runs `kubectl apply -f` after every merge, and Flux is also installed but the Applications are in read-only mode. Three weeks in, the team is seeing persistent `OutOfSync` reports on Flux but the cluster is always converged to what CI just applied. Engineers are unsure which one to trust.
Passing score: 75%. Answers are checked in this browser.