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
Monorepo (Recommended for Small-Medium Teams)
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-Pattern | Consequence | Fix |
|---|---|---|
| kubectl apply in CI/CD | Bypasses Git, no audit trail | All changes through Git commits |
| Secrets in plaintext | Security breach waiting to happen | Sealed Secrets or ESO |
| Single repo for app + infra | Blast radius too large | Separate app repos from platform |
| No drift alerting | Manual changes go unnoticed | Enable self-heal + alert on drift |
| Applying to all envs at once | No staging validation | Promote 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.