Back to Software Engineering & Delivery Mastery Series CI/CD Platform Deep Dive

CI/CD Platform Deep Dive: Argo CD & GitOps

May 14, 2026 Wasil Zafar 44 min read

Master Argo CD and GitOps — declarative delivery for Kubernetes, Application CRDs, sync policies, multi-cluster management, ApplicationSets, and progressive delivery with Argo Rollouts for zero-downtime deployments.

Table of Contents

  1. Introduction
  2. GitOps Principles
  3. Argo CD Architecture
  4. Installation & Setup
  5. Application CRD
  6. Sync Policies
  7. ApplicationSets
  8. Multi-Cluster Management
  9. Argo Rollouts
  10. Integration Patterns
  11. Repository Structure
  12. Security
  13. Troubleshooting
  14. Exercises

Introduction

Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes. Unlike traditional CI/CD platforms that push deployments to clusters, Argo CD pulls — it continuously monitors Git repositories and automatically synchronizes the live cluster state with the desired state defined in Git. When someone commits a change to the manifests repository, Argo CD detects the drift and reconciles it.

This fundamental inversion — from "CI pushes to cluster" to "cluster pulls from Git" — eliminates an entire class of problems: credential sprawl (no cluster credentials in CI systems), configuration drift (continuous reconciliation corrects manual changes), and audit gaps (Git history becomes the definitive deployment record).

Key Insight: Argo CD doesn't build your code or run your tests — that's still CI's job. Argo CD handles the delivery side: taking tested, built artifacts and ensuring they run in your clusters exactly as specified in Git. It's the "CD" in CI/CD, purpose-built for Kubernetes.

Pull vs Push Deployment

Traditional CI/CD (Jenkins, GitHub Actions, GitLab CI) uses a push model: the CI system authenticates to the cluster and applies changes. This requires storing cluster credentials in the CI system — a security risk. Pull-based GitOps inverts this: the agent running inside the cluster watches Git and applies changes locally, eliminating external credential exposure.

GitOps Principles

GitOps is a set of practices that use Git as the single source of truth for declarative infrastructure and applications. The four core principles (formalized by the OpenGitOps project under CNCF):

  1. Declarative — The entire system is described declaratively (Kubernetes manifests, Helm charts, Kustomize overlays).
  2. Versioned and Immutable — The desired state is stored in Git, providing full history, audit trail, and easy rollback via git revert.
  3. Pulled Automatically — Software agents (Argo CD) automatically pull the desired state from Git — no manual kubectl apply.
  4. Continuously Reconciled — Agents compare desired state (Git) with actual state (cluster) and correct any drift automatically.
GitOps Workflow: Pull-Based Delivery
flowchart LR
    DEV[Developer] -->|Push code| CI[CI Pipeline]
    CI -->|Build & Test| REG[Container Registry]
    CI -->|Update manifests| GIT[Git Repo - Manifests]
    
    GIT -->|Watches| ARGO[Argo CD]
    ARGO -->|Syncs| K8S[Kubernetes Cluster]
    K8S -->|Pulls image| REG
    
    K8S -.->|Status| ARGO
    ARGO -.->|Drift detection| GIT
    
    style DEV fill:#132440,color:#fff
    style CI fill:#16476A,color:#fff
    style GIT fill:#3B9797,color:#fff
    style ARGO fill:#BF092F,color:#fff
    style K8S fill:#132440,color:#fff
    style REG fill:#16476A,color:#fff
                            

The separation of concerns is critical: CI handles building and testing (producing artifacts), while CD (Argo CD) handles deploying those artifacts to clusters. The manifests repository is the contract between them.

Argo CD Architecture

Argo CD runs as a set of Kubernetes controllers in your cluster. Understanding its components helps with debugging, scaling, and security hardening.

