GitOps: Complete Guide to Git-Based Operations
· Pavan Y N · gitops, kubernetes, argocd, flux, automation
Published: October 30, 2025
Reading Time: 22 minutes
Tags: GitOps, Kubernetes, ArgoCD, Flux, Automation, CI/CD
# Introduction
GitOps represents a paradigm shift in how we manage infrastructure and applications. By using Git as the single source of truth for declarative infrastructure and applications, GitOps brings the power of version control, collaboration, and automation to operations.
In this comprehensive guide, we'll explore GitOps principles, implementation strategies, tools, and real-world use cases.
# What is GitOps?
GitOps is an operational framework that applies DevOps best practices—version control, collaboration, compliance, and CI/CD—to infrastructure automation.
## Core Principles
- •Declarative Configuration: Everything is described declaratively
- •Version Controlled: All configuration stored in Git
- •Automated Synchronization: Changes automatically applied
- •Continuous Reconciliation: System continuously converges to desired state
## GitOps vs Traditional Ops
| Aspect | Traditional Ops | GitOps | |--------|----------------|--------| | Configuration | Imperative scripts | Declarative manifests | | Source of Truth | Production state | Git repository | | Deployment | Manual or scripted | Automated sync | | Rollback | Complex procedures | Git revert | | Audit Trail | Scattered logs | Git history | | Access Control | Direct cluster access | Git permissions |
# GitOps Workflow
## The GitOps Loop
┌─────────────┐
│ Developer │
│ Changes │
└──────┬──────┘
│
▼
┌─────────────┐
│ Git Commit │
│ & Push │
└──────┬──────┘
│
▼
┌─────────────┐
│ CI Pipeline│
│ (Build & │
│ Test) │
└──────┬──────┘
│
▼
┌─────────────┐
│ Update │
│ Manifest │
│ Repo │
└──────┬──────┘
│
▼
┌─────────────┐
│ GitOps │
│ Operator │
│ Detects │
│ Change │
└──────┬──────┘
│
▼
┌─────────────┐
│ Apply to │
│ Cluster │
└─────────────┘
## Repository Structure
Option 1: Monorepo
my-app/
├── src/ # Application code
├── Dockerfile
├── k8s/ # Kubernetes manifests
│ ├── base/
│ │ ├── deployment.yaml
│ │ ├── service.yaml
│ │ └── kustomization.yaml
│ └── overlays/
│ ├── dev/
│ ├── staging/
│ └── production/
└── .github/workflows/
└── ci.yaml
Option 2: Separate Repos (Recommended)
app-repo/ # Application code
├── src/
├── Dockerfile
└── .github/workflows/
└── ci.yaml
config-repo/ # Configuration
├── apps/
│ └── my-app/
│ ├── base/
│ └── overlays/
├── infrastructure/
│ ├── networking/
│ └── monitoring/
└── clusters/
├── dev/
├── staging/
└── production/
# GitOps Tools
## 1. ArgoCD
ArgoCD is a declarative, GitOps continuous delivery tool for Kubernetes.
Installation:
# Install ArgoCD
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# Access ArgoCD UI
kubectl port-forward svc/argocd-server -n argocd 8080:443
# Get admin password
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
Application Definition:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
spec:
project: default
# Source repository
source:
repoURL: https://github.com/myorg/config-repo
targetRevision: HEAD
path: apps/my-app/overlays/production
# Kustomize configuration
kustomize:
images:
- myapp=myregistry.azurecr.io/myapp:v1.2.3
# Destination cluster
destination:
server: https://kubernetes.default.svc
namespace: production
# Sync policy
syncPolicy:
automated:
prune: true # Delete resources not in Git
selfHeal: true # Sync when cluster state differs
allowEmpty: false
syncOptions:
- CreateNamespace=true
- PruneLast=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
# Health assessment
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas # Ignore HPA-managed replicas
ArgoCD CLI Usage:
# Login
argocd login localhost:8080
# Create application
argocd app create my-app \
--repo https://github.com/myorg/config-repo \
--path apps/my-app/overlays/production \
--dest-server https://kubernetes.default.svc \
--dest-namespace production
# Sync application
argocd app sync my-app
# Get application status
argocd app get my-app
# View application logs
argocd app logs my-app
# Rollback to previous version
argocd app rollback my-app
# Delete application
argocd app delete my-app
## 2. Flux CD
Flux is a GitOps operator for Kubernetes that automatically ensures cluster state matches Git.
Installation:
# Install Flux CLI
curl -s https://fluxcd.io/install.sh | sudo bash
# Bootstrap Flux
flux bootstrap github \
--owner=myorg \
--repository=config-repo \
--branch=main \
--path=clusters/production \
--personal
GitRepository Source:
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: my-app
namespace: flux-system
spec:
interval: 1m
url: https://github.com/myorg/config-repo
ref:
branch: main
secretRef:
name: github-credentials
Kustomization:
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: my-app
namespace: flux-system
spec:
interval: 5m
path: ./apps/my-app/overlays/production
prune: true
sourceRef:
kind: GitRepository
name: my-app
healthChecks:
- apiVersion: apps/v1
kind: Deployment
name: my-app
namespace: production
timeout: 2m
HelmRelease:
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: my-app
namespace: production
spec:
interval: 5m
chart:
spec:
chart: my-app
version: '1.2.x'
sourceRef:
kind: HelmRepository
name: my-charts
namespace: flux-system
values:
replicaCount: 3
image:
repository: myregistry.azurecr.io/myapp
tag: v1.2.3
resources:
limits:
cpu: 500m
memory: 512Mi
# Implementing GitOps
## Step 1: Prepare Your Repository
Directory Structure:
config-repo/
├── apps/
│ ├── frontend/
│ │ ├── base/
│ │ │ ├── deployment.yaml
│ │ │ ├── service.yaml
│ │ │ ├── ingress.yaml
│ │ │ └── kustomization.yaml
│ │ └── overlays/
│ │ ├── dev/
│ │ ├── staging/
│ │ └── production/
│ └── backend/
│ └── ...
├── infrastructure/
│ ├── ingress-nginx/
│ ├── cert-manager/
│ └── monitoring/
└── clusters/
├── dev/
├── staging/
└── production/
Base Deployment:
# apps/frontend/base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
spec:
replicas: 2
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: myregistry.azurecr.io/frontend:latest
ports:
- containerPort: 80
env:
- name: API_URL
value: http://backend:8080
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
livenessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 80
initialDelaySeconds: 5
periodSeconds: 5
Production Overlay:
# apps/frontend/overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: production
resources:
- ../../base
replicas:
- name: frontend
count: 5
images:
- name: myregistry.azurecr.io/frontend
newTag: v1.2.3
patches:
- patch: |-
- op: replace
path: /spec/template/spec/containers/0/resources/limits/cpu
value: 1000m
- op: replace
path: /spec/template/spec/containers/0/resources/limits/memory
value: 1Gi
target:
kind: Deployment
name: frontend
configMapGenerator:
- name: frontend-config
literals:
- API_URL=https://api.production.example.com
- LOG_LEVEL=info
## Step 2: Set Up CI Pipeline
GitHub Actions Workflow:
name: CI Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
REGISTRY: myregistry.azurecr.io
IMAGE_NAME: frontend
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
outputs:
image-tag: ${{ steps.meta.outputs.tags }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix={{branch}}-
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
update-manifest:
needs: build-and-push
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Checkout config repo
uses: actions/checkout@v4
with:
repository: myorg/config-repo
token: ${{ secrets.GIT_TOKEN }}
- name: Update image tag
run: |
cd apps/frontend/overlays/production
kustomize edit set image \
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}=${{ needs.build-and-push.outputs.image-tag }}
- name: Commit and push
run: |
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
git add .
git commit -m "Update frontend image to ${{ needs.build-and-push.outputs.image-tag }}"
git push
## Step 3: Configure GitOps Operator
ArgoCD Application of Applications Pattern:
# clusters/production/apps.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: production-apps
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/myorg/config-repo
targetRevision: HEAD
path: clusters/production
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true
selfHeal: true
# clusters/production/frontend-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: frontend
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/myorg/config-repo
targetRevision: HEAD
path: apps/frontend/overlays/production
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
# Advanced GitOps Patterns
## Progressive Delivery
Canary Deployment with Flagger:
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: frontend
namespace: production
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: frontend
service:
port: 80
analysis:
interval: 1m
threshold: 5
maxWeight: 50
stepWeight: 10
metrics:
- name: request-success-rate
thresholdRange:
min: 99
interval: 1m
- name: request-duration
thresholdRange:
max: 500
interval: 1m
webhooks:
- name: load-test
url: http://flagger-loadtester/
timeout: 5s
metadata:
cmd: "hey -z 1m -q 10 -c 2 http://frontend-canary/"
## Multi-Cluster GitOps
ArgoCD ApplicationSet:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: frontend-multicluster
namespace: argocd
spec:
generators:
- list:
elements:
- cluster: dev
url: https://dev-cluster.example.com
namespace: development
- cluster: staging
url: https://staging-cluster.example.com
namespace: staging
- cluster: production
url: https://prod-cluster.example.com
namespace: production
template:
metadata:
name: 'frontend-{{cluster}}'
spec:
project: default
source:
repoURL: https://github.com/myorg/config-repo
targetRevision: HEAD
path: 'apps/frontend/overlays/{{cluster}}'
destination:
server: '{{url}}'
namespace: '{{namespace}}'
syncPolicy:
automated:
prune: true
selfHeal: true
## Secret Management
Sealed Secrets:
# Install Sealed Secrets controller
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.0/controller.yaml
# Create sealed secret
echo -n 'my-secret-value' | kubectl create secret generic my-secret \
--dry-run=client \
--from-file=password=/dev/stdin \
-o yaml | \
kubeseal -o yaml > sealed-secret.yaml
# Commit sealed secret to Git
git add sealed-secret.yaml
git commit -m "Add sealed secret"
git push
External Secrets Operator:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: app-secrets
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: azure-keyvault
kind: SecretStore
target:
name: app-secrets
creationPolicy: Owner
data:
- secretKey: database-password
remoteRef:
key: prod-db-password
- secretKey: api-key
remoteRef:
key: prod-api-key
# STAR Approach: GitOps Implementation
Situation: Company had 50+ microservices deployed manually to Kubernetes, leading to configuration drift, deployment errors, and slow rollbacks.
Task: Implement GitOps to standardize deployments, improve reliability, and enable self-service for developers.
Action:
- •
Repository Setup:
- →Created separate config repository
- →Organized manifests by environment and application
- →Implemented Kustomize for configuration management
- •
ArgoCD Deployment:
- →Installed ArgoCD in management cluster
- →Configured RBAC and SSO integration
- →Set up ApplicationSets for multi-cluster management
- •
CI/CD Integration:
- →Updated CI pipelines to build and push images
- →Automated manifest updates via GitHub Actions
- →Implemented image promotion workflow
- •
Developer Onboarding:
- →Created documentation and templates
- →Conducted training sessions
- →Established GitOps best practices
- •
Monitoring & Alerting:
- →Integrated ArgoCD with Slack
- →Set up Prometheus metrics
- →Created Grafana dashboards
Result:
- →Deployment time reduced from 30 minutes to 5 minutes
- →Configuration drift eliminated (100% compliance)
- →Rollback time improved from 1 hour to 2 minutes
- →Developer satisfaction increased (self-service deployments)
- →Audit trail complete (all changes in Git history)
- →Mean time to recovery (MTTR) decreased by 70%
# Common Issues & Troubleshooting
## Issue 1: Application Out of Sync
Symptoms:
- →ArgoCD shows "OutOfSync" status
- →Cluster state doesn't match Git
Diagnosis:
# Check application status
argocd app get my-app
# View differences
argocd app diff my-app
# Check sync status
kubectl get application my-app -n argocd -o yaml
Solutions:
# Manual sync
argocd app sync my-app
# Force sync (ignore differences)
argocd app sync my-app --force
# Prune extra resources
argocd app sync my-app --prune
## Issue 2: Image Pull Errors
Symptoms:
- →Pods stuck in ImagePullBackOff
- →Authentication failures
Solution:
# Create image pull secret
apiVersion: v1
kind: Secret
metadata:
name: registry-credentials
namespace: production
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: <base64-encoded-config>
---
# Reference in deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
template:
spec:
imagePullSecrets:
- name: registry-credentials
## Issue 3: Sync Loops
Symptoms:
- →Application continuously syncing
- →Resources constantly being updated
Diagnosis:
# Check for ignored differences
argocd app get my-app --show-operation
# View resource differences
kubectl diff -f manifest.yaml
Solution:
# Ignore specific fields
apiVersion: argoproj.io/v1alpha1
kind: Application
spec:
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas
- /spec/template/metadata/annotations
## Issue 4: Slow Sync Times
Symptoms:
- →Deployments take too long
- →Timeout errors
Solutions:
# Increase timeout
apiVersion: argoproj.io/v1alpha1
kind: Application
spec:
syncPolicy:
retry:
limit: 5
backoff:
duration: 5s
maxDuration: 5m
# Use sync waves for ordering
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
argocd.argoproj.io/sync-wave: "1"
# Best Practices
## 1. Repository Organization
- →Separate application code from configuration
- →Use environment-specific overlays
- →Keep base manifests DRY
- →Version control everything
## 2. Security
- →Use sealed secrets or external secret management
- →Implement RBAC for Git and ArgoCD
- →Scan manifests for security issues
- →Rotate credentials regularly
## 3. Deployment Strategy
- →Use progressive delivery (canary, blue-green)
- →Implement health checks
- →Set appropriate timeouts
- →Plan rollback procedures
## 4. Monitoring
- →Track sync status and health
- →Monitor deployment metrics
- →Set up alerts for failures
- →Maintain audit logs
## 5. Team Workflow
- →Require pull requests for changes
- →Implement automated testing
- →Document deployment procedures
- →Conduct regular reviews
# Conclusion
GitOps transforms operations by bringing software development best practices to infrastructure management. By treating Git as the single source of truth, teams gain version control, auditability, and automation for their entire stack.
Whether you're managing a handful of applications or hundreds of microservices across multiple clusters, GitOps provides a scalable, reliable, and developer-friendly approach to continuous delivery.
# Additional Resources
- →ArgoCD Documentation
- →Flux Documentation
- →GitOps Principles
- →CNCF GitOps Working Group
- →Kustomize Documentation
Ready to implement GitOps? Start with a single application and gradually expand to your entire infrastructure!