ArgoCD: Complete Guide to GitOps Kubernetes Deployments 2026

Published February 12, 2026 · 28 min read

Deploying to Kubernetes with kubectl apply works for a single developer on a single cluster. The moment you add multiple environments, teams, and dozens of microservices, manual deployments become a liability: no audit trail, no easy rollback, and no guarantee that what is running in production matches what is in your Git repository. ArgoCD solves this by making Git the single source of truth for every Kubernetes deployment, continuously reconciling your cluster state against your declared manifests.

This guide covers ArgoCD from first installation through production-grade multi-cluster GitOps, including sync strategies, Helm and Kustomize integration, the app-of-apps pattern, ApplicationSets, RBAC, secrets management, and a head-to-head comparison with Flux. If you are new to Kubernetes, start with our Kubernetes Pods guide first.

⚙ Related: Validate your manifests with the YAML Validator and build Helm charts with the Helm Charts Guide.

Table of Contents

  1. What is ArgoCD and GitOps Principles
  2. Installation (Helm, kubectl, HA)
  3. Core Concepts
  4. Creating Applications
  5. Sync Strategies
  6. Helm Chart Deployments
  7. Kustomize Integration
  8. App-of-Apps Pattern
  9. ApplicationSets
  10. RBAC and SSO Configuration
  11. Secrets Management
  12. Multi-Cluster Deployments
  13. Rollback and Disaster Recovery
  14. ArgoCD vs Flux Comparison
  15. Best Practices and Common Pitfalls

1. What is ArgoCD and GitOps Principles

ArgoCD is a declarative, GitOps continuous delivery tool for Kubernetes. It is a CNCF graduated project that watches Git repositories containing Kubernetes manifests and continuously reconciles the live cluster state with the desired state in Git.

GitOps is built on four principles:

ArgoCD runs inside your Kubernetes cluster as a set of controllers. It polls your Git repositories (or listens for webhooks), compares manifests against the live cluster, and reports the sync status. When drift is detected, it either auto-syncs or alerts you depending on configuration.

2. Installation

Quick Install with kubectl

# Create the namespace
kubectl create namespace argocd

# Install ArgoCD (non-HA, good for dev/staging)
kubectl apply -n argocd -f \
  https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

# Get the initial admin password
argocd admin initial-password -n argocd

# Port-forward the server to access the UI
kubectl port-forward svc/argocd-server -n argocd 8080:443

# Login via CLI
argocd login localhost:8080 --username admin --insecure

Install with Helm (recommended for production)

# Add the ArgoCD Helm repo
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update

# Install with custom values
helm install argocd argo/argo-cd \
  --namespace argocd \
  --create-namespace \
  -f argocd-values.yaml

A production argocd-values.yaml:

# argocd-values.yaml
global:
  domain: argocd.example.com

server:
  replicas: 2
  ingress:
    enabled: true
    ingressClassName: nginx
    annotations:
      cert-manager.io/cluster-issuer: letsencrypt-prod
    tls: true
  metrics:
    enabled: true

controller:
  replicas: 1
  metrics:
    enabled: true

repoServer:
  replicas: 2
  metrics:
    enabled: true

redis-ha:
  enabled: true

configs:
  params:
    server.insecure: false
  cm:
    timeout.reconciliation: 180s
    resource.exclusions: |
      - apiGroups: [""]
        kinds: ["Event"]
        clusters: ["*"]

HA Setup

# HA manifests (3 controllers, 3 API servers, 3 repo servers, Redis HA)
kubectl apply -n argocd -f \
  https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/ha/install.yaml

3. Core Concepts

Application

The Application CRD is the fundamental unit in ArgoCD. It maps a Git source (repository + path + revision) to a Kubernetes destination (cluster + namespace).

AppProject

An AppProject groups Applications and defines access boundaries: which repositories can be used, which clusters and namespaces can be deployed to, and which Kubernetes resource kinds are allowed. The default project permits everything; production setups should create restricted projects.

Repository

A Repository is a Git repo (or Helm chart repo, or OCI registry) registered in ArgoCD. Repositories can authenticate via SSH keys, HTTPS tokens, or GitHub App credentials.

# Register a private Git repository
argocd repo add git@github.com:myorg/manifests.git \
  --ssh-private-key-path ~/.ssh/id_ed25519

# Register a Helm chart repository
argocd repo add https://charts.bitnami.com/bitnami \
  --type helm --name bitnami

