GitOps with ArgoCD & Flux

The GitOps Model

18 min Lesson 1 of 30

The GitOps Model

GitOps is an operational framework that takes DevOps best practices — version control, collaboration, and CI/CD — and applies them directly to infrastructure and application delivery. The central principle is deceptively simple: Git is the single source of truth for the entire desired state of your system. If it is not in Git, it does not exist.

At Google, Meta, and every major cloud-native shop, the infrastructure that runs production is described as code in repositories. A cluster running in production is not the product of someone running kubectl apply on their laptop. It is the product of a continuous reconciliation loop that reads Git and makes the live cluster match what Git says it should look like. This is the GitOps model.

The Two Deployment Paradigms: Push vs Pull

To understand why GitOps matters, you first need to feel the pain of the alternative — the classic push-based deployment model used by traditional CI/CD pipelines.

In a push model, your CI system (Jenkins, GitHub Actions, a shell script) holds the deployment credentials and actively reaches out to the cluster. After a build succeeds, the pipeline runs kubectl apply or helm upgrade, pushing the new manifests directly onto the cluster. This works, but it creates a set of structural problems:

  • Credentials leak surface: your CI runner needs wide cluster credentials — often cluster-admin — that can be stolen, rotated incorrectly, or scoped wrong.
  • Drift is invisible: someone with kubectl access can change a Deployment replica count directly on the cluster. The CI system has no idea. Git and reality diverge silently.
  • Auditability is fragile: you have to reconstruct what was deployed by digging through pipeline logs and cross-referencing timestamps. There is no single log of desired state over time.
  • Disaster recovery is slow: rebuilding a cluster from scratch requires replaying pipelines in the right order, hoping all dependencies are still available.
Push-based vs Pull-based deployment model PUSH MODEL Developer git push Git Repo app code CI/CD Runner holds credentials K8s Cluster live state kubectl apply Manual Change → silent drift PULL MODEL (GitOps) Developer Pull Request Config Repo desired state GitOps Agent ArgoCD / Flux K8s Cluster live state polls / watches reconcile Drift Detected auto-reverted Reconciliation Loop (continuous): 1. Agent reads desired state from Git (every 30–180 s by default) 2. Agent compares desired state with live cluster state (diff) 3. If diff detected → apply corrective actions → cluster converges to Git state
Push model (top) vs Pull-based GitOps model (bottom) — the agent inside the cluster reconciles continuously.

Pull-Based Reconciliation: The Core Mechanism

In the GitOps pull model, a dedicated agent — ArgoCD or Flux — runs inside the cluster. It continuously polls (or receives webhook notifications from) the Git repository and compares the declared state in Git against the live state of the cluster. When it detects a difference (called drift), it applies the corrective changes itself. The cluster operator never needs to reach in from outside.

This inversion has enormous practical consequences:

  • No outbound credentials: the cluster does not expose credentials to CI. The agent uses in-cluster service accounts. Your pipeline only needs write access to Git — not to the cluster.
  • Automatic drift correction: if an engineer runs kubectl edit deployment/api --replicas=5 on production at 2 AM during an incident, the GitOps agent will notice and revert it to the value in Git within minutes — unless you explicitly annotate it as an exception.
  • Git history is the audit log: every change to production is a git commit. git log, git blame, and pull request reviews are now your change management system.
  • Disaster recovery is a git clone: to rebuild a cluster from scratch, point a fresh agent at the same repo. The entire desired state is there.
The Four GitOps Principles (OpenGitOps v1.0):
  1. Declarative: the entire system must be described declaratively. You say what you want, not how to achieve it.
  2. Versioned and immutable: desired state is stored in a version-control system that enforces immutability and retains a complete history.
  3. Pulled automatically: software agents automatically pull the desired state declarations from the source.
  4. Continuously reconciled: agents continuously observe the actual system state and attempt to apply the desired state.

What Goes in the Git Repository

A GitOps repository holds the desired state of your system — Kubernetes manifests, Helm chart values, Kustomize overlays, or a mix of all three. It does not hold application source code (that stays in the app repo). This separation is deliberate: you want to be able to update which image tag is deployed without triggering a full application build.

A minimal starting structure looks like this:

# gitops-config/ — the config repo (separate from your app repo) . ├── apps/ │ ├── base/ │ │ └── api-service/ │ │ ├── deployment.yaml │ │ ├── service.yaml │ │ └── kustomization.yaml │ └── overlays/ │ ├── staging/ │ │ └── kustomization.yaml # patches: replicas=1, image tag │ └── production/ │ └── kustomization.yaml # patches: replicas=3, image tag └── clusters/ ├── staging/ │ └── apps.yaml # ArgoCD Application pointing to overlays/staging └── production/ └── apps.yaml # ArgoCD Application pointing to overlays/production

A concrete Kubernetes Deployment manifest in the base directory:

# apps/base/api-service/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: api-service labels: app: api-service spec: replicas: 1 # overridden per environment in overlays selector: matchLabels: app: api-service template: metadata: labels: app: api-service spec: containers: - name: api image: ghcr.io/myorg/api-service:latest # overridden by image tag automation ports: - containerPort: 8080 resources: requests: cpu: "100m" memory: "128Mi" limits: cpu: "500m" memory: "256Mi"
Production practice — separate app repo from config repo: At scale, teams maintain one repository per service for source code and one shared "platform" config repo (or per-team config repos). The CI pipeline's only GitOps job is to open a PR updating the image tag in the config repo. A human (or automated policy gate) merges it. This gives you a clean, auditable promotion path: merge the PR → ArgoCD/Flux picks it up → cluster updates. Never let your CI pipeline push directly to main of the config repo without a review gate in production.

Reconciliation in Practice

When you commit a change to the config repo, here is the sequence of events in a pull-based system:

  1. You open a PR changing image: ghcr.io/myorg/api-service:v1.4.2 to :v1.5.0. A teammate approves and merges.
  2. The GitOps agent (running in the cluster) detects the new commit — either by polling on an interval (default: 3 minutes in ArgoCD, 1 minute in Flux) or via a Git webhook that triggers an immediate sync.
  3. The agent computes the diff between what is in Git and what is currently running in the cluster.
  4. The agent applies kubectl apply (or the equivalent Helm/Kustomize operation) internally, targeting only the resources that changed.
  5. Kubernetes performs the rolling update. The agent waits and reports health status back to its dashboard.
Do not mix GitOps and manual kubectl changes in the same cluster. If your team merges a GitOps agent but engineers continue to apply manifests by hand, you will have constant reconciliation fights where the agent reverts manual changes. Establish the rule on day one: all cluster changes go through Git. Use RBAC to remove direct kubectl apply permissions from human users in production — force changes through the PR process.

Why GitOps Is the Standard at Scale

Push pipelines can work for small teams with a single cluster and one environment. They fall apart when you have 50 microservices, 5 environments, and a 200-person engineering org. GitOps solves the coordination problem: every team's desired state is in one place, reconciled continuously, with a complete history of every change and who approved it. Compliance and audit requirements that used to require custom tooling become trivially satisfiable — just show the git log.

The next lesson covers how to design the Git repository structure itself — mono-repo vs multi-repo, folder conventions, and branching strategies that work with GitOps at the scale of a real engineering organization.