Argo CD Internal Architecture
flowchart TD
    UI[Web UI / CLI] --> API[API Server]
    API --> REPO[Repo Server]
    API --> CTRL[Application Controller]
    API --> REDIS[Redis Cache]
    
    REPO -->|Clone & render| GIT[Git Repositories]
    CTRL -->|Watch & sync| K8S[Kubernetes API]
    CTRL --> REDIS
    REPO --> REDIS
    
    DEX[Dex - SSO] --> API
    NOTIFY[Notifications Controller] --> CTRL
    
    style API fill:#3B9797,color:#fff
    style CTRL fill:#132440,color:#fff
    style REPO fill:#16476A,color:#fff
    style REDIS fill:#BF092F,color:#fff
                            
  • API Server — Exposes the gRPC/REST API consumed by the Web UI, CLI, and CI systems. Handles authentication and authorization.
  • Repository Server — Clones Git repos, renders Helm charts, processes Kustomize overlays, and caches the results. Stateless and horizontally scalable.
  • Application Controller — The core reconciliation loop. Watches Application CRDs, compares desired vs actual state, and performs syncs. Runs leader-elected for HA.
  • Redis — In-memory cache for repo manifests and app state. Reduces Git polling and API server load.
  • Dex — OpenID Connect provider for SSO integration (LDAP, SAML, GitHub, GitLab, etc.).

Installation & Setup

Argo CD can be installed via plain manifests, Helm chart, or as part of a managed offering (OpenShift GitOps, Akuity). Here are the primary methods:

# Method 1: Plain manifests (non-HA, good for dev/test)
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

# Method 2: HA installation (production)
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/ha/install.yaml

# Method 3: Helm chart (most configurable)
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update
helm install argocd argo/argo-cd \
  --namespace argocd \
  --create-namespace \
  --set server.service.type=LoadBalancer \
  --set configs.params."server\.insecure"=true
# Get initial admin password
kubectl -n argocd get secret argocd-initial-admin-secret \
  -o jsonpath="{.data.password}" | base64 -d

# Install Argo CD CLI
curl -sSL -o argocd-linux-amd64 \
  https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
chmod +x argocd-linux-amd64
sudo mv argocd-linux-amd64 /usr/local/bin/argocd

# Login via CLI
argocd login argocd.example.com --username admin --password $INITIAL_PASSWORD

# Change admin password
argocd account update-password

Application CRD

The Application is Argo CD's core custom resource. It defines what to deploy (source), where to deploy (destination), and how to sync (policy). Every managed workload is represented by an Application.

# Basic Application manifest
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: default

  source:
    repoURL: https://github.com/myorg/my-app-manifests.git
    targetRevision: main
    path: overlays/production

  destination:
    server: https://kubernetes.default.svc
    namespace: production

  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
      - ApplyOutOfSyncOnly=true
    retry:
      limit: 5
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m
# Application with Helm source
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: prometheus
  namespace: argocd
spec:
  project: monitoring
  source:
    repoURL: https://prometheus-community.github.io/helm-charts
    chart: kube-prometheus-stack
    targetRevision: 55.5.0
    helm:
      releaseName: prometheus
      valuesObject:
        grafana:
          enabled: true
          adminPassword: ${grafana_password}
        alertmanager:
          enabled: true
        prometheus:
          prometheusSpec:
            retention: 30d
            storageSpec:
              volumeClaimTemplate:
                spec:
                  resources:
                    requests:
                      storage: 50Gi
  destination:
    server: https://kubernetes.default.svc
    namespace: monitoring
  syncPolicy:
    automated:
      selfHeal: true

Sync Policies

Sync policies control how and when Argo CD reconciles the desired state with the actual state. The key settings:

Sync Policy Options

Manual Sync: Argo CD detects drift but waits for explicit user action (UI click or CLI command) before applying changes. Best for production environments requiring human approval.

Auto Sync: Argo CD automatically applies changes when drift is detected. Enables true continuous delivery — commit to Git and changes deploy automatically.

Self-Heal: If someone manually changes a resource in the cluster (e.g., kubectl edit), Argo CD reverts it to match Git. Prevents configuration drift from manual interventions.

Prune: When resources are removed from Git, automatically delete them from the cluster. Without prune, removed manifests leave orphaned resources.

# Sync windows — restrict when syncs can occur
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: production
  namespace: argocd