# Register an OCI Helm repository
argocd repo add ghcr.io/myorg/charts \
  --type helm --name myorg-oci \
  --enable-oci

4. Creating Applications

CLI

# Create an app from a Git repo with a directory of manifests
argocd app create nginx-app \
  --repo https://github.com/myorg/k8s-manifests.git \
  --path nginx \
  --dest-server https://kubernetes.default.svc \
  --dest-namespace default \
  --sync-policy automated \
  --auto-prune \
  --self-heal

# Create an app from a Helm chart
argocd app create redis \
  --repo https://charts.bitnami.com/bitnami \
  --helm-chart redis \
  --revision 19.x \
  --dest-server https://kubernetes.default.svc \
  --dest-namespace redis

Declarative YAML (recommended)

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: myapp
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  source:
    repoURL: https://github.com/myorg/k8s-manifests.git
    targetRevision: main
    path: apps/myapp
  destination:
    server: https://kubernetes.default.svc
    namespace: myapp
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
      - PruneLast=true
    retry:
      limit: 5
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m

Web UI

The ArgoCD web UI at https://argocd.example.com provides a visual interface for creating, syncing, and monitoring applications. Click New App, fill in the repository URL, path, and destination, then click Create. The UI shows a real-time resource tree with health status for every Kubernetes object.

5. Sync Strategies

Manual Sync

# Sync an application manually
argocd app sync myapp

# Sync specific resources only
argocd app sync myapp --resource :Service:myapp-svc

# Dry run to preview changes
argocd app sync myapp --dry-run

# Check diff before syncing
argocd app diff myapp

Automatic Sync

syncPolicy:
  automated:
    # Prune resources that are no longer in Git
    prune: true
    # Revert manual changes made directly to the cluster
    selfHeal: true
    # Only sync when the app is OutOfSync (default)
    allowEmpty: false

Sync Waves and Hooks

Sync waves let you control the order resources are applied. Lower wave numbers sync first. Hooks run at specific phases of the sync process.

# Wave 0: Namespace and RBAC (applied first)
apiVersion: v1
kind: Namespace
metadata:
  name: myapp
  annotations:
    argocd.argoproj.io/sync-wave: "0"
---
# Wave 1: ConfigMaps and Secrets
apiVersion: v1
kind: ConfigMap
metadata:
  name: myapp-config
  annotations:
    argocd.argoproj.io/sync-wave: "1"
---
# Wave 2: Deployments and Services
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  annotations:
    argocd.argoproj.io/sync-wave: "2"
---
# PreSync hook: run database migrations before deployment
apiVersion: batch/v1
kind: Job
metadata:
  name: db-migrate
  annotations:
    argocd.argoproj.io/hook: PreSync
    argocd.argoproj.io/hook-delete-policy: HookSucceeded
spec:
  template:
    spec:
      containers:
        - name: migrate
          image: myorg/myapp:latest
          command: ["python", "manage.py", "migrate"]
      restartPolicy: Never

6. Helm Chart Deployments with ArgoCD

ArgoCD natively supports Helm charts as a source. It renders the chart server-side and applies the resulting manifests — you get the benefits of Helm templating with GitOps reconciliation.

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: prometheus
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://prometheus-community.github.io/helm-charts
    chart: kube-prometheus-stack
    targetRevision: 65.x
    helm:
      releaseName: prometheus
      valuesObject:
        grafana:
          enabled: true
        prometheus:
          prometheusSpec:
            retention: 30d
  destination:
    server: https://kubernetes.default.svc
    namespace: monitoring
  syncPolicy:
    automated: { prune: true, selfHeal: true }
    syncOptions:
      - CreateNamespace=true
      - ServerSideApply=true

Helm Values from Git

# Use a Helm chart from a repo but override values from Git
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: myapp-helm
  namespace: argocd
spec:
  project: default
  sources:
    - repoURL: https://charts.example.com
      chart: myapp
      targetRevision: 2.5.x
      helm:
        valueFiles:
          - $values/environments/production/values.yaml
    - repoURL: https://github.com/myorg/helm-values.git
      targetRevision: main
      ref: values
  destination:
    server: https://kubernetes.default.svc
    namespace: myapp

7. Kustomize Integration

ArgoCD auto-detects kustomization.yaml files and renders them before applying. This is ideal for environment-specific overlays.

# overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - ../../base
patches:
  - path: patches/replicas.yaml
images:
  - name: myapp
    newTag: v2.3.1
