Module 7: Gitops Beyond Terraform
4. *Push-based* vs. *pull-based*: two GitOps, one principle
Description
Module 1, lesson 4, left a sentence pending, on purpose: "Here, a CI pipeline (GitHub Actions) pushes the change outward when it detects a push to main — this is called push-based. In Kubernetes's model with ArgoCD or Flux, an operator running inside the cluster constantly watches the Git repository and pulls changes inward when it detects them — this is called pull-based." You already built the complete push mechanism (Modules 3 and 5) and already met, by name and exact shape, the pull mechanism (previous lesson). This lesson closes that thread: it shows you, with a diagram and a table of concrete implications —including the security one, the more important of the two—, why they're two genuinely different mechanisms that fulfill the same GitOps principle.
Connection to the module
This lesson depends directly on the previous one: without knowing ArgoCD/Flux's real shape (lesson 3), the push vs. pull distinction would be abstract. With that shape already known, this lesson can be concrete and compare, line by line, your own apply.yml (Module 5) against an ArgoCD Application's syncPolicy.automated. It's the more "pure conceptual" of the two lessons that revisit Module 1 —lesson 2 revisited the market evidence, this one revisits the technical distinction— and closes, with this, this entire guide's last open thread. Lesson 5 moves into new territory: what happens after the desired state is already in the cluster (deployment strategies), a problem neither push nor pull solve on their own.
Analogy: the letter at your door versus the trip to the mailbox
Imagine two ways of receiving mail. In the first, a mail carrier walks up to your door every day and delivers the mail directly into your inbox — you do nothing, the mail arrives on its own, pushed from outside into your house. In the second, no mail carrier knocks on your door: there's a shared mailbox on the corner, and it's you who walks over, checks whether there's anything new, and if there is, brings it inside. In both cases the same letter ends up in your hands —the final result is identical—, but the direction of movement is opposite: in the first, someone from outside pushes toward your house; in the second, you —from inside— go looking outward.
That's, precisely, the difference between push-based GitOps (the first: apply.yml pushes the change toward AWS when it detects a push to main) and pull-based GitOps (the second: ArgoCD, running inside the cluster, goes looking for the change in the Git repository at set intervals). The final destination —infrastructure or cluster updated per what Git says— is the same in both cases. What changes is who initiates the movement, and as you'll see below, that difference isn't just semantic: it has a real, measurable security consequence.
The mechanism, side by side
PUSH-BASED GITOPS (this guide, Modules 3 and 5)
Developer GitHub CI Pipeline AWS
│ │ │ │
│──git push─────────▶│ │ │
│ │──triggers apply.yml────▶│ │
│ │ │──terraform apply──▶│
│ │ │ (CREDENTIALS │
│ │ │ travel │
│ │ │ OUTWARD) │
│ │ │◀───confirmation────│
│ │◀────state updated───────│ │
The PIPELINE initiates the movement. An event (push to main) triggers it.
The pipeline needs credentials to write to AWS — the credentials
travel OUTWARD from the repository, toward the cloud provider.
PULL-BASED GITOPS (ArgoCD/Flux, Kubernetes — named, not built here)
Developer GitHub ArgoCD/Flux K8s Cluster
│ │ (runs INSIDE │
│ │ the cluster) │
│──git push─────────▶│ │ │
│ │◀──── watch/poll every N minutes ───────────│
│ │ (the operator COMES looking, │
│ │ nobody pushes it) │
│ │────current repository▶│ │
│ │ │──applies inside─▶│
│ │ │ the SAME │
│ │ │ cluster it │
│ │ │ already runs in │
The OPERATOR initiates the movement. A timer (interval) triggers it,
not an external event. The operator already lives inside the cluster it's
going to modify — it doesn't need credentials traveling in from anywhere.
The central difference, in one sentence: in push, something external to the destination (a CI pipeline, running on a runner that isn't part of your AWS infrastructure) initiates the change and needs permission to get in. In pull, something that already lives inside the destination (a controller running as Pods in the same cluster it manages) initiates the change on its own, with nothing external triggering it.
The complete sequence, in a flow diagram
sequenceDiagram
participant Dev as Developer
participant Git as Git Repository
participant CI as CI Pipeline (push)
participant AWS as AWS (destination, push)
participant Op as GitOps Operator (pull)
participant K8s as K8s Cluster (destination, pull)
Dev->>Git: git push (approved change)
rect rgb(235, 245, 255)
Note over Git,AWS: PUSH mechanism — this guide
Git-->>CI: push event triggers apply.yml
CI->>AWS: terraform apply (credentials travel outward to AWS)
AWS-->>CI: infrastructure updated
end
rect rgb(255, 245, 235)
Note over Op,K8s: PULL mechanism — ArgoCD/Flux
Op->>Git: poll every N minutes (the operator asks)
Git-->>Op: repository's latest state
Op->>K8s: applies the state (already lives inside, no external credentials)
K8s-->>Op: state reconciled
end
Notice the timing detail: in push, the change applies almost immediately after the push to main — it's reactive, triggered by the event. In pull, the change applies at some point within the next interval (Flux's GitRepository's 5m0s you saw in the previous lesson, for example) — it's periodic, not instant. This latency difference is real and sometimes matters: a push pipeline can deploy in seconds; a typical pull operator takes, in the worst case, up to a full interval — although both ArgoCD and Flux also support triggering an immediate sync via webhook, to reduce that latency when needed.
The security implication, the difference that matters most
Here's the technical reason the Kubernetes industry, specifically, prefers pull over push for its own deployments — it's not an aesthetic preference, it's a different attack surface:
| Push-based (this guide) | Pull-based (ArgoCD/Flux) | |
|---|---|---|
| Who holds write credentials toward the destination? | The CI pipeline (a system external to AWS) | Nobody external — the operator already lives inside the cluster it manages |
| Where do those credentials live? | As a GitHub Actions secret (Module 4), injected into every run | No "external" credentials exist — the operator uses the ServiceAccount it already runs with inside the cluster |
| If the CI pipeline gets compromised | An attacker with access to the pipeline can use those credentials to write to AWS directly | Doesn't apply — there are no write credentials to steal from that pipeline toward the outside |
| Attack surface | The Git repository and the CI system and the credentials channel between the two | Only the Git repository — the operator never exposes write credentials outside the cluster |
This doesn't mean push is "insecure" — you yourself built, in Module 4, the entire OIDC model (named) that solves exactly this problem: short-lived credentials, no long-lived secrets stored, federated right at the moment the pipeline needs them. It means pull eliminates an entire category of risk by design —there are never write credentials toward the cluster traveling outside it—, while push manages it with good practices (OIDC, rotatable secrets, minimal permissions) instead of eliminating it at the root. It's the real technical reason, not a trend, behind why production Kubernetes leans toward pull, and why this distinction is worth having clear if you ever have to justify, in a real design, why you chose one mechanism or the other.
GitOps's four properties, confirmed in both mechanisms
Go back to Module 1, lesson 4's four properties — declarative, Git as source of truth, automatic application, continuous reconciliation. Neither push nor pull gains or loses points on that list: all four hold in both cases, only how changes.
| Property | How push fulfills it (this guide) | How pull fulfills it (ArgoCD/Flux) |
|---|---|---|
| Declared desired state | Terraform HCL | Kubernetes manifests (Deployment YAML, etc.) |
| Git as the single source of truth | Reviewed PR before merging to main | Same — reviewed PR before merging |
| Automatic application of approved changes | apply.yml, triggered by push to main | The operator, triggered by its own interval |
| Continuous reconciliation | drift.yml, scheduled, detects and warns | selfHeal: true, continuous, detects and fixes |
The last row is the most concrete difference you already saw in the previous lesson: both mechanisms reconcile, but pull typically does it more aggressively (automatic correction) because the operator is designed to run indefinitely, comparing state nonstop — while drift.yml, a scheduled job inside a CI system meant for point-in-time runs, more naturally stops at "warning" and leaves the fix for an apply reviewed like any other change.
Common mistakes
Thinking "pull is always better than push" (hasty-generalization-based). What happens: someone, after reading this lesson's security section, concludes push is an inferior choice and that this guide should have used pull from the start. Why it happens: the security table above shows a real advantage for pull, and it's easy to generalize "less attack surface" into "always better." How to spot it: if your conclusion is that this guide's design "should change" to pull. How to fix it: pull solves one problem (credential attack surface) at the cost of requiring a running Kubernetes cluster, with a dedicated operator, with its own maintenance — it's not an option available for non-Kubernetes infrastructure, like the S3, DynamoDB, IAM, and Lambda andes-cargo-infra/ manages. There's no widely adopted "ArgoCD for Terraform against plain AWS" equivalent — push remains, today, the standard industry mechanism for IaC outside Kubernetes.
Confusing "the operator lives inside the cluster" with "it needs no permissions at all" (security-based, subtle). What happens: someone interprets the security table as if ArgoCD/Flux needed no credentials whatsoever. Why it happens: the row says "nobody external holds write credentials," and it's easy to read that as "no credential is needed at all." How to spot it: if you think ArgoCD needs no configured access at all. How to fix it: the operator does need permissions —a Kubernetes ServiceAccount with a Role/ClusterRole letting it create, update, and delete the resources it manages inside the cluster—, and also read credentials toward the Git repository (if it's private). What it eliminates is the need for external write credentials toward the destination, traveling from a system outside the cluster — it doesn't eliminate the need for permissions in general.
Believing this guide "isn't real GitOps" for being push-based (already seen in Module 1, reinforced here). What happens: someone, now with the complete technical detail of pull, doubts again whether Andes Cargo's pipeline "counts" as GitOps. Why it happens: Kubernetes's example is the most cited in the industry when someone says "GitOps," and it's easy to assume it's the only valid one. How to spot it: if after this lesson you still think push is "second-class GitOps." How to fix it: the four-properties table above confirms it precisely — all four hold in both mechanisms. Push and pull are two valid technical implementations of the same principle, not a "complete" version and an "incomplete" one of GitOps.
Exercises
Exercise 1 — Direction of movement and credentials, unaided. Without looking at this lesson, draw (on paper or in text) the two credential arrows: which way do write credentials travel in push, and which way in pull?
See solution
Push: credentials travel outward from the CI system, inward into AWS — the pipeline (external to AWS) needs to authenticate against AWS to be able to write there. Pull: no write credentials travel between systems — the GitOps operator already runs inside the cluster it manages, so it uses the ServiceAccount it's already locally authenticated with, with no credential needing to "come in" from outside. The only credential that does cross a boundary in pull is a read one, from the operator toward the Git repository — never a write one toward the cluster.
Exercise 2 — Apply the distinction to a new scenario. A colleague tells you: "I want to deploy my Kubernetes application automatically when I merge a PR, using GitHub Actions with a kubectl apply inside the workflow, instead of installing ArgoCD." Is that design push or pull? Justify your answer.
See solution
It's push-based, even though the destination is a Kubernetes cluster. What defines the mechanism isn't "whether the destination is Kubernetes or not" — it's who initiates the movement and from where. A GitHub Actions workflow that runs kubectl apply is running outside the cluster (on a GitHub runner), and needs credentials (a kubeconfig with cluster access) that travel inward when the pipeline gets triggered by a push/merge event — exactly the same shape as this guide's apply.yml, just with the destination being a Kubernetes cluster instead of AWS via Terraform. It's a valid, in fact common in practice, push design — choosing to use ArgoCD/Flux instead of this is, precisely, the choice to move to pull for that same destination.
Exercise 3 — Explain why this guide can't become "pull" without changing destinations. In two or three sentences, explain why Andes Cargo's pipeline —which manages S3, DynamoDB, IAM, and Lambda via Terraform— couldn't turn into pull-based just by installing ArgoCD.
See solution
A complete explanation sounds, roughly, like this: "ArgoCD and Flux are operators that run inside a Kubernetes cluster, and their pull mechanism depends on that cluster existing as the destination — 'living inside the destination they manage' is literally the property that eliminates the need for external credentials. Andes Cargo's infrastructure (S3, DynamoDB, IAM, Lambda) isn't a Kubernetes cluster: it's a set of AWS services managed via API, with nowhere 'inside' for an operator to run and continuously watch. For this pipeline to be pull-based, something would need to exist running permanently against the AWS account watching its state — a pattern that exists (some commercial products offer it), but isn't the standard, widely adopted mechanism ArgoCD/Flux is for Kubernetes."
Summary and next step
In this lesson you closed the thread opened in Module 1: you confirmed, with a sequence diagram and a table of concrete security implications, that push-based (this guide's pipeline) and pull-based (ArgoCD/Flux) are two genuinely different technical mechanisms —who initiates the movement, which way credentials travel, how much latency a change applies with— that fulfill exactly the same four GitOps properties. You understood why production Kubernetes leans toward pull (it eliminates an entire category of credential risk) without that making push a second-class implementation.
Before moving on you should be able to: draw from memory the direction of credentials in both mechanisms; explain the latency difference between a reactive pipeline and an operator with an interval; and defend, with the four-properties table, why Andes Cargo's pipeline is complete GitOps even though it's push.
Lesson 5 moves away from GitOps itself and into new territory: what happens to real user traffic when an application gets deployed — blue/green, canary, rolling deploys — a problem neither push nor pull solve on their own, because it lives one level down, in how a running artifact's versions get replaced.
Resources
- Weaveworks Blog — What Is GitOps, Really? — the term's origin, already cited in Module 1, lesson 4, the basis for the four properties' definition used in this lesson.
- Argo CD — Documentation — official documentation, including the
syncPolicy.automatedandselfHealsection referenced in this lesson. - Flux — Concepts — official documentation for Flux's continuous reconciliation mechanism.
- GitHub Docs — About continuous deployment — the push mechanism this guide implemented in Modules 3 and 5, this lesson's point of comparison.
terraform-and-iac-guide, Module 4 (AWS secrets and credentials management) — the foundation for why OIDC (this guide's Module 4) is push's answer to the security problem pull solves by design.