GitOps revolutionizes how we manage infrastructure and applications by using Git as the single source of truth. This comprehensive guide explores GitOps principles and shows you how to implement them using ArgoCD and Flux CD.
What is GitOps?
GitOps is a operational framework that applies DevOps best practices like version control, collaboration, and CI/CD to infrastructure automation:
Core Principles:
- Declarative: System desired state expressed declaratively
- Versioned: Desired state stored in Git
- Pulled Automatically: Software agents automatically pull desired state
- Continuously Reconciled: Software agents ensure correct state
Why GitOps?
Benefits over traditional deployment methods:
- Single Source of Truth: Git repo contains complete system state
- Version Control: Full audit trail of changes
- Easy Rollbacks: Simply revert Git commits
- Security: Pull-based deployments, no external access needed
- Developer Productivity: Use familiar Git workflows
- Disaster Recovery: Entire cluster state in Git
- Compliance: Automated enforcement of policies
GitOps vs Traditional CI/CD
Traditional CI/CD:
Git Push → CI Build → CD Tool Pushes → Cluster
GitOps:
Git Push → GitOps Operator Pulls → Cluster Syncs
ArgoCD: Getting Started
Installation
# Create namespace
kubectl create namespace argocd
# Install ArgoCD
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# Wait for pods to be ready
kubectl wait --for=condition=ready pod --all -n argocd --timeout=300s
# Get admin password
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
# Port forward to access UI
kubectl port-forward svc/argocd-server -n argocd 8080:443
# Access UI at https://localhost:8080
# Username: admin
# Password: (from command above)
# Install ArgoCD CLI
curl -sSL -o argocd-linux-amd64 https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
sudo install -m 555 argocd-linux-amd64 /usr/local/bin/argocd
rm argocd-linux-amd64
# Login via CLI
argocd login localhost:8080 --username admin --password <password> --insecure
First Application
Create a Git repository with this structure:
my-app/
├── k8s/
│ ├── deployment.yaml
│ ├── service.yaml
│ └── ingress.yaml
└── README.md
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: nginx:1.25
ports:
- containerPort: 80
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
---
apiVersion: v1
kind: Service
metadata:
name: my-app
namespace: default
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 80
type: ClusterIP
Create ArgoCD Application:
# application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/myorg/my-app
targetRevision: main
path: k8s
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
allowEmpty: false
syncOptions:
- CreateNamespace=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
Apply the application:
# Using kubectl
kubectl apply -f application.yaml
# Or using ArgoCD CLI
argocd app create my-app \
--repo https://github.com/myorg/my-app \
--path k8s \
--dest-server https://kubernetes.default.svc \
--dest-namespace default \
--sync-policy automated \
--auto-prune \
--self-heal
# View application status
argocd app get my-app
# Sync application
argocd app sync my-app
# View logs
argocd app logs my-app
Flux CD: Getting Started
Installation
# Install Flux CLI
curl -s https://fluxcd.io/install.sh | sudo bash
# Verify installation
flux --version
# Check prerequisites
flux check --pre
# Bootstrap Flux with GitHub
export GITHUB_TOKEN=<your-token>
export GITHUB_USER=<your-username>
export GITHUB_REPO=<your-repo-name>
flux bootstrap github \
--owner=$GITHUB_USER \
--repository=$GITHUB_REPO \
--branch=main \
--path=./clusters/production \
--personal
# Check Flux components
flux check
# View all Flux resources
kubectl get all -n flux-system
First Application with Flux
# apps/my-app/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: my-app
---
# apps/my-app/source.yaml
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: my-app
namespace: flux-system
spec:
interval: 1m
url: https://github.com/myorg/my-app
ref:
branch: main
secretRef:
name: git-credentials
---
# apps/my-app/kustomization.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: my-app
namespace: flux-system
spec:
interval: 5m
path: "./k8s"
prune: true
sourceRef:
kind: GitRepository
name: my-app
healthChecks:
- apiVersion: apps/v1
kind: Deployment
name: my-app
namespace: my-app
timeout: 2m
# Apply Flux resources
kubectl apply -f apps/my-app/
# Watch reconciliation
flux get kustomizations --watch
# View logs
flux logs --follow --level=info
# Trigger manual reconciliation
flux reconcile kustomization my-app --with-source
Advanced ArgoCD Patterns
Multi-Environment Setup
# apps/dev/application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app-dev
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/myorg/my-app
targetRevision: develop
path: k8s/overlays/dev
destination:
server: https://kubernetes.default.svc
namespace: dev
syncPolicy:
automated:
prune: true
selfHeal: true
---
# apps/staging/application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app-staging
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/myorg/my-app
targetRevision: main
path: k8s/overlays/staging
destination:
server: https://kubernetes.default.svc
namespace: staging
syncPolicy:
automated:
prune: true
selfHeal: false # Manual approval for staging
---
# apps/production/application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app-production
namespace: argocd
spec:
project: production
source:
repoURL: https://github.com/myorg/my-app
targetRevision: v1.0.0 # Use tags for production
path: k8s/overlays/production
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: false # Never auto-prune in production
selfHeal: false # Manual approval required
syncOptions:
- CreateNamespace=true
- PruneLast=true
# Ignore differences in specific fields
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas # Allow HPA to control replicas
App of Apps Pattern
# apps/root-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: root-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/myorg/gitops-config
targetRevision: main
path: apps
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true
selfHeal: true
Directory structure:
gitops-config/
├── apps/
│ ├── dev/
│ │ ├── app1.yaml
│ │ └── app2.yaml
│ ├── staging/
│ │ ├── app1.yaml
│ │ └── app2.yaml
│ └── production/
│ ├── app1.yaml
│ └── app2.yaml
└── infrastructure/
├── ingress-nginx.yaml
├── cert-manager.yaml
└── prometheus.yaml
ArgoCD with Helm
# apps/my-helm-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-helm-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/myorg/helm-charts
targetRevision: main
path: charts/my-app
helm:
releaseName: my-app
valueFiles:
- values.yaml
- values-production.yaml
parameters:
- name: image.tag
value: v1.0.0
- name: replicas
value: "3"
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
ArgoCD with Kustomize
# apps/kustomize-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: kustomize-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/myorg/my-app
targetRevision: main
path: k8s/overlays/production
kustomize:
images:
- nginx:1.25=nginx:1.26
commonLabels:
environment: production
commonAnnotations:
managed-by: argocd
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
Advanced Flux Patterns
Multi-Tenant Setup
# clusters/production/tenants/tenant-a/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: tenant-a
---
# clusters/production/tenants/tenant-a/sync.yaml
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: tenant-a
namespace: flux-system
spec:
interval: 1m
url: https://github.com/tenant-a/apps
ref:
branch: main
secretRef:
name: tenant-a-git-credentials
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: tenant-a
namespace: flux-system
spec:
interval: 5m
path: "./apps"
prune: true
sourceRef:
kind: GitRepository
name: tenant-a
targetNamespace: tenant-a
serviceAccountName: tenant-a-reconciler
validation: client
Flux with Helm Releases
# apps/my-app/helmrelease.yaml
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
name: bitnami
namespace: flux-system
spec:
interval: 1h
url: https://charts.bitnami.com/bitnami
---
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: nginx
namespace: default
spec:
interval: 5m
chart:
spec:
chart: nginx
version: '13.x'
sourceRef:
kind: HelmRepository
name: bitnami
namespace: flux-system
values:
replicaCount: 3
service:
type: LoadBalancer
resources:
limits:
cpu: 200m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi
install:
remediation:
retries: 3
upgrade:
remediation:
retries: 3
test:
enable: true
Flux Image Automation
# apps/my-app/image-policy.yaml
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
name: my-app
namespace: flux-system
spec:
image: myregistry.azurecr.io/my-app
interval: 1m
secretRef:
name: acr-credentials
---
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
name: my-app
namespace: flux-system
spec:
imageRepositoryRef:
name: my-app
policy:
semver:
range: '>=1.0.0 <2.0.0'
---
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageUpdateAutomation
metadata:
name: my-app
namespace: flux-system
spec:
interval: 1m
sourceRef:
kind: GitRepository
name: my-app
git:
checkout:
ref:
branch: main
commit:
author:
email: fluxcdbot@example.com
name: Flux CD Bot
messageTemplate: |
Update image to {{range .Updated.Images}}{{println .}}{{end}}
push:
branch: main
update:
path: ./k8s
strategy: Setters
In your Kubernetes manifests:
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
template:
spec:
containers:
- name: my-app
image: myregistry.azurecr.io/my-app:1.0.0 # {"$imagepolicy": "flux-system:my-app"}
Progressive Delivery with Argo Rollouts
# Install Argo Rollouts
kubectl create namespace argo-rollouts
kubectl apply -n argo-rollouts -f https://github.com/argoproj/argo-rollouts/releases/latest/download/install.yaml
# Rollout manifest
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: my-app
spec:
replicas: 5
strategy:
canary:
steps:
- setWeight: 20
- pause: {duration: 1m}
- setWeight: 40
- pause: {duration: 1m}
- setWeight: 60
- pause: {duration: 1m}
- setWeight: 80
- pause: {duration: 1m}
revisionHistoryLimit: 2
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: myapp:1.0.0
ports:
- containerPort: 8080
Blue-Green Deployment
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: my-app
spec:
replicas: 3
revisionHistoryLimit: 2
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: myapp:2.0.0
ports:
- containerPort: 8080
strategy:
blueGreen:
activeService: my-app-active
previewService: my-app-preview
autoPromotionEnabled: false
scaleDownDelaySeconds: 30
Monitoring and Observability
ArgoCD Metrics
# ServiceMonitor for Prometheus
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: argocd-metrics
namespace: argocd
spec:
selector:
matchLabels:
app.kubernetes.io/name: argocd-server
endpoints:
- port: metrics
Flux Monitoring
# Install Flux monitoring stack
flux create source git flux-monitoring \
--url=https://github.com/fluxcd/flux2 \
--branch=main
flux create kustomization monitoring \
--source=flux-monitoring \
--path="./manifests/monitoring/prometheus" \
--prune=true \
--interval=1h
Best Practices
Repository Structure
gitops-repo/
├── apps/
│ ├── base/
│ │ ├── deployment.yaml
│ │ ├── service.yaml
│ │ └── kustomization.yaml
│ └── overlays/
│ ├── dev/
│ │ ├── kustomization.yaml
│ │ └── replica-patch.yaml
│ ├── staging/
│ │ ├── kustomization.yaml
│ │ └── replica-patch.yaml
│ └── production/
│ ├── kustomization.yaml
│ └── replica-patch.yaml
├── infrastructure/
│ ├── nginx-ingress/
│ ├── cert-manager/
│ └── prometheus/
└── clusters/
├── dev/
├── staging/
└── production/
Security Best Practices
✅ Use SSH keys for Git access
✅ Implement RBAC for ArgoCD/Flux
✅ Use sealed secrets or external secret managers
✅ Enable webhook signatures
✅ Implement resource quotas per namespace
✅ Use namespaced installations for multi-tenancy
✅ Enable audit logging
✅ Regularly update ArgoCD/Flux versions
Sealed Secrets
# Install Sealed Secrets controller
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.0/controller.yaml
# Install kubeseal CLI
wget https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.0/kubeseal-0.24.0-linux-amd64.tar.gz
tar -xvzf kubeseal-0.24.0-linux-amd64.tar.gz
sudo install -m 755 kubeseal /usr/local/bin/kubeseal
# Create sealed secret
kubectl create secret generic my-secret \
--from-literal=password=supersecret \
--dry-run=client -o yaml | \
kubeseal -o yaml > sealed-secret.yaml
# Apply sealed secret (safe to commit)
kubectl apply -f sealed-secret.yaml
Disaster Recovery
Backup ArgoCD
# Export all applications
kubectl get applications -n argocd -o yaml > argocd-apps-backup.yaml
# Export all projects
kubectl get appprojects -n argocd -o yaml > argocd-projects-backup.yaml
# Backup using Velero
velero backup create argocd-backup --include-namespaces argocd
Backup Flux
# Backup Flux resources
flux export source git --all > flux-sources-backup.yaml
flux export kustomization --all > flux-kustomizations-backup.yaml
flux export helmrelease --all > flux-helmreleases-backup.yaml
Troubleshooting
ArgoCD
# Check application sync status
argocd app get my-app
# View sync details
argocd app get my-app --show-operation
# Check diff
argocd app diff my-app
# View logs
argocd app logs my-app --tail 100 --follow
# Refresh application
argocd app get my-app --refresh
# Hard refresh (force sync)
argocd app sync my-app --force
Flux
# Check Git source status
flux get sources git
# Check kustomization status
flux get kustomizations
# View reconciliation logs
flux logs --kind=Kustomization --name=my-app --follow
# Suspend reconciliation
flux suspend kustomization my-app
# Resume reconciliation
flux resume kustomization my-app
# Force reconciliation
flux reconcile kustomization my-app --with-source
Conclusion
GitOps transforms infrastructure and application management by leveraging Git’s version control capabilities. Whether you choose ArgoCD or Flux CD, implementing GitOps provides better visibility, security, and reliability for your Kubernetes deployments. Both tools excel at different use cases—ArgoCD shines with its UI and application management, while Flux excels at flexibility and multi-tenancy.
Resources
Which GitOps tool do you prefer? Share your experiences in the comments!