# ArgoCD Application pointing to a Kustomize overlay
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: myapp-production
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/myorg/k8s-manifests.git
    targetRevision: main
    path: overlays/production
    kustomize:
      namePrefix: prod-
      commonLabels:
        environment: production
      images:
        - myapp=myregistry.io/myapp:v2.3.1
  destination:
    server: https://kubernetes.default.svc
    namespace: production

8. App-of-Apps Pattern

The app-of-apps pattern uses a single root Application that manages other Application manifests stored in Git. This gives you one entry point to bootstrap an entire cluster.

# Directory structure:
# cluster-bootstrap/
#   root-app.yaml
#   apps/
#     cert-manager.yaml
#     ingress-nginx.yaml
#     prometheus.yaml
#     myapp.yaml

# root-app.yaml - the single app you create manually
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: root
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/myorg/cluster-config.git
    targetRevision: main
    path: apps
  destination:
    server: https://kubernetes.default.svc
    namespace: argocd
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
# apps/cert-manager.yaml - a child application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: cert-manager
  namespace: argocd
  annotations:
    argocd.argoproj.io/sync-wave: "0"
spec:
  project: infrastructure
  source:
    repoURL: https://charts.jetstack.io
    chart: cert-manager
    targetRevision: v1.16.x
    helm:
      valuesObject:
        installCRDs: true
  destination:
    server: https://kubernetes.default.svc
    namespace: cert-manager
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
# apps/myapp.yaml - an application service
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: myapp
  namespace: argocd
  annotations:
    argocd.argoproj.io/sync-wave: "5"
spec:
  project: default
  source:
    repoURL: https://github.com/myorg/myapp-manifests.git
    targetRevision: main
    path: overlays/production
  destination:
    server: https://kubernetes.default.svc
    namespace: myapp
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

Use sync waves on the child Application manifests to control bootstrap order: CRDs and cert-manager first (wave 0), then ingress controllers (wave 1), then monitoring (wave 2), then application services (wave 5).

9. ApplicationSets

ApplicationSets generate multiple Applications from a single template using generators. This is how you scale from tens to hundreds of applications without maintaining individual YAML files.

Git Directory Generator

# Create one Application per directory under apps/
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: cluster-apps
  namespace: argocd
spec:
  goTemplate: true
  goTemplateOptions: ["missingkey=error"]
  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.path}}'
      destination:
        server: https://kubernetes.default.svc
        namespace: '{{.path.basename}}'
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
          - CreateNamespace=true

List Generator

# Deploy the same app to multiple clusters/environments
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: myapp-multi-env
  namespace: argocd
spec:
  goTemplate: true
  generators:
    - list:
        elements:
          - cluster: staging
            url: https://staging-api.example.com
            revision: develop
          - cluster: production
            url: https://prod-api.example.com
            revision: main
  template:
    metadata:
      name: 'myapp-{{.cluster}}'
    spec:
      project: default
      source:
        repoURL: https://github.com/myorg/myapp.git
        targetRevision: '{{.revision}}'
        path: overlays/{{.cluster}}
      destination:
        server: '{{.url}}'
        namespace: myapp

Matrix Generator (combining generators)

# Deploy every app to every cluster
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: all-apps-all-clusters
  namespace: argocd
