2026-10-08 · 8 min read
GitOps Anti-Patterns (and How to Avoid Them)
GitOps is more than 'put YAML in Git.' The common anti-patterns, config drift, secrets in repos, monorepo sprawl, manual kubectl, and the practices that fix them.

GitOps Anti-Patterns (and How to Avoid Them)
GitOps is a genuinely good idea: the desired state of your system lives in Git, and an agent continuously reconciles reality to match it. But "we use ArgoCD" doesn't mean you're doing GitOps well. I've seen plenty of setups that have the tools and none of the benefits. Here are the anti-patterns I run into most, and how to fix them.
Anti-pattern 1: kubectl apply on the side
The smell: the cluster is "managed by GitOps," but people still run kubectl apply, kubectl edit, or helm upgrade directly when they're in a hurry.
Why it's bad: the moment you change the cluster outside Git, Git is no longer the source of truth. Either your GitOps agent reverts your change (confusing) or it doesn't notice (drift). Both undermine the entire model.
The fix: enable auto-sync with self-heal and prune in ArgoCD so out-of-band changes get reverted automatically. Remove humans' write access to the cluster, if the only path to change is a PR, the anti-pattern becomes impossible. (My gitops-kubernetes-platform shows this wired up.)
Anti-pattern 2: secrets in Git
The smell: plaintext Secret manifests, or base64'd secrets (base64 is encoding, not
encryption) committed to the repo.
Why it's bad: Git history is forever. A secret committed once is compromised forever, even if you delete it in the next commit.
The fix: never commit raw secrets. Use Sealed Secrets (encrypt to a cluster-specific key, safe to commit), or External Secrets Operator pulling from Vault / AWS Secrets Manager so the secret never touches Git at all (see secrets management comparison). The repo holds a reference, not the secret.
Anti-pattern 3: one giant repo, one giant app
The smell: every service, every environment, every cluster crammed into one Argo Application
pointed at one folder.
Why it's bad: no blast-radius control. A bad sync affects everything. No per-team boundaries. The diff on any PR is enormous.
The fix: structure for isolation. Use the app-of-apps or ApplicationSets pattern, separate
per-environment overlays (Kustomize) or value files (Helm), and per-team Applications. Promote
changes through environments with separate paths/branches, not one big blob.
Anti-pattern 4: no environment promotion strategy
The smell: dev, staging, and prod all read from main, or worse, all share the exact same
manifests with no way to promote a change deliberately.
Why it's bad: you can't test a change in staging before prod, and "promote to prod" becomes a manual scramble.
The fix: model environments explicitly: Kustomize overlays or per-env Helm values, with a clear promotion flow (PR that updates the prod image tag after staging is verified). Pair with progressive delivery for the actual rollout.
Anti-pattern 5: mutable image tags
The smell: deployments reference myapp:latest or a branch name.
Why it's bad: GitOps reconciles to the manifest, but if the tag is mutable, the manifest didn't change while the running image silently did. You've lost the "Git is the source of truth" guarantee for the thing that matters most, what code is running.
The fix: use immutable tags (git SHA or semver). The image tag in Git == the image running. Tools like Argo CD Image Updater can bump the tag via PR when a new image is built.
Anti-pattern 6: ignoring drift and sync status
The smell: ArgoCD shows OutOfSync / Degraded on half the apps and nobody looks.
Why it's bad: GitOps only delivers reliability if someone trusts and watches the reconciliation status. Permanent yellow = the dashboard is noise.
The fix: alert on sync/health status, keep apps green, and treat OutOfSync as a real signal.
Add Argo notifications to Slack. If something is intentionally divergent, encode that (ignore
differences) rather than tolerating chronic drift.
Anti-pattern 7: no separation between CI and CD
The smell: the CI pipeline runs kubectl apply to deploy (push-based "GitOps" that isn't).
Why it's bad: it gives CI cluster credentials (a security risk), and it's push-based: there's no continuous reconciliation, so drift creeps back.
The fix: CI builds and pushes the image and opens a PR to the config repo; CD (ArgoCD) pulls and reconciles. CI never touches the cluster. This is the pull-based model that makes GitOps secure and self-healing.
The short version
GitOps works when Git is genuinely the only source of truth and an agent continuously enforces it. The anti-patterns all boil down to violating that:
- No out-of-band
kubectl, enable self-heal and remove direct access. - No secrets in Git, Sealed Secrets or External Secrets.
- Structure repos for isolation and environment promotion.
- Immutable image tags, always.
- Watch and alert on sync/health status.
- Keep CI out of the cluster, pull-based reconciliation only.
Do those, and GitOps delivers what it promises: auditable, reproducible, self-healing infrastructure.
Setting up GitOps that actually delivers is part of my consulting work, reach out.