Back to Blog
DevOps
Cloud

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.

8 min readMay 20, 2026Netvionix Team
GitOps with ArgoCD: Continuous Delivery for Kubernetes That Actually Works

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.