spec:
  goTemplate: true
  generators:
    - matrix:
        generators:
          - git:
              repoURL: https://github.com/myorg/manifests.git
              revision: main
              directories:
                - path: apps/*
          - clusters:
              selector:
                matchLabels:
                  environment: production
  template:
    metadata:
      name: '{{.path.basename}}-{{.name}}'
    spec:
      project: default
      source:
        repoURL: https://github.com/myorg/manifests.git
        targetRevision: main
        path: '{{.path.path}}'
      destination:
        server: '{{.server}}'
        namespace: '{{.path.basename}}'

10. RBAC and SSO Configuration

RBAC Policies

# argocd-rbac-cm ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-rbac-cm
  namespace: argocd
data:
  policy.default: role:readonly
  policy.csv: |
    # Admins can do everything
    p, role:admin, applications, *, */*, allow
    p, role:admin, clusters, *, *, allow
    p, role:admin, repositories, *, *, allow
    p, role:admin, projects, *, *, allow

    # Developers can sync and view their project's apps
    p, role:developer, applications, get, default/*, allow
    p, role:developer, applications, sync, default/*, allow
    p, role:developer, applications, action/*, default/*, allow
    p, role:developer, logs, get, default/*, allow

    # Viewers can only read
    p, role:viewer, applications, get, */*, allow
    p, role:viewer, logs, get, */*, allow

    # Map SSO groups to roles
    g, platform-team, role:admin
    g, backend-team, role:developer
    g, stakeholders, role:viewer

SSO with OIDC (e.g., Keycloak, Okta, Azure AD)

# argocd-cm ConfigMap - OIDC configuration
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-cm
  namespace: argocd
data:
  url: https://argocd.example.com
  oidc.config: |
    name: Okta
    issuer: https://mycompany.okta.com/oauth2/default
    clientID: 0oa1234567890abcdef
    clientSecret: $oidc.okta.clientSecret
    requestedScopes: [openid, profile, email, groups]

11. Secrets Management

Kubernetes Secrets in Git repositories are base64-encoded, not encrypted. Never commit plain Secrets to Git. Use one of these strategies instead.

Sealed Secrets (Bitnami)

# Install Sealed Secrets controller
helm install sealed-secrets sealed-secrets/sealed-secrets \
  --namespace kube-system

# Encrypt a secret locally (only the cluster can decrypt it)
kubeseal --format yaml < my-secret.yaml > my-sealed-secret.yaml

# The SealedSecret in Git:
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: myapp-db
  namespace: myapp
spec:
  encryptedData:
    DB_PASSWORD: AgBz3e...truncated...==
    DB_HOST: AgCi7f...truncated...==

External Secrets Operator

# ExternalSecret pulls from AWS Secrets Manager, Vault, etc.
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: myapp-secrets
  namespace: myapp
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secrets-manager
    kind: ClusterSecretStore
  target:
    name: myapp-secrets
    creationPolicy: Owner
  data:
    - secretKey: DB_PASSWORD
      remoteRef:
        key: production/myapp/database
        property: password
    - secretKey: API_KEY
      remoteRef:
        key: production/myapp/api
        property: key

HashiCorp Vault with ArgoCD Vault Plugin

# Use path placeholders in manifests - AVP replaces them at render time
apiVersion: v1
kind: Secret
metadata:
  name: myapp-secrets
  annotations:
    avp.kubernetes.io/path: "secret/data/myapp"
type: Opaque
stringData:
  DB_PASSWORD: <DB_PASSWORD>

12. Multi-Cluster Deployments

# Register an external cluster
argocd cluster add my-production-context --name production

# Declarative cluster secret
apiVersion: v1
kind: Secret
metadata:
  name: production-cluster
  namespace: argocd
  labels:
    argocd.argoproj.io/secret-type: cluster
type: Opaque
stringData:
  name: production
  server: https://prod-k8s-api.example.com
  config: |
    {
      "bearerToken": "<service-account-token>",
      "tlsClientConfig": {
        "insecure": false,
        "caData": "<base64-ca-cert>"
      }
    }
# Application targeting a remote cluster
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: myapp-production
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/myorg/manifests.git
    targetRevision: main
    path: overlays/production
  destination:
    server: https://prod-k8s-api.example.com
    namespace: myapp
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

For large-scale multi-cluster, use ApplicationSets with the clusters generator to automatically deploy to every registered cluster matching a label selector.

13. Rollback and Disaster Recovery

# View history and rollback
argocd app history myapp
argocd app rollback myapp 3

# Or revert in Git (preferred GitOps approach)
git revert HEAD && git push origin main
# ArgoCD will auto-sync to the reverted state

# Pin to a specific Git SHA
argocd app set myapp --revision abc123def

Disaster Recovery

# Back up ArgoCD resources
kubectl get applications,appprojects -n argocd -o yaml > argocd-backup.yaml
kubectl get secrets -n argocd -l argocd.argoproj.io/secret-type -o yaml > repos-backup.yaml

# Restore: install ArgoCD, then apply backups
kubectl apply -f argocd-backup.yaml && kubectl apply -f repos-backup.yaml
# ArgoCD will re-sync everything from Git automatically

Since all desired state lives in Git, the ultimate disaster recovery is reinstalling ArgoCD and pointing it at your Git repositories. The app-of-apps pattern makes this a single kubectl apply.

14. ArgoCD vs Flux Comparison

FeatureArgoCDFlux
ArchitectureCentralized server with UI, API, and controllersDecentralized set of individual controllers
Web UIRich built-in UI with resource tree visualizationNo built-in UI (Weave GitOps available separately)
Multi-tenancyAppProjects with fine-grained RBACNamespace-scoped tenancy with service accounts
Multi-clusterCentral management plane for all clustersInstall Flux per cluster (pull model)
Helm supportNative rendering, values from Git or inlineHelmRelease CRD with dependency management
Kustomize supportAuto-detected, inline patchesKustomization CRD with health checks
Progressive deliveryRequires Argo Rollouts (separate project)Native integration with Flagger
SSO/OIDCBuilt-in OIDC, LDAP, SAML supportRelies on Kubernetes RBAC and external auth
NotificationsArgo Notifications (Slack, Teams, webhooks)Notification controller (Slack, Teams, webhooks)
CNCF statusGraduatedGraduated
Scaling patternApplicationSets for bulk generationKustomization overlays and variable substitution
Image automationArgo CD Image Updater (separate)Built-in image reflector and automation controllers

Choose ArgoCD when you want a powerful UI, centralized multi-cluster management, and strong multi-tenancy with RBAC. Choose Flux when you prefer lightweight, composable controllers, need built-in image automation, or want each cluster to be fully self-contained. Many organizations use both: ArgoCD for application delivery, Flux for cluster bootstrapping.

15. Best Practices and Common Pitfalls

Repository Structure

Sync Configuration

Common Pitfalls

Performance at Scale

# Tune controller settings for large clusters (500+ apps)
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-cmd-params-cm
  namespace: argocd
data:
  # Increase status processors for faster reconciliation
  controller.status.processors: "50"
  controller.operation.processors: "25"
  # Shard the controller across multiple replicas
  controller.sharding.algorithm: "round-robin"
  # Increase repo server parallelism
  reposerver.parallelism.limit: "10"

Frequently Asked Questions

What is ArgoCD and how does it implement GitOps?

ArgoCD is a declarative, GitOps continuous delivery tool for Kubernetes. It implements GitOps by continuously monitoring Git repositories that contain Kubernetes manifests, Helm charts, or Kustomize overlays and automatically reconciling the live cluster state with the desired state defined in Git. When someone pushes a change to the Git repository, ArgoCD detects the drift, compares the live state against the desired state, and either automatically syncs or waits for manual approval depending on your configuration. This means Git becomes the single source of truth for your infrastructure, providing full audit trails, easy rollbacks via git revert, and reproducible deployments across environments.

ArgoCD vs Flux: which GitOps tool should I use?

ArgoCD is the better choice if you want a rich web UI for visualizing application state, need multi-tenancy with fine-grained RBAC, or prefer a centralized management plane for multiple clusters. Flux is the better choice if you prefer a lightweight, CLI-driven approach, want tighter integration with cloud-native tooling like Flagger for progressive delivery, or need the controller to run entirely within each target cluster without a central server. ArgoCD excels at the app-of-apps and ApplicationSet patterns for managing hundreds of applications declaratively. Flux excels at composability with its individual controllers. Both are CNCF graduated projects with strong community support.

How do I set up ArgoCD app-of-apps pattern?

The app-of-apps pattern uses a root ArgoCD Application that points to a Git directory containing other Application manifests. First, create a Git repository with a directory (e.g., apps/) where each file is an ArgoCD Application YAML. Then create a root Application that points to that directory with source.path set to apps/. When ArgoCD syncs the root application, it discovers and creates all the child applications automatically. This gives you a single entry point to manage your entire cluster. To add a new application, simply commit a new Application YAML to the apps/ directory. The pattern supports hierarchical structures and sync waves to control bootstrap order.

Conclusion

ArgoCD transforms Kubernetes deployments from imperative, manual processes into declarative, auditable GitOps workflows. Every change flows through Git, giving you version history, code review, and one-click rollbacks. Whether you are deploying a single application or managing hundreds of services across multiple clusters, ArgoCD scales from simple Application manifests to ApplicationSets that generate deployments dynamically.

Start with a single Application pointing at a Git repository, enable automated sync with self-heal and prune, then graduate to the app-of-apps pattern as your cluster grows. Combine ArgoCD with Sealed Secrets or External Secrets Operator for secrets, use sync waves for ordered deployments, and configure RBAC policies to give each team exactly the access they need.

Learn More

Related Resources

Kubernetes Pods Guide
Master pods, containers, lifecycle, and resource management
Helm Charts Guide
Chart structure, templates, values, dependencies, and Helmfile
Docker Compose Guide
Multi-container orchestration with services, networks, and volumes