GitOps with ArgoCD & Flux
Git as the source of truth for infrastructure. Continuous reconciliation, audit trails, declarative everything.
What GitOps Means
GitOps is a discipline: the desired state of every system lives in Git. Software running in clusters reconciles toward that state continuously.
Old model — push:
Engineer → CI runs kubectl apply → cluster
GitOps — pull:
Engineer → commits to Git → ArgoCD/Flux notices → applies to cluster
The differences are subtle but important:
1. Git is the source of truth. Whatever is in main is what should be running. Period.
2. Drift detection. If someone manually changes a deployment in the cluster, ArgoCD notices and flags (or reverts) the drift.
3. No CI access to production. CI doesn't need cluster credentials. The cluster pulls — it has its own identity.
4. Audit trail by default. Every change is a Git commit. Want to know what's running and why? git log.
5. Declarative everywhere. You describe what should exist, the system makes it so.
The two main tools: ArgoCD and Flux. Both CNCF-graduated. Both are excellent. The choice is mostly preference.
ArgoCD — UI-First GitOps
ArgoCD has a great web UI showing what's deployed, sync status, recent commits.
Setup:
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
Define what to sync — an Application:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/myorg/myapp-config
path: k8s/production
targetRevision: main
destination:
server: https://kubernetes.default.svc
namespace: myapp
syncPolicy:
automated:
prune: true # delete resources no longer in Git
selfHeal: true # revert drift back to Git state
syncOptions:
- CreateNamespace=true
Once applied, ArgoCD watches the Git repo. New commits → sync to cluster. Manual changes in cluster → reverted to match Git.
The UI shows:
• Every Application's sync status (in sync, out of sync, syncing)
• Diff between Git and cluster
• Resource tree (Deployment → ReplicaSet → Pods)
• Recent events and logs
Good for: teams that value visibility, multi-cluster setups, large numbers of applications.
Flux — Lighter, More Modular
Flux is the other major GitOps tool. Smaller footprint, no UI by default (you can use Weave GitOps or other UIs).
Setup with the Flux CLI:
flux bootstrap github \
--owner=myorg \
--repository=fleet-config \
--branch=main \
--path=clusters/production
This creates a Git repo, installs Flux in the cluster, and configures Flux to watch that repo.
Then you commit Kubernetes manifests, Helm charts, or Kustomize overlays. Flux applies them.
Helm release via Flux:
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: myapp
namespace: production
spec:
interval: 5m
chart:
spec:
chart: myapp
version: '1.x'
sourceRef:
kind: HelmRepository
name: my-helm-repo
values:
replicas: 5
image:
tag: v1.2.3
Image automation — Flux can also automatically update Helm values when new images are pushed. You commit the new tag back to Git, completing the GitOps loop.
ArgoCD vs Flux:
• Want a UI: ArgoCD
• Want lightweight, CLI-driven: Flux
• Both work well. Try both, pick what fits your workflow.
Repo Structure for GitOps
Two patterns dominate:
Mono-repo — all environment configs in one repo
fleet-config/
base/ # base manifests
deployment.yaml
service.yaml
overlays/
dev/
kustomization.yaml
staging/
kustomization.yaml
production/
kustomization.yaml
ArgoCD/Flux applies the right overlay to the right cluster.
App-of-apps — one Application that points to many Applications
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: production-apps
spec:
source:
repoURL: https://github.com/myorg/fleet-config
path: production-apps # this dir contains many Application YAMLs
Adding a new app = committing a new Application to that directory. ArgoCD picks it up automatically.
Per-app repos vs central repo
• Central — easier governance, single source of truth
• Per-app — teams move independently, more autonomy
Most teams: central config repo, separate from app code repos. App code repo CI builds image and pushes a tag. A bot opens a PR to the central config repo bumping the tag. Reviewer merges → ArgoCD deploys.
Image Updates — Closing the Loop
Without image automation, GitOps requires a manual step: code repo merges → image built → human edits config repo to bump tag → ArgoCD deploys.
Better: automate the bump.
Approaches:
1. ArgoCD Image Updater — watches a registry, updates Git automatically when new images appear.
2. Flux Image Automation — same idea. Flux's built-in feature.
3. CI commits to config repo — your app's CI does it after pushing the image:
- name: Update config repo
run: |
git clone https://github.com/myorg/fleet-config
cd fleet-config/production
yq e -i '.image.tag = "${{ github.sha }}"' values.yaml
git commit -am "bump production to ${{ github.sha }}"
git push
This last approach is simple and explicit. The CI bumps the tag; the GitOps tool deploys.
For mature setups, automation policies define WHICH images get auto-bumped:
• Production: only after manual approval (human merges PR)
• Staging: auto-merge if image scan passes
• Dev: auto-merge always
Multi-Cluster GitOps
As you scale, you may have multiple clusters: per-region, per-team, per-environment. GitOps scales naturally.
ArgoCD: one ArgoCD instance can manage many clusters. Add clusters as resources, then target Applications at specific clusters.
Flux: each cluster runs its own Flux. Each cluster's repo is shaped to its needs.
Patterns:
• Hub-and-spoke — one central ArgoCD manages everything
• Federated — each cluster has its own ArgoCD/Flux
• Hybrid — central for shared services, local for app-specific
For very large fleets (hundreds of clusters), specialized tools:
• Rancher Fleet
• ArgoCD ApplicationSets
• KubeVela
The big win of GitOps + multi-cluster: every cluster's state is in Git. Reproducing a cluster (after a disaster, or for testing) is git clone + bootstrap. Cattle, not pets, applied to entire clusters.
⁂ Back to all modules