Skip to main content
RunBook Academy

KubernetesCIII · GitOps IntroductionGitOps

GitOps anti-patterns — the most common mistakes

Advanced⏱ ~16 mingitargocd

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

Not yet marked complete on this device.

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

  1. Q1. Why is storing plain Secret manifests in Git an anti-pattern even for a private repository?

  2. Q2. Removing a committed Secret in a later commit makes it safe.

  3. 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.

  4. 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.