Introduction
GitOps is a modern approach to continuous deployment where Git serves as the single source of truth for both application code and infrastructure. Changes are made through Git commits, and automated agents ensure the live environment matches the desired state in Git.
This guide visualizes the GitOps workflow:
- Declarative Infrastructure: Everything defined as code in Git
- Automated Sync: Agents continuously reconcile live state with Git
- Drift Detection: Automatic detection and correction of manual changes
- Pull-Based Deployment: Agents pull changes from Git (vs push-based CI/CD)
- Audit Trail: Complete history of changes in Git
Part 1: GitOps Overview
GitOps Principles
%%{init: {'theme':'dark', 'themeVariables': {'primaryTextColor':'#e5e7eb','secondaryTextColor':'#e5e7eb','tertiaryTextColor':'#e5e7eb','textColor':'#e5e7eb','nodeTextColor':'#e5e7eb','edgeLabelText':'#e5e7eb','clusterTextColor':'#e5e7eb','actorTextColor':'#e5e7eb'}}}%%
flowchart TD
Start([GitOps Core Principles]) --> P1[1️⃣ Declarative
All config in Git as YAML
Desired state, not steps] Start --> P2[2️⃣ Versioned & Immutable
Git history is truth
Every change tracked] Start --> P3[3️⃣ Pulled Automatically
Agents pull from Git
No push access needed] Start --> P4[4️⃣ Continuously Reconciled
Agents detect drift
Auto-heal to Git state] P1 --> Example1[Example:
Kubernetes manifests
Terraform code
Helm charts] P2 --> Example2[Example:
git log shows who changed what
git revert to rollback
git blame for accountability] P3 --> Example3[Example:
ArgoCD polls Git every 3min
FluxCD watches Git repo
No CI/CD push needed] P4 --> Example4[Example:
Manual kubectl edit detected
Reverted to Git state
Drift alert sent] style P1 fill:#064e3b,stroke:#10b981 style P2 fill:#064e3b,stroke:#10b981 style P3 fill:#064e3b,stroke:#10b981 style P4 fill:#064e3b,stroke:#10b981
All config in Git as YAML
Desired state, not steps] Start --> P2[2️⃣ Versioned & Immutable
Git history is truth
Every change tracked] Start --> P3[3️⃣ Pulled Automatically
Agents pull from Git
No push access needed] Start --> P4[4️⃣ Continuously Reconciled
Agents detect drift
Auto-heal to Git state] P1 --> Example1[Example:
Kubernetes manifests
Terraform code
Helm charts] P2 --> Example2[Example:
git log shows who changed what
git revert to rollback
git blame for accountability] P3 --> Example3[Example:
ArgoCD polls Git every 3min
FluxCD watches Git repo
No CI/CD push needed] P4 --> Example4[Example:
Manual kubectl edit detected
Reverted to Git state
Drift alert sent] style P1 fill:#064e3b,stroke:#10b981 style P2 fill:#064e3b,stroke:#10b981 style P3 fill:#064e3b,stroke:#10b981 style P4 fill:#064e3b,stroke:#10b981
Part 2: GitOps vs Traditional CI/CD
Architecture Comparison
%%{init: {'theme':'dark', 'themeVariables': {'primaryTextColor':'#e5e7eb','secondaryTextColor':'#e5e7eb','tertiaryTextColor':'#e5e7eb','textColor':'#e5e7eb','nodeTextColor':'#e5e7eb','edgeLabelText':'#e5e7eb','clusterTextColor':'#e5e7eb','actorTextColor':'#e5e7eb'}}}%%
graph TB
subgraph Traditional[Traditional CI/CD PUSH Model]
T1([Developer]) --> T2[git push]
T2 --> T3[CI Server
GitHub Actions
Jenkins] T3 --> T4[Build & Test] T4 --> T5[Docker Build] T5 --> T6[Push Image] T6 --> T7[Deploy Script
kubectl apply] T7 --> T8[Kubernetes Cluster] Note1[Issues:
❌ CI needs cluster credentials
❌ Push-based security risk
❌ No drift detection
❌ Manual changes persist] end subgraph GitOps[GitOps PULL Model] G1([Developer]) --> G2[git push] G2 --> G3[Git Repository
Kubernetes manifests
Helm charts] G4[GitOps Agent
ArgoCD/Flux
Running IN cluster] G4 --> |Polls every 3min| G3 G4 --> G5{Desired state
= Live state?} G5 --> |No| G6[Apply changes
kubectl apply] G6 --> G7[Kubernetes Cluster] G5 --> |Yes| G8[No action needed] G7 --> |Detect drift| G4 Note2[Benefits:
✅ No external cluster access
✅ Pull-based security
✅ Automatic drift detection
✅ Self-healing
✅ Audit trail in Git] end style Traditional fill:#7f1d1d,stroke:#ef4444 style GitOps fill:#064e3b,stroke:#10b981
GitHub Actions
Jenkins] T3 --> T4[Build & Test] T4 --> T5[Docker Build] T5 --> T6[Push Image] T6 --> T7[Deploy Script
kubectl apply] T7 --> T8[Kubernetes Cluster] Note1[Issues:
❌ CI needs cluster credentials
❌ Push-based security risk
❌ No drift detection
❌ Manual changes persist] end subgraph GitOps[GitOps PULL Model] G1([Developer]) --> G2[git push] G2 --> G3[Git Repository
Kubernetes manifests
Helm charts] G4[GitOps Agent
ArgoCD/Flux
Running IN cluster] G4 --> |Polls every 3min| G3 G4 --> G5{Desired state
= Live state?} G5 --> |No| G6[Apply changes
kubectl apply] G6 --> G7[Kubernetes Cluster] G5 --> |Yes| G8[No action needed] G7 --> |Detect drift| G4 Note2[Benefits:
✅ No external cluster access
✅ Pull-based security
✅ Automatic drift detection
✅ Self-healing
✅ Audit trail in Git] end style Traditional fill:#7f1d1d,stroke:#ef4444 style GitOps fill:#064e3b,stroke:#10b981
Part 3: Complete GitOps Flow
End-to-End Workflow
%%{init: {'theme':'dark', 'themeVariables': {'primaryTextColor':'#e5e7eb','secondaryTextColor':'#e5e7eb','tertiaryTextColor':'#e5e7eb','textColor':'#e5e7eb','nodeTextColor':'#e5e7eb','edgeLabelText':'#e5e7eb','clusterTextColor':'#e5e7eb','actorTextColor':'#e5e7eb'}}}%%
flowchart TD
Start([Developer makes change]) --> Change[Update Kubernetes manifest
deployment.yaml:
image: myapp:v2.0
replicas: 5] Change --> Commit[git commit -m "Update to v2.0"] Commit --> Push[git push origin main] Push --> GitRepo[(Git Repository
k8s-configs/
├─ apps/
│ └─ myapp/
│ ├─ deployment.yaml
│ ├─ service.yaml
│ └─ ingress.yaml
└─ infrastructure/
└─ namespaces.yaml)] GitRepo --> Webhook{Webhook
configured?} Webhook -->|Yes| Notify[Notify ArgoCD
immediately] Webhook -->|No| Poll[ArgoCD polls
every 3 minutes] Notify --> ArgoCD[ArgoCD Controller
Detect changes in Git] Poll --> ArgoCD ArgoCD --> Compare[Compare Git state
vs Live cluster state] Compare --> Diff{Differences
detected?} Diff -->|No| InSync[✅ Application in sync
No action needed
Status: Healthy] Diff -->|Yes| OutOfSync[⚠️ Application out of sync
Git: image: v2.0
Cluster: image: v1.0] OutOfSync --> SyncPolicy{Auto-sync
enabled?} SyncPolicy -->|No| WaitManual[⏸️ Waiting for
manual sync trigger] WaitManual --> ManualSync[User clicks "Sync"
in ArgoCD UI] ManualSync --> ApplyChanges SyncPolicy -->|Yes| ApplyChanges[Apply changes from Git
kubectl apply -f deployment.yaml] ApplyChanges --> RolloutStart[Kubernetes Rolling Update
Create new pods with v2.0] RolloutStart --> HealthCheck[Health Check Loop
Check pod status
Run readiness probes] HealthCheck --> HealthStatus{All pods
healthy?} HealthStatus -->|No| CheckTimeout{Exceeded
timeout?} CheckTimeout -->|No| HealthCheck CheckTimeout -->|Yes| SyncFailed[❌ Sync Failed
Status: Degraded
Send alert] SyncFailed --> Rollback{Auto-rollback
enabled?} Rollback -->|Yes| RevertGit[Revert to previous
Git commit
Trigger new sync] Rollback -->|No| Manual[Manual intervention
required] HealthStatus -->|Yes| Synced[✅ Sync Successful
Application: Healthy
Git ≡ Cluster] Synced --> ContinuousMonitor[Continuous Monitoring
Detect drift
Watch for manual changes] ContinuousMonitor --> DriftDetect{Manual change
detected?} DriftDetect -->|Yes| DriftAlert[🚨 Drift Detected!
Someone ran kubectl edit
Cluster ≠ Git] DriftAlert --> AutoHeal{Self-healing
enabled?} AutoHeal -->|Yes| RevertDrift[Revert manual change
Restore Git state
Cluster ≡ Git again] AutoHeal -->|No| DriftNotify[Notify team
Manual change persists
Update Git to match?] RevertDrift --> ContinuousMonitor DriftNotify --> ContinuousMonitor DriftDetect -->|No| ContinuousMonitor style InSync fill:#064e3b,stroke:#10b981 style Synced fill:#064e3b,stroke:#10b981 style OutOfSync fill:#78350f,stroke:#f59e0b style SyncFailed fill:#7f1d1d,stroke:#ef4444 style DriftAlert fill:#7f1d1d,stroke:#ef4444
deployment.yaml:
image: myapp:v2.0
replicas: 5] Change --> Commit[git commit -m "Update to v2.0"] Commit --> Push[git push origin main] Push --> GitRepo[(Git Repository
k8s-configs/
├─ apps/
│ └─ myapp/
│ ├─ deployment.yaml
│ ├─ service.yaml
│ └─ ingress.yaml
└─ infrastructure/
└─ namespaces.yaml)] GitRepo --> Webhook{Webhook
configured?} Webhook -->|Yes| Notify[Notify ArgoCD
immediately] Webhook -->|No| Poll[ArgoCD polls
every 3 minutes] Notify --> ArgoCD[ArgoCD Controller
Detect changes in Git] Poll --> ArgoCD ArgoCD --> Compare[Compare Git state
vs Live cluster state] Compare --> Diff{Differences
detected?} Diff -->|No| InSync[✅ Application in sync
No action needed
Status: Healthy] Diff -->|Yes| OutOfSync[⚠️ Application out of sync
Git: image: v2.0
Cluster: image: v1.0] OutOfSync --> SyncPolicy{Auto-sync
enabled?} SyncPolicy -->|No| WaitManual[⏸️ Waiting for
manual sync trigger] WaitManual --> ManualSync[User clicks "Sync"
in ArgoCD UI] ManualSync --> ApplyChanges SyncPolicy -->|Yes| ApplyChanges[Apply changes from Git
kubectl apply -f deployment.yaml] ApplyChanges --> RolloutStart[Kubernetes Rolling Update
Create new pods with v2.0] RolloutStart --> HealthCheck[Health Check Loop
Check pod status
Run readiness probes] HealthCheck --> HealthStatus{All pods
healthy?} HealthStatus -->|No| CheckTimeout{Exceeded
timeout?} CheckTimeout -->|No| HealthCheck CheckTimeout -->|Yes| SyncFailed[❌ Sync Failed
Status: Degraded
Send alert] SyncFailed --> Rollback{Auto-rollback
enabled?} Rollback -->|Yes| RevertGit[Revert to previous
Git commit
Trigger new sync] Rollback -->|No| Manual[Manual intervention
required] HealthStatus -->|Yes| Synced[✅ Sync Successful
Application: Healthy
Git ≡ Cluster] Synced --> ContinuousMonitor[Continuous Monitoring
Detect drift
Watch for manual changes] ContinuousMonitor --> DriftDetect{Manual change
detected?} DriftDetect -->|Yes| DriftAlert[🚨 Drift Detected!
Someone ran kubectl edit
Cluster ≠ Git] DriftAlert --> AutoHeal{Self-healing
enabled?} AutoHeal -->|Yes| RevertDrift[Revert manual change
Restore Git state
Cluster ≡ Git again] AutoHeal -->|No| DriftNotify[Notify team
Manual change persists
Update Git to match?] RevertDrift --> ContinuousMonitor DriftNotify --> ContinuousMonitor DriftDetect -->|No| ContinuousMonitor style InSync fill:#064e3b,stroke:#10b981 style Synced fill:#064e3b,stroke:#10b981 style OutOfSync fill:#78350f,stroke:#f59e0b style SyncFailed fill:#7f1d1d,stroke:#ef4444 style DriftAlert fill:#7f1d1d,stroke:#ef4444
Part 4: ArgoCD Sync Process
Detailed Sync Flow
%%{init: {'theme':'dark', 'themeVariables': {'primaryTextColor':'#e5e7eb','secondaryTextColor':'#e5e7eb','tertiaryTextColor':'#e5e7eb','textColor':'#e5e7eb','nodeTextColor':'#e5e7eb','edgeLabelText':'#e5e7eb','clusterTextColor':'#e5e7eb','actorTextColor':'#e5e7eb'}}}%%
sequenceDiagram
participant Dev as Developer
participant Git as Git Repository
participant ArgoCD as ArgoCD Controller
participant K8s as Kubernetes API
participant Pods as Application Pods
Dev->>Git: git push
Update deployment.yaml
image: myapp:v2.0 Note over Git: Git Repository Updated
Commit: abc123 alt Webhook Configured Git->>ArgoCD: Webhook: Repo changed else Polling loop Every 3 minutes ArgoCD->>Git: Poll for changes end end ArgoCD->>Git: Fetch latest commit
git pull origin main ArgoCD->>ArgoCD: Parse manifests:
deployment.yaml
service.yaml ArgoCD->>K8s: Get live resources
kubectl get deployment myapp -o yaml K8s-->>ArgoCD: Current state:
image: myapp:v1.0
replicas: 3 Note over ArgoCD: Compare states:
Git: v2.0, replicas: 5
Live: v1.0, replicas: 3
Diff detected! ArgoCD->>ArgoCD: Status: OutOfSync
Health: Healthy alt Auto-Sync Enabled Note over ArgoCD: Auto-sync triggered else Manual Sync ArgoCD->>ArgoCD: Wait for user to click "Sync" end ArgoCD->>K8s: Apply changes
kubectl apply -f deployment.yaml K8s->>K8s: Create new ReplicaSet
for image v2.0 K8s->>Pods: Start new pods
with image v2.0 loop Rolling Update K8s->>Pods: Create 1 new pod Pods-->>K8s: Pod starting... K8s->>Pods: Run readiness probe Pods-->>K8s: Ready ✓ K8s->>Pods: Terminate 1 old pod Pods-->>K8s: Terminated ArgoCD->>K8s: Check sync progress K8s-->>ArgoCD: 2/5 pods updated end K8s-->>ArgoCD: All pods ready
5/5 running v2.0 ArgoCD->>ArgoCD: Status: Synced ✓
Health: Healthy ✓ ArgoCD->>Dev: Notification:
✅ Sync successful
myapp updated to v2.0 Note over ArgoCD,K8s: Continuous monitoring
for drift Note over K8s: Someone runs:
kubectl scale deployment myapp --replicas=10 K8s->>Pods: Scale to 10 pods ArgoCD->>K8s: Poll live state K8s-->>ArgoCD: Live: replicas: 10
Git: replicas: 5
Drift detected! ArgoCD->>ArgoCD: Status: OutOfSync
Health: Healthy
Drift: Yes alt Self-Healing Enabled ArgoCD->>K8s: Revert to Git state
kubectl apply -f deployment.yaml K8s->>Pods: Scale back to 5 pods Note over ArgoCD: Drift corrected
Cluster matches Git else No Self-Healing ArgoCD->>Dev: Alert: Manual change detected
Cluster has 10 replicas
Git has 5 replicas end
Update deployment.yaml
image: myapp:v2.0 Note over Git: Git Repository Updated
Commit: abc123 alt Webhook Configured Git->>ArgoCD: Webhook: Repo changed else Polling loop Every 3 minutes ArgoCD->>Git: Poll for changes end end ArgoCD->>Git: Fetch latest commit
git pull origin main ArgoCD->>ArgoCD: Parse manifests:
deployment.yaml
service.yaml ArgoCD->>K8s: Get live resources
kubectl get deployment myapp -o yaml K8s-->>ArgoCD: Current state:
image: myapp:v1.0
replicas: 3 Note over ArgoCD: Compare states:
Git: v2.0, replicas: 5
Live: v1.0, replicas: 3
Diff detected! ArgoCD->>ArgoCD: Status: OutOfSync
Health: Healthy alt Auto-Sync Enabled Note over ArgoCD: Auto-sync triggered else Manual Sync ArgoCD->>ArgoCD: Wait for user to click "Sync" end ArgoCD->>K8s: Apply changes
kubectl apply -f deployment.yaml K8s->>K8s: Create new ReplicaSet
for image v2.0 K8s->>Pods: Start new pods
with image v2.0 loop Rolling Update K8s->>Pods: Create 1 new pod Pods-->>K8s: Pod starting... K8s->>Pods: Run readiness probe Pods-->>K8s: Ready ✓ K8s->>Pods: Terminate 1 old pod Pods-->>K8s: Terminated ArgoCD->>K8s: Check sync progress K8s-->>ArgoCD: 2/5 pods updated end K8s-->>ArgoCD: All pods ready
5/5 running v2.0 ArgoCD->>ArgoCD: Status: Synced ✓
Health: Healthy ✓ ArgoCD->>Dev: Notification:
✅ Sync successful
myapp updated to v2.0 Note over ArgoCD,K8s: Continuous monitoring
for drift Note over K8s: Someone runs:
kubectl scale deployment myapp --replicas=10 K8s->>Pods: Scale to 10 pods ArgoCD->>K8s: Poll live state K8s-->>ArgoCD: Live: replicas: 10
Git: replicas: 5
Drift detected! ArgoCD->>ArgoCD: Status: OutOfSync
Health: Healthy
Drift: Yes alt Self-Healing Enabled ArgoCD->>K8s: Revert to Git state
kubectl apply -f deployment.yaml K8s->>Pods: Scale back to 5 pods Note over ArgoCD: Drift corrected
Cluster matches Git else No Self-Healing ArgoCD->>Dev: Alert: Manual change detected
Cluster has 10 replicas
Git has 5 replicas end
Part 5: GitOps Repository Structure
Recommended Directory Layout
gitops-repo/
├── apps/
│ ├── production/
│ │ ├── myapp/
│ │ │ ├── deployment.yaml
│ │ │ ├── service.yaml
│ │ │ ├── ingress.yaml
│ │ │ └── kustomization.yaml
│ │ └── database/
│ │ ├── statefulset.yaml
│ │ └── service.yaml
│ │
│ ├── staging/
│ │ └── myapp/
│ │ ├── deployment.yaml
│ │ ├── service.yaml
│ │ └── kustomization.yaml
│ │
│ └── dev/
│ └── myapp/
│ └── ...
│
├── infrastructure/
│ ├── namespaces/
│ │ ├── production.yaml
│ │ ├── staging.yaml
│ │ └── dev.yaml
│ │
│ ├── ingress-controller/
│ │ └── nginx-ingress.yaml
│ │
│ └── monitoring/
│ ├── prometheus/
│ └── grafana/
│
├── argocd/
│ ├── applications/
│ │ ├── myapp-production.yaml
│ │ ├── myapp-staging.yaml
│ │ └── infrastructure.yaml
│ │
│ └── projects/
│ └── default-project.yaml
│
└── README.md
ArgoCD Application Definition
# argocd/applications/myapp-production.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp-production
namespace: argocd
spec:
# Where the app is defined in Git
source:
repoURL: https://github.com/myorg/gitops-repo
targetRevision: main
path: apps/production/myapp
# Where to deploy
destination:
server: https://kubernetes.default.svc
namespace: production
# Sync policy
syncPolicy:
automated:
prune: true # Delete resources not in Git
selfHeal: true # Revert manual changes
allowEmpty: false
syncOptions:
- CreateNamespace=true
- PrunePropagationPolicy=foreground
- PruneLast=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
# Health assessment
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas # Ignore HPA changes
# Notifications
notifications:
- when: on-sync-succeeded
destination: slack
- when: on-sync-failed
destination: pagerduty
Part 6: Drift Detection and Self-Healing
Drift Handling Flow
%%{init: {'theme':'dark', 'themeVariables': {'primaryTextColor':'#e5e7eb','secondaryTextColor':'#e5e7eb','tertiaryTextColor':'#e5e7eb','textColor':'#e5e7eb','nodeTextColor':'#e5e7eb','edgeLabelText':'#e5e7eb','clusterTextColor':'#e5e7eb','actorTextColor':'#e5e7eb'}}}%%
flowchart TD
Start([Cluster State]) --> Monitor[ArgoCD monitors
every 3 minutes] Monitor --> Compare[Compare:
Git desired state
vs
Cluster live state] Compare --> Check{States
match?} Check -->|Yes| InSync[✅ In Sync
No action needed] Check -->|No| DriftType{Type of
drift?} DriftType --> ManualEdit[Manual kubectl edit
Someone changed replicas
from 5 to 10] DriftType --> ResourceAdd[New resource added
Not in Git
e.g., manual deployment] DriftType --> ResourceDelete[Resource deleted
In Git but not in cluster] ManualEdit --> SelfHeal1{Self-healing
enabled?} ResourceAdd --> SelfHeal2{Prune
enabled?} ResourceDelete --> SelfHeal3{Self-healing
enabled?} SelfHeal1 -->|Yes| Revert[Revert to Git state
kubectl apply -f deployment.yaml
Replicas: 10 → 5] SelfHeal1 -->|No| Alert1[🚨 Alert Only
Manual drift detected
Replicas changed] SelfHeal2 -->|Yes| Delete[Delete extra resource
kubectl delete deployment xyz
Not in Git = Removed] SelfHeal2 -->|No| Alert2[⚠️ Alert Only
Extra resource detected
Not managed by Git] SelfHeal3 -->|Yes| Recreate[Recreate resource
kubectl apply -f service.yaml
Restore from Git] SelfHeal3 -->|No| Alert3[⚠️ Alert Only
Resource missing
Expected in Git] Revert --> Healed[✅ Drift Corrected
Cluster ≡ Git
Log event] Delete --> Healed Recreate --> Healed Alert1 --> Decision[Team Decision:
1. Update Git to match?
2. Revert cluster to Git?] Alert2 --> Decision Alert3 --> Decision Healed --> InSync InSync -.->|Continue monitoring| Monitor style InSync fill:#064e3b,stroke:#10b981 style Healed fill:#064e3b,stroke:#10b981 style Alert1 fill:#78350f,stroke:#f59e0b style Alert2 fill:#78350f,stroke:#f59e0b style Alert3 fill:#78350f,stroke:#f59e0b
every 3 minutes] Monitor --> Compare[Compare:
Git desired state
vs
Cluster live state] Compare --> Check{States
match?} Check -->|Yes| InSync[✅ In Sync
No action needed] Check -->|No| DriftType{Type of
drift?} DriftType --> ManualEdit[Manual kubectl edit
Someone changed replicas
from 5 to 10] DriftType --> ResourceAdd[New resource added
Not in Git
e.g., manual deployment] DriftType --> ResourceDelete[Resource deleted
In Git but not in cluster] ManualEdit --> SelfHeal1{Self-healing
enabled?} ResourceAdd --> SelfHeal2{Prune
enabled?} ResourceDelete --> SelfHeal3{Self-healing
enabled?} SelfHeal1 -->|Yes| Revert[Revert to Git state
kubectl apply -f deployment.yaml
Replicas: 10 → 5] SelfHeal1 -->|No| Alert1[🚨 Alert Only
Manual drift detected
Replicas changed] SelfHeal2 -->|Yes| Delete[Delete extra resource
kubectl delete deployment xyz
Not in Git = Removed] SelfHeal2 -->|No| Alert2[⚠️ Alert Only
Extra resource detected
Not managed by Git] SelfHeal3 -->|Yes| Recreate[Recreate resource
kubectl apply -f service.yaml
Restore from Git] SelfHeal3 -->|No| Alert3[⚠️ Alert Only
Resource missing
Expected in Git] Revert --> Healed[✅ Drift Corrected
Cluster ≡ Git
Log event] Delete --> Healed Recreate --> Healed Alert1 --> Decision[Team Decision:
1. Update Git to match?
2. Revert cluster to Git?] Alert2 --> Decision Alert3 --> Decision Healed --> InSync InSync -.->|Continue monitoring| Monitor style InSync fill:#064e3b,stroke:#10b981 style Healed fill:#064e3b,stroke:#10b981 style Alert1 fill:#78350f,stroke:#f59e0b style Alert2 fill:#78350f,stroke:#f59e0b style Alert3 fill:#78350f,stroke:#f59e0b
Part 7: GitOps Workflow Best Practices
Git Branching Strategy
%%{init: {'theme':'dark', 'themeVariables': {'primaryTextColor':'#e5e7eb','secondaryTextColor':'#e5e7eb','tertiaryTextColor':'#e5e7eb','textColor':'#e5e7eb','nodeTextColor':'#e5e7eb','edgeLabelText':'#e5e7eb','clusterTextColor':'#e5e7eb','actorTextColor':'#e5e7eb'}}}%%
gitGraph
commit id: "Initial infrastructure"
commit id: "Add myapp v1.0"
branch feature/upgrade-v2
checkout feature/upgrade-v2
commit id: "Update to v2.0"
commit id: "Add new config"
checkout main
commit id: "Hotfix: security patch"
checkout feature/upgrade-v2
merge main id: "Merge main"
checkout main
merge feature/upgrade-v2 id: "PR merged" tag: "Deploy to staging"
commit id: "Staging validated" tag: "Deploy to prod"
Approval Process
# PR approval workflow
name: GitOps PR Validation
on:
pull_request:
paths:
- 'apps/**'
- 'infrastructure/**'
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Validate Kubernetes YAML
run: |
kubeval apps/**/*.yaml
kustomize build apps/production/myapp | kubeval -
- name: Dry-run in staging
run: |
kubectl apply --dry-run=server -k apps/staging/myapp
- name: Security scan
run: |
kubesec scan apps/production/myapp/deployment.yaml
- name: Policy check
run: |
conftest test apps/production/myapp/*.yaml
require-approval:
needs: validate
runs-on: ubuntu-latest
steps:
- name: Check required approvals
if: contains(github.event.pull_request.files, 'apps/production')
run: |
# Require 2 approvals for production changes
APPROVALS=$(gh pr view ${{ github.event.pull_request.number }} --json reviews -q '.reviews | length')
if [ "$APPROVALS" -lt 2 ]; then
echo "Production changes require 2 approvals"
exit 1
fi
Part 8: Comparison Table
GitOps Tools Comparison
| Feature | ArgoCD | Flux | Jenkins X |
|---|---|---|---|
| Architecture | Controller + UI | Set of controllers | Full platform |
| UI | Rich web UI | No UI (CLI only) | Web UI |
| Multi-cluster | ✅ Native support | ✅ Via Flux controllers | ✅ Supported |
| Helm support | ✅ Native | ✅ Via Helm controller | ✅ Native |
| Kustomize support | ✅ Native | ✅ Via Kustomize controller | ✅ Supported |
| SSO/RBAC | ✅ Built-in | ❌ Use K8s RBAC | ✅ Built-in |
| Notifications | ✅ Slack, email, webhook | ✅ Via providers | ✅ Various channels |
| Drift detection | ✅ Visual in UI | ✅ CLI/metrics | ✅ Supported |
| Learning curve | Medium | Low | High |
| Best for | Teams wanting UI | GitOps purists | Full CI/CD platform |
Conclusion
GitOps provides:
- Single Source of Truth: Git is the authoritative source for everything
- Declarative Configuration: Desired state, not imperative steps
- Automated Reconciliation: Continuous sync and drift correction
- Audit Trail: Every change tracked in Git history
- Security: Pull-based model, no cluster credentials in CI
- Disaster Recovery: Rebuild entire infrastructure from Git
Key benefits:
- Improved security (pull vs push)
- Complete audit trail (git log)
- Easy rollbacks (git revert)
- Self-healing infrastructure
- Version control for infrastructure
Common challenges:
- Initial learning curve
- Secret management
- Large-scale repository management
- Migration from traditional CI/CD
The visual diagrams in this guide show how GitOps works end-to-end, making it easier to adopt this powerful deployment methodology.
Further Reading
Let Git be your single source of truth!