KubernetesCIII · GitOps IntroductionGitOps
GitOps anti-patterns — the most common mistakes
What you'll learn
- Identify the most common GitOps anti-patterns
- Explain why each anti-pattern is a problem
- Apply the fixes for each anti-pattern
- Build the operational discipline of avoiding GitOps anti-patterns
Prerequisites
Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16
GitOps anti-patterns are the most common mistakes that operators make when adopting GitOps. This lesson walks the anti-patterns, the fixes, and the operational discipline.
Anti-pattern 1: Directly pushing to main
flowchart LR
A[Developer] -->|git push| B[Main branch]
B --> C[Cluster]
The intent is speed; the result is bypassed review.
The fix:
flowchart LR
A[Developer] -->|git push| B[Feature branch]
B --> C[Pull request]
C --> D[Review]
D --> E[Merge to main]
E --> F[Cluster]
The fix is a branch protection rule that requires PR review before merge. Every change goes through review.
Anti-pattern 2: Storing Secrets in Git
# Anti-pattern
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
data:
password: c3VwZXJzZWNyZXRwYXNzd29yZA==
The Secret is base64-encoded, not encrypted. The manifest repository is not a Secret store.
The fix is to use sealed-secrets, Vault, or External Secrets Operator.
# Sealed-secrets
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: db-credentials
spec:
encryptedData:
password: AgBy3i... # encrypted with cluster's public key
The SealedSecret is safe to commit to Git; the cluster’s sealed-secrets controller decrypts it on apply.
Anti-pattern 3: Missing sync waves
flowchart LR
A["Application: CRDs"] -->|sync order: random| B["Application: CRs"]
B --> C["Application: workloads"]
The anti-pattern: Applications sync in arbitrary order; CRs are applied before CRDs; the API server rejects them.
The fix: sync waves.
metadata:
annotations:
argocd.argoproj.io/sync-wave: "-10" # CRDs sync first
---
metadata:
annotations:
argocd.argoproj.io/sync-wave: "0" # CRs sync second
---
metadata:
annotations:
argocd.argoproj.io/sync-wave: "10" # workloads sync last
CRDs in wave -10; CRs in wave 0; workloads in wave 10. The ordering is enforced.
Anti-pattern 4: selfHeal disabled
spec:
syncPolicy:
automated:
prune: true
selfHeal: false # anti-pattern
Without selfHeal, drift is reported but not corrected. The cluster’s actual state diverges from Git. Production should always have selfHeal enabled.
The fix:
spec:
syncPolicy:
automated:
prune: true
selfHeal: true
Anti-pattern 5: No ignoreDifferences for HPA
flowchart LR
A[HPA scales replicas to 5] --> B[Drift detected]
B --> C[selfHeal reverts to 3]
C --> D[HPA scales to 5 again]
D --> E[Loop]
The anti-pattern: HPA scales the Deployment; the controller detects drift (replicas: 3 in Git, 5 in cluster); selfHeal reverts to 3; HPA scales again. A loop.
The fix:
spec:
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas
The controller ignores the replicas field; HPA is allowed to manage it.
Anti-pattern 6: Monolithic Application per cluster
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: everything
spec:
source:
path: .
destination:
namespace: default
A single Application with all manifests. Hard to manage; hard to see what changed; hard to debug.
The fix is one Application per workload or per logical group, with App of Apps for grouping.
Anti-pattern 7: No drift alerts
flowchart LR
A[Drift] --> B[No alert]
B --> C[Drift accumulates]
C --> D[Production incident]
Drift is invisible until something breaks. The fix is to alert on drift:
argocd_app_health_status{status="Degraded"} > 0
argocd_app_sync_status{status="OutOfSync"} > 0
Alert on Degraded and OutOfSync Applications.
Quiz
Knowledge check · 4 questions
Q1. Why is storing plain Secret manifests in Git an anti-pattern even for a private repository?
Q2. Removing a committed Secret in a later commit makes it safe.
Q3. Fix an Application whose sync fails with an unknown kind because custom resources are applied before their CRDs.
A new `observability` Application fails to sync with `unable to recognize "prometheusrule.yaml": no matches for kind "PrometheusRule" in version "monitoring.coreos.com/v1"`. The Prometheus Operator CRDs are in the same repository path as the resources that need them, and a manual re-sync ten minutes later sometimes succeeds. No resource in the path carries an `argocd.argoproj.io/sync-wave` annotation.
Q4. Which two fields under `syncPolicy.automated` are the ones that, left off, allow drift to persist and allow resources deleted from Git to keep running?
Passing score: 75%. Answers are checked in this browser.
The operational discipline
GitOps anti-patterns’ operational discipline:
- Branch protection. Require PR review before merge to main.
- External secrets. Use sealed-secrets, Vault, or External Secrets Operator.
- Sync waves. Use sync waves for any dependency ordering.
- selfHeal enabled. Production should have selfHeal enabled.
- ignoreDifferences for controllers. Ignore fields managed by Kubernetes controllers (HPA, PDB, etc.).
- Multiple Applications. One Application per workload or logical group.
- Drift alerts. Alert on Degraded and OutOfSync Applications.
GitOps is a discipline. The anti-patterns are the shortcuts that break the discipline. The discipline is to enforce the patterns consistently.