Push vs pull
You wake up to a deleted deployment and the pager already ringing. Someone with prod access ran a delete by hand at 3am, and now the service is down. Whether it comes back, and who brings it back, is the difference between two ways of running GitOps.
Everyone has seen the old model. The pipeline runs outside the cluster. It holds a
kubeconfig, often with cluster admin, and it applies manifests at the cluster with
kubectl apply or helm upgrade. It works. It shipped a lot of
software. And it has three structural problems that GitOps exists to fix.
The credentials are the crown jewel
In the push model, your CI system holds the keys to the cluster. Every pipeline runner has network path to the API server and credentials that can change production. That means your cluster's security is downstream of your CI system's security, and CI systems are juicy targets, full of third party dependencies and long lived tokens.
Pull inverts this. The controller lives inside the cluster. It holds the credentials, and it reaches out to Git, not the other way around. Your pipeline only needs write access to the repository. If CI is compromised, the worst case is a bad commit, which gets reviewed and reverted. There are no cluster keys outside the cluster.
That's not a small difference. It changes what the blast radius of a CI compromise even is.
Push runs once and forgets
The second problem is the one people notice at 3am. A push pipeline runs, applies the manifest, and leaves. Nothing watches afterward. If a pod dies, if someone hand runs a delete, if a resource is missing, nothing brings it back. The cluster's state is whatever the last push left it.
The pull model runs a reconciliation loop forever. The controller watches Git and the cluster, and when they disagree, it converges. Someone deletes a Deployment that Flux manages, Flux recreates it. A pod crashes, it comes back. The drift is corrected continuously, not at the next deploy.
Here's the 3am test. Someone with prod access runs
kubectl delete deploy payment. Push model. The deletion sticks,
the service stays down, users error, and that is what wakes you up. Pull model. The
controller quietly recreates it at the next reconcile, and you find out in the
morning via logs. The pull model did not just fix it faster. It removed the incident
class entirely.
Drift is invisible until it isn't
The third problem is drift. Git says one thing, the cluster is actually another, and nobody notices until the next deploy surfaces it, or until it surfaces as an incident. In the pull model, drift is the thing the controller exists to find. It is not an exceptional state, it's the daily job.
The consequences of that show up in the details. In a pull model, a pipeline that dies mid deploy is fine, the controller converges to Git state eventually. You don't get a permanently half applied cluster. And because Git is the source of truth, what Flux applied from Git gets drift corrected and pruned when it leaves the repo. One honest caveat. It only prunes what it previously applied. Something hand created that was never in Git is invisible to it, which is another reason admission policy and audits matter.
The phrase that captures it
Push the image, pull the config. CI still builds and pushes images, that part stays push. Only the apply moves to pull, and that's the part that was fragile.
GitOps is not a new deployment tool. It's a different contract with the cluster, where the cluster is the one asking for state, not the one being told. That contract is what makes self healing, auditability, and blast radius reduction possible, and none of those are features you bolt on. They fall out of the direction of the pull.
The boring conclusion
None of this means push pipelines are evil. They shipped the internet. But the questions GitOps answers are the ones push was never designed to answer. Who holds the keys, who notices drift, and who owns the state when the pipeline is gone.
When was the last time a hand-run kubectl delete fixed itself before you got the page?