All posts

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

  1. •Declarative Configuration: Everything is described declaratively
  2. •Version Controlled: All configuration stored in Git
  3. •Automated Synchronization: Changes automatically applied
  4. •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

code
┌─────────────┐
│   Developer │
│   Changes   │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│  Git Commit │
│  & Push     │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│  CI Pipeline│
│  (Build &   │
│   Test)     │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│  Update     │
│  Manifest   │
│  Repo       │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│  GitOps     │
│  Operator   │
│  Detects    │
│  Change     │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│  Apply to   │
│  Cluster    │
└─────────────┘

## Repository Structure

Option 1: Monorepo

code
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)

code
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:

code
# 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:

code
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:

code
# 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:

code
# 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:

code
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:

code
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:

code
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:

code
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:

code
# 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:

code
# 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:

code
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:

code
# 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
code
# 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:

code
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:

code
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:

code
# 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:

code
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:

  1. •

    Repository Setup:

    • →Created separate config repository
    • →Organized manifests by environment and application
    • →Implemented Kustomize for configuration management
  2. •

    ArgoCD Deployment:

    • →Installed ArgoCD in management cluster
    • →Configured RBAC and SSO integration
    • →Set up ApplicationSets for multi-cluster management
  3. •

    CI/CD Integration:

    • →Updated CI pipelines to build and push images
    • →Automated manifest updates via GitHub Actions
    • →Implemented image promotion workflow
  4. •

    Developer Onboarding:

    • →Created documentation and templates
    • →Conducted training sessions
    • →Established GitOps best practices
  5. •

    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:

code
# 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:

code
# 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:

code
# 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:

code
# Check for ignored differences
argocd app get my-app --show-operation

# View resource differences
kubectl diff -f manifest.yaml

Solution:

code
# 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:

code
# 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


Ready to implement GitOps? Start with a single application and gradually expand to your entire infrastructure!