7 GitOps Mistakes Teams Make (And How to Fix Them) in 2026
Learn the common GitOps pitfalls: mixing imperative and declarative, auto-sync without approval, secrets in Git, and how to avoid them.
A team adopted GitOps on Monday. By Wednesday, they mixed imperative changes (kubectl apply) with declarative changes (Git commits). By Thursday, they had no idea what was actually in the cluster. Services drifted between the Git state and the manually-applied state. Two weeks of "Everything is a GitOps now" followed by "Let us go back to kubectl." What went wrong? They made the top 7 GitOps mistakes. We will walk through each one and how to fix it.
The Problem
GitOps is simple in theory: Git is source of truth. Cluster is reconciled to Git. In practice, teams find adoption messy. They mix Git-based changes with manual kubectl commands. They enable auto-sync on critical systems without approval gates. They store secrets in Git repositories. They skip the ceremony. Then they wonder why GitOps failed.
Why This Happens
GitOps requires discipline and process. It is slower than kubectl apply on the surface (one extra commit, one extra review). Teams feel the friction and bypass it. A critical incident happens. Someone runs kubectl apply to fix it immediately instead of creating a Git commit first. The drift happens. The rest of the team does not know about the fix. GitOps is blamed instead of the team's deviation from GitOps principles.
The 7 GitOps Mistakes and How to Fix Them
Mistake #1: Mixing Imperative and Declarative Changes
Some engineers push to Git. Others run kubectl apply directly. The cluster state diverges from Git. Two sources of truth is one too many.
Fix: Make Git-based changes mandatory. Remove cluster-level write permissions from engineers. Only the ArgoCD service account can modify the cluster. Engineers can only push to Git. This forces all changes through the GitOps workflow.
# RBAC: Only ArgoCD can deploy. Engineers cannot kubectl apply directly.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: engineer-read-only
rules:
- apiGroups: [""]
resources: ["pods", "services", "configmaps"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets"]
verbs: ["get", "list", "watch"]
# No "create", "update", "patch", "delete" verbs
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: argocd-admin
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: argocd
namespace: argocd
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: argocd-admin-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: argocd-admin
subjects:
- kind: ServiceAccount
name: argocd
namespace: argocd
Mistake #2: Enabling Auto-Sync Without Approval on Critical Systems
A team sets `automated.selfHeal: true` on their production ArgoCD Application. Someone pushes a malformed Deployment manifest to Git. ArgoCD syncs it immediately. Production goes down before anyone notices the commit.
Fix: Use manual sync for critical systems, automatic for non-critical. Require pull request approval and CI/CD validation before production changes are synced.
# Production: Manual sync (requires human approval)
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: payment-service-production
namespace: argocd
spec:
source:
repoURL: https://github.com/skillzmist/infrastructure
targetRevision: main
path: services/payment-service
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
syncOptions:
- CreateNamespace=true
# NO automated sync policy
# Manual sync requires explicit approval via ArgoCD UI
retry:
limit: 5
---
# Staging: Automatic sync (for fast feedback)
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: payment-service-staging
namespace: argocd
spec:
source:
repoURL: https://github.com/skillzmist/infrastructure
targetRevision: main
path: services/payment-service
destination:
server: https://kubernetes.default.svc
namespace: staging
syncPolicy:
automated:
prune: true
selfHeal: true # OK for staging
syncOptions:
- CreateNamespace=true
retry:
limit: 5
Mistake #3: Storing Secrets in Git (Plain Text)
A database password is stored in a ConfigMap in Git. Anyone with Git access can read the password. Compliance fails. The password is rotated manually every 90 days because secret management is hard. Passwords get lost. Services break.
Fix: Use sealed-secrets or external-secrets. Store encrypted secrets in Git. ArgoCD can sync them. Only the cluster can decrypt them. Rotate secrets automatically via a separate system.
# Using sealed-secrets: secrets are encrypted in Git
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: db-credentials
namespace: production
spec:
encryptedData:
password: AgBvx3n4FxK... # Encrypted value, safe to commit to Git
template:
metadata:
name: db-credentials
namespace: production
type: Opaque
---
# After ArgoCD syncs, the sealed-secret is decrypted and becomes:
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: production
type: Opaque
data:
password: bXlwYXNzd29yZAo= # Base64 decoded in the cluster only
Mistake #4: No Branch Protection on the Infrastructure Repository
Anyone can push directly to main. A junior engineer pushes a YAML syntax error. ArgoCD tries to sync it. Production resources become unmanageable. No review process caught the error.
Fix: Require pull requests and code review. No direct pushes to main. All changes require approval from at least one other engineer. CI/CD validates YAML syntax before merge.
GitHub branch protection settings:
- Require pull request reviews before merging (at least 1)
- Require status checks to pass (CI/CD validates YAML, Kustomize, Helm)
- Require branches to be up to date before merging
- Dismiss stale pull request approvals when new commits are pushed
- Restrict who can push to matching branches (only admins)
Mistake #5: Not Testing Changes Before Deploying
A Terraform module is updated in Git. It looks correct. ArgoCD syncs it to production. The update breaks a service that depends on it. No one tested the change in staging first.
Fix: Require CI/CD testing before production sync. Pull requests to main trigger CI/CD that validates YAML, tests Helm charts, runs kube-score, scans for security issues. Only after checks pass can the PR be merged.
Mistake #6: Misunderstanding Sync Waves and Health Assessment
A team deploys a database migration and a service update in the same Git commit. The service starts before the migration completes. Data corruption occurs. Different resources need to deploy in a specific order.
Fix: Use ArgoCD Sync Waves to control deployment order.
# Stage 1: Database migration runs first
apiVersion: batch/v1
kind: Job
metadata:
name: db-migration
annotations:
argocd.argoproj.io/sync-wave: "0"
spec:
template:
spec:
containers:
- name: migrate
image: migration-image:v1.2
restartPolicy: Never
---
# Stage 2: Database is ready, now deploy the service
apiVersion: apps/v1
kind: Deployment
metadata:
name: payment-service
annotations:
argocd.argoproj.io/sync-wave: "1"
spec:
replicas: 3
template:
spec:
containers:
- name: service
image: payment-service:v2.4.1
Mistake #7: No Monitoring of ArgoCD Sync Status
ArgoCD is syncing, but no one notices when syncs fail. A Git commit breaks the deployment. Services stay in a degraded state for hours because no alert was fired.
Fix: Monitor ArgoCD Application status. Set up alerts for sync failures, health issues, and drift detection.
# PrometheusRule: Alert if ArgoCD sync fails
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: argocd-alerts
namespace: monitoring
spec:
groups:
- name: argocd
interval: 30s
rules:
- alert: ArgoCDSyncFailed
expr: argocd_app_sync_total{phase="Failed"} > 0
for: 5m
annotations:
summary: "ArgoCD Application sync failed"
description: "ArgoCD application {{ $labels.name }} failed to sync"
- alert: ArgoCDAppHealthDegraded
expr: argocd_app_health_status{health_status="Degraded"} > 0
for: 10m
annotations:
summary: "ArgoCD Application health degraded"
description: "Application {{ $labels.name }} is unhealthy"
Key Takeaways
- Enforce Git-based changes: Remove direct cluster write permissions. Only ArgoCD can modify the cluster.
- Use manual sync for critical systems: Staging can auto-sync. Production requires approval.
- Never store plain-text secrets in Git: Use sealed-secrets or external-secrets for encryption.
- Require pull request review and CI/CD validation: Catch errors before they reach production.
- Test changes in lower environments first: Staging deployments catch problems before production.
- Use sync waves for ordered deployment: Database migrations must complete before services start.
- Monitor ArgoCD sync status: Alerts fire when syncs fail or health degrades.
Ready to avoid these GitOps mistakes? The Skillzmist team has implemented GitOps patterns for dozens of organizations. Reach out for a free technical consultation — we respond within 24 hours.
Related: GitOps and Declarative Deployment with ArgoCD | How Platform Engineering Reduces CI/CD Complexity