spec:
  syncWindows:
    # Allow syncs only during business hours on weekdays
    - kind: allow
      schedule: '0 9 * * 1-5'
      duration: 8h
      applications:
        - '*'
    # Deny all syncs during weekends
    - kind: deny
      schedule: '0 0 * * 0,6'
      duration: 48h
      applications:
        - '*'
    # Allow emergency hotfixes anytime for critical apps
    - kind: allow
      schedule: '* * * * *'
      duration: 24h
      applications:
        - 'critical-*'
      manualSync: true

ApplicationSets

ApplicationSets dynamically generate multiple Applications from a single template. They're essential for multi-cluster deployments, multi-tenant platforms, and monorepo management. Instead of manually creating 50 Application manifests for 50 microservices, an ApplicationSet generates them from a directory structure or cluster list.

# Git Directory Generator — one Application per subdirectory
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: microservices
  namespace: argocd
spec:
  generators:
    - git:
        repoURL: https://github.com/myorg/k8s-manifests.git
        revision: main
        directories:
          - path: apps/*
  template:
    metadata:
      name: '{{path.basename}}'
    spec:
      project: default
      source:
        repoURL: https://github.com/myorg/k8s-manifests.git
        targetRevision: main
        path: '{{path}}'
      destination:
        server: https://kubernetes.default.svc
        namespace: '{{path.basename}}'
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
          - CreateNamespace=true
# Cluster Generator — deploy to all registered clusters
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: platform-services
  namespace: argocd
spec:
  generators:
    - clusters:
        selector:
          matchLabels:
            environment: production
  template:
    metadata:
      name: 'platform-{{name}}'
    spec:
      project: platform
      source:
        repoURL: https://github.com/myorg/platform-manifests.git
        targetRevision: main
        path: base
        kustomize:
          patches:
            - target:
                kind: Namespace
              patch: |
                - op: replace
                  path: /metadata/labels/cluster
                  value: '{{name}}'
      destination:
        server: '{{server}}'
        namespace: platform
      syncPolicy:
        automated:
          selfHeal: true
# Matrix Generator — combine clusters x environments
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: multi-env-deploy
  namespace: argocd
spec:
  generators:
    - matrix:
        generators:
          - clusters:
              selector:
                matchLabels:
                  tier: production
          - list:
              elements:
                - app: frontend
                  path: apps/frontend
                - app: backend
                  path: apps/backend
                - app: worker
                  path: apps/worker
  template:
    metadata:
      name: '{{app}}-{{name}}'
    spec:
      source:
        repoURL: https://github.com/myorg/manifests.git
        path: '{{path}}/overlays/{{metadata.labels.region}}'
      destination:
        server: '{{server}}'
        namespace: '{{app}}'

Multi-Cluster Management

Argo CD excels at managing deployments across multiple Kubernetes clusters from a single control plane. The hub-and-spoke model has one Argo CD instance (the hub) managing applications across all target clusters (spokes).

Hub-and-Spoke Multi-Cluster Architecture
flowchart TD
    GIT[Git Repository] --> HUB[Argo CD Hub Cluster]
    
    HUB -->|Syncs| PROD1[Production US-East]
    HUB -->|Syncs| PROD2[Production EU-West]
    HUB -->|Syncs| STAGING[Staging]
    HUB -->|Syncs| DEV[Development]
    
    HUB -->|Manages itself| HUB
    
    style GIT fill:#3B9797,color:#fff
    style HUB fill:#BF092F,color:#fff
    style PROD1 fill:#132440,color:#fff
    style PROD2 fill:#132440,color:#fff
    style STAGING fill:#16476A,color:#fff
    style DEV fill:#16476A,color:#fff
                            
# Register a remote cluster with Argo CD
argocd cluster add my-production-cluster \
  --name production-us-east \
  --kubeconfig ~/.kube/config \
  --context production-us-east

# List registered clusters
argocd cluster list

# Add cluster labels for ApplicationSet generators
argocd cluster set production-us-east \
  --label environment=production \
  --label region=us-east
Security: Cluster credentials are stored as Kubernetes Secrets in the Argo CD namespace. Use short-lived tokens, OIDC federation, or the Argo CD Agent model (where agents in remote clusters pull from the hub) to minimize credential exposure.

Argo Rollouts

Argo Rollouts extends Kubernetes with advanced deployment strategies — canary releases with traffic management, blue-green deployments, and automated analysis-driven promotions. It replaces the standard Kubernetes Deployment with a Rollout CRD that provides fine-grained control over how new versions are introduced.

# Canary Rollout with traffic management and analysis
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: my-app
  namespace: production
spec:
  replicas: 10
  revisionHistoryLimit: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
        - name: app
          image: myregistry/my-app:latest
          ports:
            - containerPort: 8080
  strategy:
    canary:
      canaryService: my-app-canary
      stableService: my-app-stable
      trafficRouting:
        istio:
          virtualServices:
            - name: my-app-vsvc
              routes:
                - primary
      steps:
        - setWeight: 5
        - pause: { duration: 2m }
        - analysis:
            templates:
              - templateName: success-rate
            args:
              - name: service-name
                value: my-app-canary
        - setWeight: 25
        - pause: { duration: 5m }
        - analysis:
            templates:
              - templateName: success-rate
        - setWeight: 50
        - pause: { duration: 5m }
        - setWeight: 100
# Analysis Template — automated promotion/rollback decisions
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: success-rate
spec:
  args:
    - name: service-name
  metrics:
    - name: success-rate
      interval: 30s
      count: 5
      successCondition: result[0] >= 0.95
      failureLimit: 3
      provider:
        prometheus:
          address: http://prometheus.monitoring:9090
          query: |
            sum(rate(http_requests_total{service="{{args.service-name}}",status=~"2.."}[2m]))
            /
            sum(rate(http_requests_total{service="{{args.service-name}}"}[2m]))
# Blue-Green Rollout
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: my-app-bg
spec:
  replicas: 5
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
        - name: app
          image: myregistry/my-app:latest
  strategy:
    blueGreen:
      activeService: my-app-active
      previewService: my-app-preview
      autoPromotionEnabled: false
      prePromotionAnalysis:
        templates:
          - templateName: smoke-tests
      postPromotionAnalysis:
        templates:
          - templateName: success-rate
      scaleDownDelaySeconds: 300

Integration Patterns

Argo CD integrates with the broader Argo ecosystem and CI tools to form complete delivery pipelines:

  • Argo CD + CI Pipeline — CI builds the image, updates the manifests repo (image tag), Argo CD detects the change and syncs.
  • Argo CD Image Updater — Automatically detects new container images in registries and updates the Application's image tag without CI involvement.
  • Argo CD + Argo Events — Event-driven triggers (webhooks, message queues) that create Argo CD sync operations or ApplicationSets.
  • Argo CD + Argo Workflows — Complex pre/post-sync operations (database migrations, smoke tests) orchestrated as Argo Workflows triggered by sync hooks.
# CI pipeline step: Update manifests repo after image build
# (GitHub Actions example — triggers Argo CD sync)
- name: Update manifests
  run: |
    git clone https://github.com/myorg/k8s-manifests.git
    cd k8s-manifests
    
    # Update image tag in kustomization
    cd overlays/production
    kustomize edit set image myregistry/my-app=myregistry/my-app:${{ github.sha }}
    
    git add .
    git commit -m "deploy: my-app ${{ github.sha }}"
    git push

Repository Structure

How you organize your GitOps repositories significantly impacts maintainability and security. Common patterns:

Repository Strategies

App of Apps: A root Application that manages other Applications. The root points to a directory of Application manifests — adding a new YAML file to that directory automatically creates a new managed application.

Monorepo: All manifests in one repository with directory-based separation (apps/frontend, apps/backend, platform/). Simpler access control but Git history is shared.

Multi-repo: Each application has its own manifests repository. Better isolation and access control, but more repositories to manage.

Recommended: Monorepo with Kustomize overlays for most teams. Multi-repo for strict team isolation requirements.

# Recommended monorepo structure with Kustomize
k8s-manifests/
├── apps/
│   ├── frontend/
│   │   ├── base/
│   │   │   ├── deployment.yaml
│   │   │   ├── service.yaml
│   │   │   └── kustomization.yaml
│   │   └── overlays/
│   │       ├── staging/
│   │       │   ├── kustomization.yaml
│   │       │   └── replica-patch.yaml
│   │       └── production/
│   │           ├── kustomization.yaml
│   │           ├── replica-patch.yaml
│   │           └── hpa.yaml
│   ├── backend/
│   │   ├── base/
│   │   └── overlays/
│   └── worker/
│       ├── base/
│       └── overlays/
├── platform/
│   ├── cert-manager/
│   ├── ingress-nginx/
│   └── monitoring/
└── argocd-apps/
    ├── frontend.yaml
    ├── backend.yaml
    └── platform.yaml

Security

Argo CD security spans RBAC, SSO, project isolation, and secrets management:

  • RBAC — Fine-grained policies controlling who can sync, override, delete, or view Applications. Integrated with SSO groups.
  • Projects — Logical groupings that restrict which repositories, clusters, and namespaces an Application can target. Prevent teams from deploying to each other's namespaces.
  • SSO — Integrate with OIDC providers (Okta, Azure AD, GitHub) via the bundled Dex server.
  • Secrets — Argo CD cannot natively manage secrets (they'd be in Git). Use Sealed Secrets, External Secrets Operator, or Vault for secret injection.
# Argo CD RBAC policy
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-rbac-cm
  namespace: argocd
data:
  policy.csv: |
    # Team frontend can manage their apps
    p, role:frontend-team, applications, *, frontend-project/*, allow
    p, role:frontend-team, logs, get, frontend-project/*, allow
    
    # Platform team can manage all apps and clusters
    p, role:platform-admin, applications, *, */*, allow
    p, role:platform-admin, clusters, *, *, allow
    p, role:platform-admin, projects, *, *, allow
    
    # Map SSO groups to roles
    g, frontend-devs, role:frontend-team
    g, platform-eng, role:platform-admin
  policy.default: role:readonly

