← Back to Blog
DevOps & Infrastructure2026-05-3112 min read

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

Related posts

GitOps and Declarative Deployment with ArgoCD: The Complete 2026 Guide

Declarative infrastructure with GitOps and ArgoCD. Git as source of truth. Automatic sync, rollback, and audit trail.

Read more