GitOps
GitOps is a deployment practice where Git is the single source of truth for your infrastructure and application state. Instead of running kubectl apply or helm install from a CI pipeline, a GitOps operator running in the cluster watches a Git repository and automatically synchronizes the cluster to match the desired state in Git.
Why GitOps?
Section titled “Why GitOps?”| Traditional CI/CD Deploy | GitOps |
|---|---|
| CI pipeline pushes to the cluster | Cluster pulls from Git |
| Pipeline needs cluster credentials | Only the in-cluster operator needs access |
| ”What’s running?” → check the cluster | ”What’s running?” → check Git |
| Drift goes unnoticed | Drift is auto-corrected |
| Rollback = rerun old pipeline | Rollback = git revert |
| Audit trail in CI logs | Audit trail in Git history |
Push vs Pull Model
Section titled “Push vs Pull Model”Push-based (traditional CI/CD): Developer ──► Git ──► CI Pipeline ──► kubectl apply ──► Cluster
Pull-based (GitOps): Developer ──► Git ◄── GitOps Operator (in cluster) ──► reconcile ──► Cluster| Push-Based | Pull-Based (GitOps) | |
|---|---|---|
| Who deploys | CI pipeline | In-cluster operator |
| Cluster credentials | Stored in CI (secret) | Only the operator has access |
| Drift detection | Manual or none | Continuous (operator watches) |
| Security | CI needs write access to cluster | Cluster pulls (no inbound access) |
| Tools | GitHub Actions, GitLab CI, Jenkins | ArgoCD, Flux |
ArgoCD
Section titled “ArgoCD”ArgoCD is the most popular GitOps tool for Kubernetes. It’s a CNCF graduated project that runs as a controller in your cluster and continuously syncs applications from Git.
How ArgoCD Works
Section titled “How ArgoCD Works”Git Repository (desired state) │ │ ArgoCD watches for changes ▼ArgoCD Controller (in cluster) │ │ Compares desired state (Git) vs live state (cluster) │ ├── In Sync ──► nothing to do │ └── Out of Sync ──► reconcile (apply changes to cluster)Core Concepts
Section titled “Core Concepts”| Concept | What It Is |
|---|---|
| Application | A CRD that maps a Git repo path to a Kubernetes namespace |
| Project | Groups applications with shared access policies and allowed destinations |
| Sync | Apply the desired state from Git to the cluster |
| Health | Whether the deployed resources are running correctly |
| Refresh | Check Git for changes (automatic or manual) |
Installing ArgoCD
Section titled “Installing ArgoCD”# Install ArgoCD in the clusterkubectl create namespace argocdkubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# Install the CLIbrew install argocd
# Get the initial admin passwordargocd admin initial-password -n argocd
# Loginargocd login localhost:8080Defining an Application
Section titled “Defining an Application”apiVersion: argoproj.io/v1alpha1kind: Applicationmetadata: name: myapp namespace: argocdspec: project: default
source: repoURL: https://github.com/myorg/k8s-config.git targetRevision: main path: apps/myapp/overlays/production # Path in the repo
destination: server: https://kubernetes.default.svc namespace: myapp
syncPolicy: automated: prune: true # Delete resources removed from Git selfHeal: true # Auto-correct manual changes (drift) syncOptions: - CreateNamespace=true retry: limit: 3 backoff: duration: 5s factor: 2 maxDuration: 3mSync Policies
Section titled “Sync Policies”| Policy | What It Does |
|---|---|
| Manual sync | User clicks “Sync” in the UI or CLI |
| Auto sync | ArgoCD syncs whenever Git changes |
| Self-heal | Auto-correct drift (someone kubectl edit-ed a resource) |
| Prune | Delete resources that were removed from Git |
ArgoCD CLI
Section titled “ArgoCD CLI”# List applicationsargocd app list
# Sync an application (deploy)argocd app sync myapp
# Get application statusargocd app get myapp
# View diff (what would change)argocd app diff myapp
# Rollback to a previous Git revisionargocd app rollback myapp --revision 3
# Hard refresh (re-read from Git)argocd app get myapp --hard-refreshApp of Apps Pattern
Section titled “App of Apps Pattern”Manage many applications with a single “parent” Application that points to a directory of Application manifests:
k8s-config/ ├── apps/ │ ├── myapp/ │ ├── api-service/ │ └── worker/ └── argocd/ └── apps.yaml # App-of-apps: points to apps/ directory# argocd/apps.yaml — the "parent" ApplicationapiVersion: argoproj.io/v1alpha1kind: Applicationmetadata: name: all-apps namespace: argocdspec: project: default source: repoURL: https://github.com/myorg/k8s-config.git targetRevision: main path: apps # Directory containing Application manifests destination: server: https://kubernetes.default.svc namespace: argocd syncPolicy: automated: prune: true selfHeal: trueWhen you add a new Application YAML to the apps/ directory and push to Git, ArgoCD automatically picks it up.
ApplicationSet
Section titled “ApplicationSet”For generating many similar Applications from a template:
apiVersion: argoproj.io/v1alpha1kind: ApplicationSetmetadata: name: cluster-apps namespace: argocdspec: generators: - list: elements: - cluster: staging url: https://staging-k8s.example.com - cluster: production url: https://prod-k8s.example.com template: metadata: name: 'myapp-{{cluster}}' spec: project: default source: repoURL: https://github.com/myorg/k8s-config.git path: 'apps/myapp/overlays/{{cluster}}' destination: server: '{{url}}' namespace: myappFlux is another CNCF GitOps tool for Kubernetes. It’s a set of controllers that watch Git repositories, Helm charts, and OCI artifacts and reconcile them to the cluster.
Flux Architecture
Section titled “Flux Architecture”Git Repository │ ▼Source Controller ──► fetches manifests from Git, Helm repos, OCI │ ▼Kustomize Controller ──► applies Kustomize overlays to the clusterHelm Controller ──► installs/upgrades Helm releases │ ▼Notification Controller ──► sends alerts (Slack, Teams, webhook)Image Automation Controller ──► updates image tags in GitInstalling Flux
Section titled “Installing Flux”# Install Flux CLIbrew install fluxcd/tap/flux
# Bootstrap Flux in the cluster (also creates the Git repo structure)flux bootstrap github \ --owner=myorg \ --repository=k8s-config \ --branch=main \ --path=./clusters/production \ --personalFlux Resources
Section titled “Flux Resources”# GitRepository — tells Flux where to fetch manifestsapiVersion: source.toolkit.fluxcd.io/v1kind: GitRepositorymetadata: name: myapp namespace: flux-systemspec: interval: 1m # Check for changes every minute url: https://github.com/myorg/k8s-config.git ref: branch: main---# Kustomization — tells Flux what to deploy from the repoapiVersion: kustomize.toolkit.fluxcd.io/v1kind: Kustomizationmetadata: name: myapp namespace: flux-systemspec: interval: 5m path: ./apps/myapp prune: true # Delete removed resources sourceRef: kind: GitRepository name: myapp healthChecks: - apiVersion: apps/v1 kind: Deployment name: myapp namespace: myappFlux with Helm
Section titled “Flux with Helm”# HelmRepository — where to find chartsapiVersion: source.toolkit.fluxcd.io/v1kind: HelmRepositorymetadata: name: bitnami namespace: flux-systemspec: interval: 1h url: https://charts.bitnami.com/bitnami---# HelmRelease — what to installapiVersion: helm.toolkit.fluxcd.io/v2kind: HelmReleasemetadata: name: redis namespace: flux-systemspec: interval: 10m chart: spec: chart: redis version: "18.x" sourceRef: kind: HelmRepository name: bitnami values: architecture: standalone auth: enabled: falseArgoCD vs Flux
Section titled “ArgoCD vs Flux”| Feature | ArgoCD | Flux |
|---|---|---|
| UI | Rich web UI + CLI | CLI only (use Weave GitOps for UI) |
| Architecture | Monolithic (single controller) | Modular (multiple controllers) |
| Multi-cluster | Built-in (single ArgoCD manages many clusters) | One Flux per cluster (or remote apply) |
| Helm support | Renders Helm templates, applies as plain YAML | Native HelmRelease CRD (proper Helm lifecycle) |
| Kustomize | Supported | Native Kustomization CRD |
| Image automation | Not built-in (use Argo Image Updater) | Built-in Image Automation Controller |
| Notifications | Built-in | Notification Controller |
| RBAC | Built-in with SSO integration | Uses Kubernetes RBAC |
| App-of-apps | Yes (Application/ApplicationSet) | Yes (Kustomization dependencies) |
| Learning curve | Lower (great UI) | Higher (CRD-based, CLI-focused) |
| CNCF status | Graduated | Graduated |
When to Choose Which
Section titled “When to Choose Which”| Scenario | Choose |
|---|---|
| Want a web UI for visibility | ArgoCD |
| Prefer Helm lifecycle (install/upgrade/rollback) | Flux |
| Managing many clusters from one place | ArgoCD |
| Want modular, composable controllers | Flux |
| Team already uses Kustomize heavily | Either (both support Kustomize) |
| Need image auto-update (tag in Git) | Flux (built-in) |
GitOps with Helm and Kustomize
Section titled “GitOps with Helm and Kustomize”GitOps tools work with your existing manifest management:
Helm Charts
Section titled “Helm Charts”Both ArgoCD and Flux can deploy Helm charts from:
- A Helm repository (e.g. Bitnami, your private chart repo).
- A chart stored in a Git repository.
- An OCI registry.
The values can be stored in Git alongside the chart reference, making the full configuration auditable.
Kustomize Overlays
Section titled “Kustomize Overlays”A common pattern — base manifests with per-environment overlays:
apps/myapp/ ├── base/ │ ├── deployment.yaml │ ├── service.yaml │ └── kustomization.yaml ├── overlays/ │ ├── staging/ │ │ ├── kustomization.yaml # Patches: 1 replica, staging ingress │ │ └── ingress-patch.yaml │ └── production/ │ ├── kustomization.yaml # Patches: 3 replicas, prod ingress, HPA │ ├── ingress-patch.yaml │ └── hpa.yamlArgoCD and Flux both detect kustomization.yaml and apply the overlay automatically.
For more on Helm and Kustomize with Kubernetes, see Helm and Helm Templating.
Repository Structure Patterns
Section titled “Repository Structure Patterns”Pattern 1: Monorepo (App + Config Together)
Section titled “Pattern 1: Monorepo (App + Config Together)”myapp/ ├── src/ # Application code ├── Dockerfile ├── .github/workflows/ # CI pipeline └── k8s/ # Kubernetes manifests (GitOps watches here) ├── base/ └── overlays/Pros: Everything in one place, simple. Cons: App code changes trigger GitOps reconciliation (noisy), tight coupling.
Pattern 2: Separate Config Repo (Recommended)
Section titled “Pattern 2: Separate Config Repo (Recommended)”# Repo 1: myorg/myapp (application code)myapp/ ├── src/ ├── Dockerfile └── .github/workflows/ci.yml # CI: build, test, push image
# Repo 2: myorg/k8s-config (deployment config)k8s-config/ ├── apps/ │ ├── myapp/ │ │ ├── base/ │ │ └── overlays/ │ ├── api-service/ │ └── worker/ └── infrastructure/ ├── cert-manager/ └── ingress-nginx/Pros: Clear separation of concerns, CI doesn’t need cluster access, config changes are auditable. Cons: Extra repository to manage, need to update image tags across repos.
Pattern 3: Config Per Environment Repo
Section titled “Pattern 3: Config Per Environment Repo”# Repo per environmentmyorg/k8s-staging/ └── apps/ └── myapp/
myorg/k8s-production/ └── apps/ └── myapp/Pros: Strongest isolation between environments, different access controls. Cons: Duplication, harder to promote changes.
Recommended: Separate Config Repo
Section titled “Recommended: Separate Config Repo”The separate config repo is the most common pattern. The CI pipeline (in the app repo) builds and pushes the image, then updates the image tag in the config repo:
App repo (CI): push ──► build ──► test ──► docker build + push ──► update image tag in config repo
Config repo (GitOps): ArgoCD/Flux detects tag change ──► sync to clusterBenefits and Trade-Offs
Section titled “Benefits and Trade-Offs”Benefits
Section titled “Benefits”| Benefit | Why |
|---|---|
| Git as audit trail | Every deployment is a Git commit — who, what, when |
| Declarative | Desired state in Git, not imperative scripts |
| Drift detection | Operator auto-corrects manual changes |
| Easy rollback | git revert = rollback |
| Security | CI pipeline doesn’t need cluster credentials |
| Consistency | Same tool manages all environments |
Trade-Offs
Section titled “Trade-Offs”| Trade-Off | Consideration |
|---|---|
| Secret management | Secrets can’t live in Git as plain text — use Sealed Secrets, SOPS, or External Secrets Operator |
| Learning curve | Requires understanding ArgoCD/Flux CRDs and Git workflows |
| Multi-cluster complexity | Managing many clusters and environments adds repo structure complexity |
| Debugging | When sync fails, you troubleshoot via the operator logs, not your usual CI pipeline |
| Database migrations | Need a separate mechanism (Jobs, init containers) — not purely declarative |
Key Takeaways
Section titled “Key Takeaways”- GitOps uses Git as the single source of truth — the cluster continuously reconciles to match Git.
- Pull-based (GitOps) is more secure than push-based (CI deploys) because the cluster pulls from Git instead of CI pushing to the cluster.
- ArgoCD is best for teams that want a rich UI, multi-cluster management, and app-of-apps patterns.
- Flux is best for teams that prefer modular controllers, native Helm lifecycle, and built-in image automation.
- Use a separate config repo for deployment manifests — decouples app code from deployment config.
- Drift detection and self-healing are key GitOps benefits — manual changes are auto-corrected.
- Handle secrets with Sealed Secrets, SOPS, or External Secrets Operator — never store plain-text secrets in Git.
Related
Section titled “Related”- AIOps — LLM-assisted runbooks, RAG over operational knowledge, and evaluating AI suggestions alongside Git-defined desired state.