GitOps Deployment Guide: ArgoCD, Flux, Drift Detection & Multi-Cluster Strategies
GitOps applies the same Git workflows developers use for application code — pull requests, reviews, audit trails — to infrastructure and deployment. The desired state lives in Git. An operator running inside the cluster pulls that state and reconciles reality to match it. This guide covers the principles, tools, and operational patterns that make GitOps work in production.
Core Principles of GitOps#
GitOps rests on four principles defined by the OpenGitOps working group:
- Declarative — the entire system is described declaratively (Kubernetes manifests, Helm charts, Kustomize overlays).
- Versioned and immutable — the desired state is stored in Git, providing a complete audit trail and the ability to roll back to any prior state.
- Pulled automatically — an agent inside the cluster pulls the desired state from Git, rather than an external CI system pushing changes in.
- Continuously reconciled — the agent continuously compares actual state to desired state and corrects any drift.
┌───────────┐ pull ┌──────────────┐ reconcile ┌───────────┐
│ │ ◄────────────── │ GitOps │ ──────────────► │ Kubernetes│
│ Git │ │ Operator │ │ Cluster │
│ Repo │ │ (ArgoCD/Flux)│ ◄────────────── │ │
│ │ │ │ observe state │ │
└───────────┘ └──────────────┘ └───────────┘
Push vs Pull Deployments#
Traditional CI/CD uses a push model: the CI server builds an artifact and pushes it to the cluster using kubectl apply or helm upgrade. This has several drawbacks.
Problems with Push-Based Deployment#
- Credential sprawl — the CI server needs cluster credentials, expanding the attack surface.
- No drift correction — if someone manually changes a resource, the CI system does not know or care.
- Audit gaps — deployments are triggered by CI jobs, not Git commits, making it harder to trace who deployed what and when.
Pull-Based Deployment#
In a pull model, the GitOps operator runs inside the cluster. It watches a Git repository and pulls changes when it detects a new commit. Cluster credentials never leave the cluster.
| Aspect | Push Model | Pull Model |
|---|---|---|
| Credentials | CI server holds cluster creds | Operator runs in-cluster |
| Drift handling | None | Continuous reconciliation |
| Audit trail | CI logs | Git history |
| Rollback | Re-run old pipeline | git revert |
ArgoCD#
ArgoCD is the most widely adopted GitOps operator. It provides a declarative, Kubernetes-native continuous deployment engine with a web UI, CLI, and API.
Core Concepts#
- Application — a CRD that maps a Git repo path to a target cluster and namespace.
- AppProject — a logical grouping of Applications with RBAC, source repo restrictions, and destination constraints.
- Sync — the process of applying the desired state from Git to the cluster.
- Health status — ArgoCD continuously monitors the health of deployed resources.
Minimal Application Definition#
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/org/k8s-manifests.git
targetRevision: main
path: apps/my-app
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
The selfHeal: true flag enables automatic drift correction. If someone manually deletes a ConfigMap, ArgoCD recreates it within seconds.
Sync Waves and Hooks#
ArgoCD supports sync waves for ordered deployments:
metadata:
annotations:
argocd.argoproj.io/sync-wave: "-1"
Resources with lower wave numbers deploy first. Use this to ensure namespaces and CRDs exist before the workloads that depend on them.
Flux#
Flux is the other major GitOps operator, now a CNCF graduated project. It takes a more composable, controller-based approach compared to ArgoCD's monolithic architecture.
Flux Components#
- Source Controller — watches Git repositories, Helm repositories, and OCI registries for changes.
- Kustomize Controller — applies Kustomize overlays from a source.
- Helm Controller — manages Helm releases declaratively.
- Notification Controller — sends alerts to Slack, Teams, or webhooks on reconciliation events.
- Image Automation Controllers — scan container registries and update Git when new images are available.
Flux GitRepository and Kustomization#
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: my-app
namespace: flux-system
spec:
interval: 1m
url: https://github.com/org/k8s-manifests.git
ref:
branch: main
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: my-app
namespace: flux-system
spec:
interval: 5m
sourceRef:
kind: GitRepository
name: my-app
path: ./apps/my-app
prune: true
targetNamespace: production
Flux polls the repo every minute and reconciles every five minutes. The prune: true flag removes resources that are deleted from Git.
The Reconciliation Loop#
Both ArgoCD and Flux implement a continuous reconciliation loop. This is the heart of GitOps.
┌──────────────────────────────────────┐
│ Reconciliation Loop │
│ │
│ 1. Fetch desired state from Git │
│ 2. Fetch actual state from cluster │
│ 3. Compute diff │
│ 4. If diff exists, apply changes │
│ 5. Report status │
│ 6. Wait for next interval │
│ 7. Repeat │
└──────────────────────────────────────┘
The reconciliation interval is configurable. Shorter intervals mean faster convergence but more API server load. Most teams use 1-5 minute intervals for production workloads.
Drift Detection#
Drift occurs when the actual state of the cluster diverges from the desired state in Git. Common causes include manual kubectl edits, Helm hooks, and mutating admission webhooks.
How GitOps Operators Detect Drift#
- ArgoCD compares the rendered manifests from Git against the live objects using a structured diff. It marks drifted applications as
OutOfSync. - Flux checks resource hashes and generation numbers. If the live object does not match the last applied state, it re-applies.
Handling Drift#
With selfHeal (ArgoCD) or prune (Flux) enabled, drift is corrected automatically. For sensitive resources where you want human review, disable auto-sync and use the drift alert as a trigger for investigation.
Preventing Drift#
- Use RBAC to restrict who can run
kubectl editorkubectl patchin production namespaces. - Implement admission webhooks that reject changes not originating from the GitOps operator.
- Treat manual changes as incidents — if someone needs to hotfix, they should commit the change to Git immediately after.
Multi-Cluster GitOps#
Most organizations run multiple clusters: development, staging, production, and sometimes per-region or per-tenant clusters. GitOps scales naturally to multi-cluster setups.
Repository Strategies#
- Monorepo — one repository with directories per cluster and per environment. Simple, but can grow unwieldy.
- Repo-per-environment — separate repositories for dev, staging, and prod. Clear separation, but promotion requires cross-repo PRs.
- Repo-per-team — each team owns their manifests. A platform repo aggregates references. Best for large organizations.
ArgoCD ApplicationSet#
ArgoCD's ApplicationSet controller generates Applications dynamically from templates:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: my-app-all-clusters
spec:
generators:
- clusters:
selector:
matchLabels:
env: production
template:
metadata:
name: 'my-app-{{name}}'
spec:
source:
repoURL: https://github.com/org/k8s-manifests.git
path: apps/my-app
targetRevision: main
destination:
server: '{{server}}'
namespace: production
This deploys my-app to every cluster labeled env: production. Adding a new cluster automatically triggers deployment.
Secrets Management in GitOps#
Secrets are the hardest part of GitOps. You cannot store plaintext secrets in Git, but the desired state must be fully declarative.
Approaches to GitOps Secrets#
| Approach | How It Works | Pros | Cons |
|---|---|---|---|
| Sealed Secrets | Encrypt secrets client-side; controller decrypts in-cluster | Simple, Git-native | Key rotation requires re-encrypting all secrets |
| SOPS + Age/KMS | Encrypt secret values in YAML files using Mozilla SOPS | Fine-grained, supports multiple backends | Adds tooling to the developer workflow |
| External Secrets Operator | CRD references secrets in Vault, AWS Secrets Manager, etc. | Secrets never touch Git | Adds a runtime dependency |
| Vault Agent Injector | Sidecar injects secrets from Vault at pod startup | Dynamic secrets, lease management | Vault becomes a critical dependency |
External Secrets Operator Example#
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: my-app-secrets
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: my-app-secrets
data:
- secretKey: DATABASE_URL
remoteRef:
key: prod/my-app/database-url
The ExternalSecret CRD is safe to store in Git — it contains only references, not values. The operator fetches the actual secret values at runtime.
Key Takeaways#
- GitOps uses Git as the single source of truth for both application and infrastructure state.
- Pull-based deployments eliminate credential sprawl and enable continuous drift correction.
- ArgoCD provides a batteries-included experience with a web UI, sync waves, and ApplicationSets for multi-cluster.
- Flux offers a composable, controller-based architecture that integrates tightly with Kustomize and Helm.
- The reconciliation loop continuously compares desired state to actual state and corrects drift automatically.
- Multi-cluster GitOps scales through ApplicationSets (ArgoCD) or Kustomization references (Flux).
- Never store plaintext secrets in Git — use Sealed Secrets, SOPS, or the External Secrets Operator.
Want to go deeper on GitOps, Kubernetes, and modern deployment practices? Explore all 339 articles on codelit.io — your concise, developer-first knowledge base.
Try it on Codelit
GitHub Integration
Paste any repo URL to generate an interactive architecture diagram from real code
Related articles
Try these templates
Build this architecture
Generate an interactive architecture for GitOps Deployment Guide in seconds.
Try it in Codelit →
Comments