GitOps Workflow Patterns
Implement GitOps to manage infrastructure and application deployments through Git as the single source of truth. Covers pull-based deployment, ArgoCD, Flux, drift detection, multi-environment promotion, and the patterns that make GitOps reliable at scale.
GitOps uses Git repositories as the single source of truth for declarative infrastructure and application deployment. Instead of running kubectl apply or clicking buttons in a cloud console, you commit a change to Git and an operator automatically reconciles the live state to match. If someone manually changes production, the GitOps operator reverts it.
Push vs Pull Deployment
Push (Traditional CI/CD):
Code commit → CI builds → CI pushes to production
CI has production credentials
Production state unknown between pushes
Pull (GitOps):
Code commit → Git repo updated → Operator PULLS changes
Only operator has production credentials
Operator continuously reconciles (drift detection)
Git history = deployment audit trail
ArgoCD Setup
# ArgoCD Application definition
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: order-service
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/company/gitops-config
targetRevision: main
path: apps/order-service/overlays/production
destination:
server: https://kubernetes.default.svc
namespace: order-service
syncPolicy:
automated:
prune: true # Delete resources removed from Git
selfHeal: true # Revert manual changes
allowEmpty: false # Don't sync if source is empty
syncOptions:
- CreateNamespace=true
- PruneLast=true
- ApplyOutOfSyncOnly=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
Repository Structure
gitops-config/
├── apps/
│ ├── order-service/
│ │ ├── base/
│ │ │ ├── deployment.yaml
│ │ │ ├── service.yaml
│ │ │ ├── configmap.yaml
│ │ │ └── kustomization.yaml
│ │ └── overlays/
│ │ ├── development/
│ │ │ ├── kustomization.yaml
│ │ │ └── replicas-patch.yaml
│ │ ├── staging/
│ │ │ ├── kustomization.yaml
│ │ │ └── replicas-patch.yaml
│ │ └── production/
│ │ ├── kustomization.yaml
│ │ ├── replicas-patch.yaml
│ │ └── resources-patch.yaml
│ │
│ └── payment-service/
│ ├── base/
│ └── overlays/
│
├── infrastructure/
│ ├── monitoring/
│ ├── ingress/
│ └── cert-manager/
│
└── clusters/
├── dev-cluster/
├── staging-cluster/
└── prod-cluster/
Environment Promotion
# Promotion workflow: dev → staging → production
# Each environment is a separate directory/overlay
# Step 1: Developer opens PR to update dev overlay
# Step 2: ArgoCD auto-syncs dev
# Step 3: Tests pass → PR to promote to staging
# Step 4: ArgoCD auto-syncs staging
# Step 5: Smoke tests pass → PR to promote to production
# Step 6: ArgoCD auto-syncs production (with approval gate)
# Image promotion example:
# dev: image: registry.company.com/order-service:v1.2.4-dev
# staging: image: registry.company.com/order-service:v1.2.4-rc1
# prod: image: registry.company.com/order-service:v1.2.4
# Automated image updater (ArgoCD Image Updater):
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
annotations:
argocd-image-updater.argoproj.io/image-list: >
order=registry.company.com/order-service
argocd-image-updater.argoproj.io/order.update-strategy: semver
argocd-image-updater.argoproj.io/order.allow-tags: "regexp:^v\\d+\\.\\d+\\.\\d+$"
Drift Detection
What is drift?
Someone manually changes production (kubectl edit, console click)
Live state ≠ Git state
How GitOps handles drift:
selfHeal: true → ArgoCD automatically reverts manual changes
selfHeal: false → ArgoCD alerts but doesn't revert
Best practice:
Production: selfHeal: true (no manual changes allowed)
Development: selfHeal: false (developers need flexibility)
Combined with RBAC:
- Developers: No direct kubectl access to production
- SREs: Emergency break-glass only (audited)
- ArgoCD service account: Full production access
Anti-Patterns
| Anti-Pattern | Consequence | Fix |
|---|---|---|
| Push deployment in GitOps repo | CI has production credentials | Pull model with ArgoCD/Flux only |
| No automated sync | Git and live state drift | automated + selfHeal enabled |
| Single repo for app + config | Triggers deploy on app code changes | Separate app repo and config repo |
| No promotion gates | Bad code reaches production | PR approvals + automated tests per env |
| Manual kubectl in production | Bypasses Git audit trail | RBAC: no human kubectl in prod |
GitOps is not a tool — it is a practice. The core principle: if it is not in Git, it does not exist in production.