ESC
Type to search guides, tutorials, and reference documentation.
Verified by Garnet Grid

GitOps Automation: Git as the Single Source of Truth for Operations

Implement GitOps workflows where every operational change is a pull request. Covers ArgoCD, Flux, repository structure, drift detection, secret management, and progressive delivery patterns for Kubernetes-native automation.

GitOps takes a simple idea — everything in Git — and applies it to operations. Your cluster state, application configuration, network policies, and scaling rules all live in a Git repository. A reconciliation controller continuously compares the desired state in Git against the actual state of your infrastructure and corrects any drift.

The result: every change is a pull request. Every deployment is audited. Every rollback is git revert.


Core Principles

1. Declarative Configuration

The entire desired state of your system is described declaratively:

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 3
  template:
    spec:
      containers:
        - name: order-service
          image: registry.example.com/order-service:v2.4.1
          resources:
            requests:
              cpu: 250m
              memory: 512Mi

“Declarative” means you describe what you want, not the steps to get there. The system figures out how to reach the desired state.

2. Git as Single Source of Truth

Git is the canonical source for system state. No manual kubectl apply, no clicking through dashboards, no SSH-and-edit. If a change is not in Git, it should not exist in your cluster.

3. Automated Reconciliation

A controller watches the Git repository and the cluster, continuously reconciling them:

Git repo (desired state)  ←→  Controller  ←→  Cluster (actual state)
         ↓                                           ↓
    v2.4.1 image                              v2.3.0 image
         ↓                                           ↓
    Controller detects drift → applies v2.4.1 to cluster

4. Drift Detection and Correction

If someone manually changes a deployment — scaling it, updating an image, modifying environment variables — the controller detects the drift and restores the Git-defined state. This eliminates “snowflake” configurations.


ArgoCD Implementation

ArgoCD is the most widely adopted GitOps controller for Kubernetes.

Architecture

┌─────────────┐     ┌──────────────┐     ┌────────────────┐
│   Git Repo  │────▶│   ArgoCD     │────▶│  K8s Cluster   │
│  (manifests)│     │  (controller)│     │  (workloads)   │
└─────────────┘     └──────────────┘     └────────────────┘

                    ┌──────┴──────┐
                    │  ArgoCD UI  │
                    │  (dashboard)│
                    └─────────────┘

Application Definition

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: order-service
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/org/k8s-manifests.git
    targetRevision: main
    path: apps/order-service/production
  destination:
    server: https://kubernetes.default.svc
    namespace: order-service
  syncPolicy:
    automated:
      prune: true       # Remove resources deleted from Git
      selfHeal: true     # Correct manual drift
    syncOptions:
      - CreateNamespace=true

Sync Strategies

  • Manual sync: ArgoCD detects drift but waits for human approval
  • Auto sync: Changes in Git are automatically applied
  • Auto sync + self-heal: Drift from any source (including manual changes) is corrected

For production, start with manual sync. Move to auto sync once confidence is established.


Repository Structure

k8s-manifests/
├── apps/
│   ├── order-service/
│   │   ├── base/
│   │   │   ├── deployment.yaml
│   │   │   ├── service.yaml
│   │   │   └── kustomization.yaml
│   │   ├── staging/
│   │   │   └── kustomization.yaml      # Overrides for staging
│   │   └── production/
│   │       └── kustomization.yaml      # Overrides for production
│   ├── payment-service/
│   │   └── ...
│   └── api-gateway/
│       └── ...
├── infrastructure/
│   ├── cert-manager/
│   ├── ingress-nginx/
│   └── monitoring/
└── README.md

Kustomize Overlays

Base configuration shared across environments, with per-environment overrides:

# apps/order-service/base/kustomization.yaml
resources:
  - deployment.yaml
  - service.yaml

# apps/order-service/production/kustomization.yaml
resources:
  - ../base
patchesStrategicMerge:
  - replica-count.yaml
images:
  - name: registry.example.com/order-service
    newTag: v2.4.1

Image Update Automation

The most common GitOps workflow: a new container image triggers an update to the manifests repo.

Flux Image Automation

apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImagePolicy
metadata:
  name: order-service
spec:
  imageRepositoryRef:
    name: order-service
  policy:
    semver:
      range: ">=2.0.0"
---
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageUpdateAutomation
metadata:
  name: flux-system
spec:
  sourceRef:
    kind: GitRepository
    name: flux-system
  git:
    commit:
      author:
        name: flux-bot
        email: flux@example.com
      messageTemplate: "chore: update {{.AutomationObject}} images"
    push:
      branch: main
  update:
    strategy: Setters

Flux watches the container registry, detects new tags matching the semver policy, updates the image reference in Git, and commits the change. ArgoCD or Flux then reconciles the cluster.


Secret Management

Secrets cannot live in Git as plaintext. Common approaches:

Sealed Secrets

Encrypt secrets client-side; only the cluster controller can decrypt:

# Encrypt
kubeseal --cert pub-cert.pem < secret.yaml > sealed-secret.yaml

# Commit the sealed version
git add sealed-secret.yaml
git commit -m "chore: rotate database credentials"

External Secrets Operator

Reference secrets from external vaults:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: db-credentials
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secrets-manager
    kind: ClusterSecretStore
  target:
    name: db-credentials
  data:
    - secretKey: password
      remoteRef:
        key: /production/database/password

Progressive Delivery

GitOps pairs naturally with progressive delivery — rolling out changes gradually while monitoring for issues:

Canary Deployments with Argo Rollouts

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: order-service
spec:
  strategy:
    canary:
      steps:
        - setWeight: 10
        - pause: { duration: 5m }
        - setWeight: 30
        - pause: { duration: 5m }
        - setWeight: 60
        - pause: { duration: 5m }
      analysis:
        templates:
          - templateName: success-rate
        args:
          - name: service-name
            value: order-service

This sends 10% of traffic to the new version, waits 5 minutes while checking success rate metrics, then gradually increases if metrics are healthy.


Anti-Patterns

Anti-PatternConsequenceFix
kubectl apply in CI/CDBypasses Git, no audit trailAll changes through Git commits
Secrets in plaintextSecurity breach waiting to happenSealed Secrets or ESO
Single repo for app + infraBlast radius too largeSeparate app repos from platform
No drift alertingManual changes go unnoticedEnable self-heal + alert on drift
Applying to all envs at onceNo staging validationPromote through environments via PRs

GitOps is not just a deployment tool — it is an operating model. When everything is in Git, your operations team gets the same capabilities that development teams have had for decades: branching, review, audit, and instant rollback.

Jakub Dimitri Rezayev
Jakub Dimitri Rezayev
Founder & Chief Architect • Garnet Grid Consulting

Jakub holds an M.S. in Customer Intelligence & Analytics and a B.S. in Finance & Computer Science from Pace University. With deep expertise spanning D365 F&O, Azure, Power BI, and AI/ML systems, he architects enterprise solutions that bridge legacy systems and modern technology — and has led multi-million dollar ERP implementations for Fortune 500 supply chains.

View Full Profile →