Troubleshooting

Common Argo CD issues and their resolutions:

  • OutOfSync but healthy — Resource has fields set by Kubernetes (defaulted fields, status subresource) that don't match the manifest. Fix with ignoreDifferences in the Application spec.
  • Sync failed: "resource already exists" — Resource was created outside Argo CD. Adopt it by adding the argocd.argoproj.io/managed-by annotation.
  • Slow sync with many resources — Increase Application Controller replicas, enable sharding, or split large Applications into smaller ones.
  • Repository not accessible — Check repository credentials in argocd-repo-creds secret. Verify network connectivity from the repo-server pod.
# Ignore differences for fields managed by Kubernetes
spec:
  ignoreDifferences:
    - group: apps
      kind: Deployment
      jsonPointers:
        - /spec/replicas  # Managed by HPA
    - group: ""
      kind: Service
      jqPathExpressions:
        - .spec.clusterIP  # Auto-assigned by k8s

Exercises

Exercise 1: Complete GitOps Setup

Install Argo CD in a local Kind/k3d cluster. Create a manifests repository with a simple nginx deployment using Kustomize (base + staging overlay). Create an Application that auto-syncs from the staging overlay. Verify that pushing a replica count change to Git results in automatic cluster reconciliation.

Exercise 2: ApplicationSet for Microservices

Create a monorepo with 3 microservices (frontend, backend, worker), each with base and overlay directories. Write an ApplicationSet using the Git Directory generator that automatically creates an Application for each service. Add a 4th service directory and verify the Application is created without modifying the ApplicationSet.

Exercise 3: Progressive Delivery with Rollouts

Install Argo Rollouts alongside Argo CD. Convert a Deployment to a Rollout with canary strategy (5% → 25% → 50% → 100% with 2-minute pauses). Create an AnalysisTemplate that checks HTTP success rate from a metrics source. Deploy a new version and observe the progressive rollout with automated analysis.

Exercise 4: Multi-Cluster App of Apps

Register two clusters with Argo CD (can be separate Kind clusters). Create an App of Apps pattern where a root Application manages child Applications. Use an ApplicationSet with the Cluster generator to deploy a platform service (e.g., ingress-nginx) to both clusters simultaneously. Implement project-level RBAC restricting each team to their namespace.