GitOps with ArgoCD: Continuous Delivery for Kubernetes That Actually Works
How to implement GitOps with ArgoCD — repository structure, Helm chart patterns, progressive delivery with Argo Rollouts, and the rollback strategies that save you at 2am.
Why GitOps Changes How You Think About Deployments
Traditional CD: CI pipeline builds an artifact, SSH into a server (or call kubectl), apply the change.
GitOps: Git is the source of truth for the desired state of the cluster. ArgoCD continuously reconciles the actual cluster state with what's in Git. Deployments become git commits.
The shift matters because: every change is auditable, rollback is git revert, and no human or CI system needs direct kubectl access to production.
Repository Structure
Use the app-of-apps pattern: one repository defines all applications, each pointing to its own Helm chart or manifests.
gitops-config/
├── apps/
│ ├── root-app.yaml # The app-of-apps
│ ├── api-service.yaml
│ ├── worker-service.yaml
│ └── ml-inference.yaml
│
├── charts/
│ ├── api-service/
│ │ ├── Chart.yaml
│ │ ├── values.yaml
│ │ └── templates/
│ │ ├── deployment.yaml
│ │ ├── service.yaml
│ │ ├── hpa.yaml
│ │ └── ingress.yaml
│ └── worker-service/
│
└── environments/
├── production/
│ ├── api-service-values.yaml # Production overrides
│ └── worker-service-values.yaml
└── staging/
├── api-service-values.yaml
└── worker-service-values.yaml
ArgoCD Application Definition
# apps/api-service.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: api-service
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io # Cascading delete
spec:
project: default
source:
repoURL: https://github.com/your-org/gitops-config
targetRevision: HEAD
path: charts/api-service
helm:
valueFiles:
- ../../environments/production/api-service-values.yaml
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true # Delete resources removed from Git
selfHeal: true # Fix any manual cluster changes
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
Progressive Delivery with Argo Rollouts
Replace standard Kubernetes Deployment with an Argo Rollout for canary deployments:
# charts/api-service/templates/rollout.yaml
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: api-service
spec:
replicas: 10
selector:
matchLabels:
app: api-service
template:
spec:
containers:
- name: api
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
resources:
requests:
cpu: "250m"
memory: "512Mi"
strategy:
canary:
canaryService: api-service-canary
stableService: api-service-stable
steps:
- setWeight: 10 # Send 10% to canary
- pause:
duration: 5m # Observe for 5 minutes
- analysis:
templates:
- templateName: error-rate-check
- setWeight: 50
- pause:
duration: 10m
- setWeight: 100
The AnalysisTemplate queries Prometheus to decide if the canary should advance or abort:
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: error-rate-check
spec:
metrics:
- name: error-rate
interval: 1m
successCondition: result[0] < 0.01 # <1% error rate
failureLimit: 3
provider:
prometheus:
address: http://prometheus.monitoring:9090
query: |
sum(rate(http_requests_total{status=~"5..",app="api-service",track="canary"}[2m]))
/
sum(rate(http_requests_total{app="api-service",track="canary"}[2m]))
If the error rate exceeds 1% in three consecutive checks, ArgoCD automatically rolls back the canary.
Deployment Workflow
The CI pipeline's only job is to build and push the image. The deployment is a separate git commit:
# .github/workflows/deploy.yml
- name: Update image tag in GitOps repo
run: |
git clone https://github.com/your-org/gitops-config
cd gitops-config
# Update the image tag using yq
yq -i '.image.tag = "${{ github.sha }}"' \
environments/production/api-service-values.yaml
git config user.email "ci@your-company.com"
git config user.name "CI Bot"
git add .
git commit -m "deploy: api-service ${{ github.sha }}"
git push
# ArgoCD will detect the change and begin rollout automatically
Emergency Rollback
# Option 1: Git revert (preferred — maintains audit trail)
git revert HEAD --no-edit
git push origin main
# ArgoCD detects the change, rolls back within 60 seconds
# Option 2: ArgoCD CLI (faster, for genuine emergencies)
argocd app rollback api-service --revision 42
The golden rule: rollback via git, not via kubectl. Your Git history should tell the complete story of what ran in production and when.