Skip to main content
CI/CD·15 min read

ArgoCD GitOps Setup — Stop Running kubectl apply From Your Laptop

Master ArgoCD for Kubernetes GitOps — architecture, installation, Application CRDs, sync strategies, app-of-apps pattern, RBAC, secrets management, and multi-cluster deployment.

DT

DevOps Engineer & Technical Writer

What is GitOps and Why ArgoCD?

GitOps is an operational framework that takes DevOps best practices used for application development — version control, collaboration, compliance, CI/CD — and applies them to infrastructure automation. In a GitOps workflow, Git becomes the single source of truth for your desired system state, and an automated process ensures your live environment always matches what's declared in your repository.

GITOPS FLOW WITH ARGOCD SOURCE OF TRUTH Git Repository Manifests / Helm / Kustomize ArgoCD Controller Watches Git repo Compares desired state vs actual state Auto-Sync sync KUBERNETES CLUSTER Live State Pods Services ConfigMaps drift detection and reconciliation Developer pushes

Core GitOps Principles

  • Declarative configuration — The entire system is described declaratively.
  • Version controlled — The desired state is stored in Git with full audit trail.
  • Automatically applied — An agent reconciles live state with desired state.
  • Continuously reconciled — Drift is detected and corrected automatically.
  • Why ArgoCD?

    ArgoCD is a declarative, GitOps continuous delivery tool for Kubernetes. It monitors your Git repositories and automatically synchronizes application definitions, configurations, and environments to your clusters.

    Key advantages:

    • Pull-based deployment — No need to expose cluster credentials externally.
    • Real-time drift detection — Identifies when live state diverges from Git.
    • Multi-cluster support — Manage dozens of clusters from one instance.
    • Rich UI — Visualize app topology, sync status, and health.
    • Declarative app management — Applications defined as Kubernetes CRDs.
    • Built-in rollback — Roll back to any synced Git commit instantly.

    ---

    ArgoCD Architecture

    Understanding ArgoCD's internal components helps you troubleshoot, scale, and secure your deployment.

    Core Components

    API Server — Exposes the gRPC/REST API consumed by the Web UI, CLI, and CI/CD systems. Handles authentication, RBAC enforcement, and Git webhook events.

    Repo Server — Clones Git repositories, generates Kubernetes manifests from Helm charts, Kustomize overlays, or plain YAML, and caches results. Stateless and horizontally scalable.

    Application Controller — The reconciliation engine. Continuously monitors running applications, compares live state against desired state in Git, and takes corrective action based on configured policies.

    Redis — Caching layer and backing store for real-time UI event streams.

    Dex (optional) — OpenID Connect identity provider for SSO integration.

    ---

    Installation Methods

    Method 1: Install with kubectl (Quick Start)

    # Create the namespace
    

    kubectl create namespace argocd

    # Install ArgoCD (stable release)

    kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

    # Get the initial admin password

    kubectl -n argocd get secret argocd-initial-admin-secret \

    -o jsonpath="{.data.password}" | base64 -d

    # values-argocd.yaml
    

    global:

    image:

    tag: "v2.12.0"

    server:

    replicas: 2

    ingress:

    enabled: true

    ingressClassName: nginx

    hosts:

    - argocd.example.com

    tls:

    - secretName: argocd-tls

    hosts:

    - argocd.example.com

    controller:

    replicas: 2

    metrics:

    enabled: true

    repoServer:

    replicas: 2

    metrics:

    enabled: true

    redis-ha:

    enabled: true

    Install command:

    helm repo add argo https://argoproj.github.io/argo-helm
    

    helm repo update

    helm install argocd argo/argo-cd \

    --namespace argocd \

    --create-namespace \

    --values values-argocd.yaml

    ---

    Application CRD — Defining Apps Declaratively

    The Application CRD is the heart of ArgoCD. It declares what to deploy, where, and how to sync.

    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/my-org/my-app.git

    targetRevision: main

    path: overlays/production

    destination:

    server: https://kubernetes.default.svc

    namespace: my-app

    syncPolicy:

    automated:

    prune: true

    selfHeal: true

    syncOptions:

    - CreateNamespace=true

    - PrunePropagationPolicy=foreground

    retry:

    limit: 5

    backoff:

    duration: 5s

    factor: 2

    maxDuration: 3m

    Key Fields Explained

    FieldPurpose
    <code class="inline-code">source.repoURL</code>Git repository containing manifests
    <code class="inline-code">source.targetRevision</code>Branch, tag, or commit SHA to track
    <code class="inline-code">source.path</code>Directory within the repo
    <code class="inline-code">destination.server</code>Target cluster API server URL
    <code class="inline-code">destination.namespace</code>Target namespace for resources
    <code class="inline-code">syncPolicy.automated</code>Enable auto-sync on Git changes
    <code class="inline-code">syncPolicy.automated.prune</code>Delete resources removed from Git
    <code class="inline-code">syncPolicy.automated.selfHeal</code>Revert manual changes in cluster

    ---

    Sync Strategies

    ArgoCD offers fine-grained control over how and when applications sync.

    Automatic Sync

    Deploys automatically when Git changes are detected:

    syncPolicy:
    

    automated:

    prune: true

    selfHeal: true

    allowEmpty: false

    Manual Sync

    Requires explicit approval — ideal for production:

    syncPolicy: {}

    Trigger via CLI:

    argocd app sync my-app --revision main --prune

    Sync Windows

    Restrict when syncs can occur:

    apiVersion: argoproj.io/v1alpha1
    

    kind: AppProject

    metadata:

    name: production

    namespace: argocd

    spec:

    syncWindows:

    - kind: allow

    schedule: "0 6 1-5"

    duration: 2h

    applications:

    - "*"

    - kind: deny

    schedule: "0 0 0"

    duration: 24h

    applications:

    - "*"

    Sync Phases and Hooks

    Control deployment ordering with resource hooks:

    apiVersion: batch/v1
    

    kind: Job

    metadata:

    name: db-migration

    annotations:

    argocd.argoproj.io/hook: PreSync

    argocd.argoproj.io/hook-delete-policy: HookSucceeded

    spec:

    template:

    spec:

    containers:

    - name: migrate

    image: my-app:latest

    command: ["./migrate.sh"]

    restartPolicy: Never

    Available phases: PreSync, Sync, PostSync, SyncFail.

    ---

    App-of-Apps Pattern

    The app-of-apps pattern uses a single ArgoCD Application to manage a collection of child Applications. This is the recommended approach for managing multiple services at scale.

    Root Application

    apiVersion: argoproj.io/v1alpha1
    

    kind: Application

    metadata:

    name: platform-apps

    namespace: argocd

    spec:

    project: default

    source:

    repoURL: https://github.com/my-org/platform-apps.git

    targetRevision: main

    path: apps

    destination:

    server: https://kubernetes.default.svc

    namespace: argocd

    syncPolicy:

    automated:

    prune: true

    selfHeal: true

    Child Application (apps/frontend.yaml)

    apiVersion: argoproj.io/v1alpha1
    

    kind: Application

    metadata:

    name: frontend

    namespace: argocd

    labels:

    team: platform

    env: production

    spec:

    project: default

    source:

    repoURL: https://github.com/my-org/frontend.git

    targetRevision: v2.1.0

    path: deploy/production

    destination:

    server: https://kubernetes.default.svc

    namespace: frontend

    syncPolicy:

    automated:

    prune: true

    selfHeal: true

    syncOptions:

    - CreateNamespace=true

    ApplicationSet — Dynamic App Generation

    For dynamic environments, ApplicationSet generates Applications from templates:

    apiVersion: argoproj.io/v1alpha1
    

    kind: ApplicationSet

    metadata:

    name: microservices

    namespace: argocd

    spec:

    generators:

    - git:

    repoURL: https://github.com/my-org/services.git

    revision: main

    directories:

    - path: "services/*"

    template:

    metadata:

    name: "{{path.basename}}"

    spec:

    project: default

    source:

    repoURL: https://github.com/my-org/services.git

    targetRevision: main

    path: "{{path}}"

    destination:

    server: https://kubernetes.default.svc

    namespace: "{{path.basename}}"

    syncPolicy:

    automated:

    prune: true

    selfHeal: true

    syncOptions:

    - CreateNamespace=true

    This automatically creates an Application for each subdirectory under services/.

    ---

    Secrets Management with ArgoCD

    Storing secrets in Git violates security best practices. Here are three production-proven approaches.

    Option 1: Sealed Secrets (Bitnami)

    Sealed Secrets encrypts secrets client-side so they can be safely stored in Git:

    apiVersion: bitnami.com/v1alpha1
    

    kind: SealedSecret

    metadata:

    name: database-credentials

    namespace: my-app

    spec:

    encryptedData:

    DB_PASSWORD: AgBy8hCi...encrypted...base64==

    DB_USERNAME: AgA2kF9r...encrypted...base64==

    template:

    metadata:

    name: database-credentials

    namespace: my-app

    type: Opaque

    Generate sealed secrets:

    kubeseal --controller-name=sealed-secrets \
    

    --controller-namespace=kube-system \

    --format yaml < secret.yaml > sealed-secret.yaml

    Option 2: External Secrets Operator

    Pulls secrets from external providers (AWS Secrets Manager, Vault, GCP Secret Manager):

    apiVersion: external-secrets.io/v1beta1
    

    kind: ExternalSecret

    metadata:

    name: database-credentials

    namespace: my-app

    spec:

    refreshInterval: 1h

    secretStoreRef:

    name: aws-secrets-manager

    kind: ClusterSecretStore

    target:

    name: database-credentials

    creationPolicy: Owner

    data:

    - secretKey: DB_PASSWORD

    remoteRef:

    key: /production/database

    property: password

    - secretKey: DB_USERNAME

    remoteRef:

    key: /production/database

    property: username

    ClusterSecretStore configuration:

    apiVersion: external-secrets.io/v1beta1
    

    kind: ClusterSecretStore

    metadata:

    name: aws-secrets-manager

    spec:

    provider:

    aws:

    service: SecretsManager

    region: us-east-1

    auth:

    jwt:

    serviceAccountRef:

    name: external-secrets-sa

    namespace: external-secrets

    Option 3: HashiCorp Vault with ArgoCD Vault Plugin

    apiVersion: argoproj.io/v1alpha1
    

    kind: Application

    metadata:

    name: my-app

    namespace: argocd

    spec:

    source:

    repoURL: https://github.com/my-org/my-app.git

    targetRevision: main

    path: manifests

    plugin:

    name: argocd-vault-plugin

    env:

    - name: VAULT_ADDR

    value: https://vault.example.com

    - name: AVP_TYPE

    value: vault

    - name: AVP_AUTH_TYPE

    value: k8s

    Secret template with Vault placeholders:

    apiVersion: v1
    

    kind: Secret

    metadata:

    name: database-credentials

    annotations:

    avp.kubernetes.io/path: "secret/data/production/database"

    type: Opaque

    stringData:

    DB_PASSWORD: <password>

    DB_USERNAME: <username>

    ---

    Multi-Cluster Deployment

    ArgoCD excels at managing applications across multiple clusters from a single control plane.

    Register External Clusters

    # Add cluster via CLI
    

    argocd cluster add my-production-cluster \

    --name production \

    --kubeconfig ~/.kube/production-config

    # List registered clusters

    argocd cluster list

    Declarative Cluster Registration

    apiVersion: v1
    

    kind: Secret

    metadata:

    name: production-cluster

    namespace: argocd

    labels:

    argocd.argoproj.io/secret-type: cluster

    type: Opaque

    stringData:

    name: production

    server: https://production-k8s.example.com:6443

    config: |

    {

    "bearerToken": "<service-account-token>",

    "tlsClientConfig": {

    "insecure": false,

    "caData": "<base64-ca-cert>"

    }

    }

    Multi-Cluster ApplicationSet

    Deploy the same app across all registered clusters:

    apiVersion: argoproj.io/v1alpha1
    

    kind: ApplicationSet

    metadata:

    name: monitoring-stack

    namespace: argocd

    spec:

    generators:

    - clusters:

    selector:

    matchLabels:

    env: production

    template:

    metadata:

    name: "monitoring-{{name}}"

    spec:

    project: default

    source:

    repoURL: https://github.com/my-org/monitoring.git

    targetRevision: main

    path: base

    destination:

    server: "{{server}}"

    namespace: monitoring

    syncPolicy:

    automated:

    prune: true

    selfHeal: true

    ---

    RBAC and SSO Configuration

    RBAC Policy

    ArgoCD RBAC uses a Casbin-based model. Configure via ConfigMap:

    apiVersion: v1
    

    kind: ConfigMap

    metadata:

    name: argocd-rbac-cm

    namespace: argocd

    data:

    policy.default: role:readonly

    policy.csv: |

    # Roles

    p, role:developers, applications, get, /, allow

    p, role:developers, applications, sync, /, allow

    p, role:developers, logs, get, /, allow

    p, role:admins, applications, , /*, allow

    p, role:admins, clusters, , , allow

    p, role:admins, repositories, , , allow

    p, role:admins, projects, , , allow

    # Group bindings

    g, dev-team, role:developers

    g, platform-team, role:admins

    scopes: "[groups]"

    SSO with OIDC (Keycloak Example)

    apiVersion: v1
    

    kind: ConfigMap

    metadata:

    name: argocd-cm

    namespace: argocd

    data:

    url: https://argocd.example.com

    oidc.config: |

    name: Keycloak

    issuer: https://keycloak.example.com/realms/devops

    clientID: argocd

    clientSecret: $oidc.keycloak.clientSecret

    requestedScopes:

    - openid

    - profile

    - email

    - groups

    requestedIDTokenClaims:

    groups:

    essential: true

    Store the client secret:

    apiVersion: v1
    

    kind: Secret

    metadata:

    name: argocd-secret

    namespace: argocd

    type: Opaque

    stringData:

    oidc.keycloak.clientSecret: "your-client-secret-here"

    ---

    ArgoCD + Helm + Kustomize Integration

    Helm Source Configuration

    apiVersion: argoproj.io/v1alpha1
    

    kind: Application

    metadata:

    name: nginx-ingress

    namespace: argocd

    spec:

    project: default

    source:

    repoURL: https://kubernetes.github.io/ingress-nginx

    chart: ingress-nginx

    targetRevision: 4.8.3

    helm:

    releaseName: ingress-nginx

    valuesObject:

    controller:

    replicaCount: 3

    metrics:

    enabled: true

    resources:

    requests:

    cpu: 100m

    memory: 128Mi

    limits:

    cpu: 500m

    memory: 512Mi

    destination:

    server: https://kubernetes.default.svc

    namespace: ingress-nginx

    syncPolicy:

    automated:

    prune: true

    syncOptions:

    - CreateNamespace=true

    Kustomize with Patches

    apiVersion: argoproj.io/v1alpha1
    

    kind: Application

    metadata:

    name: my-app-production

    namespace: argocd

    spec:

    project: default

    source:

    repoURL: https://github.com/my-org/my-app.git

    targetRevision: main

    path: deploy/overlays/production

    kustomize:

    namePrefix: prod-

    commonLabels:

    env: production

    images:

    - name: my-app

    newName: registry.example.com/my-app

    newTag: v2.1.0

    destination:

    server: https://kubernetes.default.svc

    namespace: my-app-production

    Multi-Source Applications (Helm + Values from Git)

    apiVersion: argoproj.io/v1alpha1
    

    kind: Application

    metadata:

    name: my-app

    namespace: argocd

    spec:

    project: default

    sources:

    - repoURL: https://charts.example.com

    chart: my-app

    targetRevision: 1.2.3

    helm:

    valueFiles:

    - $values/environments/production/values.yaml

    - repoURL: https://github.com/my-org/app-config.git

    targetRevision: main

    ref: values

    destination:

    server: https://kubernetes.default.svc

    namespace: my-app

    ---

    Rollback Strategies

    ArgoCD maintains a history of all synced revisions, making rollbacks straightforward.

    CLI Rollback

    # View deployment history
    

    argocd app history my-app

    # Rollback to a specific revision

    argocd app rollback my-app 3

    # Rollback to a specific Git commit

    argocd app sync my-app --revision abc123def

    Automated Rollback with Analysis

    Combine with Argo Rollouts for progressive delivery:

    apiVersion: argoproj.io/v1alpha1
    

    kind: Rollout

    metadata:

    name: my-app

    spec:

    replicas: 5

    strategy:

    canary:

    steps:

    - setWeight: 20

    - pause: { duration: 5m }

    - analysis:

    templates:

    - templateName: success-rate

    - setWeight: 50

    - pause: { duration: 5m }

    - analysis:

    templates:

    - templateName: success-rate

    - setWeight: 100

    analysis:

    templates:

    - templateName: success-rate

    args:

    - name: service-name

    value: my-app

    Analysis Template for Automated Rollback

    apiVersion: argoproj.io/v1alpha1
    

    kind: AnalysisTemplate

    metadata:

    name: success-rate

    spec:

    args:

    - name: service-name

    metrics:

    - name: success-rate

    interval: 1m

    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.."

    }[5m])) /

    sum(rate(http_requests_total{

    service="{{args.service-name}}"

    }[5m]))

    ---

    Production Setup Best Practices

    1. High Availability Configuration

    # HA values for Helm installation
    

    controller:

    replicas: 2

    env:

    - name: ARGOCD_CONTROLLER_REPLICAS

    value: "2"

    server:

    replicas: 2

    autoscaling:

    enabled: true

    minReplicas: 2

    maxReplicas: 5

    repoServer:

    replicas: 2

    autoscaling:

    enabled: true

    minReplicas: 2

    maxReplicas: 5

    redis-ha:

    enabled: true

    haproxy:

    enabled: true

    2. Resource Limits

    controller:
    

    resources:

    requests:

    cpu: 500m

    memory: 512Mi

    limits:

    cpu: "2"

    memory: 2Gi

    repoServer:

    resources:

    requests:

    cpu: 250m

    memory: 256Mi

    limits:

    cpu: "1"

    memory: 1Gi

    server:

    resources:

    requests:

    cpu: 100m

    memory: 128Mi

    limits:

    cpu: 500m

    memory: 512Mi

    3. AppProject Isolation

    Restrict what each team can deploy:

    apiVersion: argoproj.io/v1alpha1
    

    kind: AppProject

    metadata:

    name: team-backend

    namespace: argocd

    spec:

    description: Backend team project

    sourceRepos:

    - "https://github.com/my-org/backend-*"

    destinations:

    - namespace: "backend-*"

    server: https://kubernetes.default.svc

    clusterResourceWhitelist:

    - group: ""

    kind: Namespace

    namespaceResourceBlacklist:

    - group: ""

    kind: ResourceQuota

    - group: ""

    kind: LimitRange

    roles:

    - name: developers

    description: Backend developers

    policies:

    - p, proj:team-backend:developers, applications, sync, team-backend/*, allow

    - p, proj:team-backend:developers, applications, get, team-backend/*, allow

    groups:

    - backend-developers

    4. Notifications Configuration

    apiVersion: v1
    

    kind: ConfigMap

    metadata:

    name: argocd-notifications-cm

    namespace: argocd

    data:

    service.slack: |

    token: $slack-token

    trigger.on-sync-failed: |

    - when: app.status.sync.status == 'Unknown'

    send: [app-sync-failed]

    trigger.on-health-degraded: |

    - when: app.status.health.status == 'Degraded'

    send: [app-health-degraded]

    template.app-sync-failed: |

    slack:

    attachments: |

    [{

    "color": "#E96D76",

    "title": "{{.app.metadata.name}} sync failed",

    "text": "Application {{.app.metadata.name}} sync failed.\nRevision: {{.app.status.sync.revision}}"

    }]

    template.app-health-degraded: |

    slack:

    attachments: |

    [{

    "color": "#f4c030",

    "title": "{{.app.metadata.name}} health degraded",

    "text": "Application {{.app.metadata.name}} is degraded."

    }]

    5. Repository Credentials Template

    apiVersion: v1
    

    kind: Secret

    metadata:

    name: private-repo-creds

    namespace: argocd

    labels:

    argocd.argoproj.io/secret-type: repo-creds

    type: Opaque

    stringData:

    type: git

    url: https://github.com/my-org

    password: ghp_xxxxxxxxxxxxxxxxxxxx

    username: argocd-bot

    6. Performance Tuning

    Key settings for large-scale deployments:

    apiVersion: v1
    

    kind: ConfigMap

    metadata:

    name: argocd-cmd-params-cm

    namespace: argocd

    data:

    # Increase repo server parallelism

    reposerver.parallelism.limit: "50"

    # Controller settings

    controller.status.processors: "50"

    controller.operation.processors: "25"

    controller.repo.server.timeout.seconds: "300"

    # Server settings

    server.enable.gzip: "true"

    ---

    Summary

    ArgoCD transforms Kubernetes deployments from imperative scripts into a declarative, auditable, and self-healing process. Here is a quick decision guide:

    ScenarioRecommended Approach
    Single team, few appsBasic Application CRDs with manual sync
    Multiple teamsAppProjects + RBAC + App-of-Apps
    Dynamic environmentsApplicationSets with Git/cluster generators
    Secrets in GitExternal Secrets Operator or Sealed Secrets
    Multi-clusterCentralized ArgoCD + cluster registration
    Progressive deliveryArgoCD + Argo Rollouts
    Production HARedis-HA + replicated components + monitoring

    Start simple with a single Application resource, then grow into app-of-apps and ApplicationSets as your platform matures. The GitOps model pays dividends in auditability, disaster recovery (redeploy everything from Git), and developer velocity once the initial patterns are established.

    ---

    Frequently Asked Questions

    What is ArgoCD and why should I use it for Kubernetes?

    ArgoCD is a declarative GitOps continuous delivery tool for Kubernetes that automatically syncs your cluster state with Git repositories. It provides a visual UI showing deployment status, drift detection, and automated rollbacks. Use it when you want Git to be your single source of truth for Kubernetes deployments.

    How do I fix ArgoCD sync failed status?

    First check the sync status details in the ArgoCD UI or run argocd app get <app-name> to see specific error messages. Common causes include invalid YAML manifests, resource quota exceeded, or missing CRDs. Fix the issue in your Git repository and ArgoCD will automatically attempt to re-sync.

    What is the difference between ArgoCD and Flux?

    Both are GitOps tools for Kubernetes, but ArgoCD provides a rich web UI and application-centric model, while Flux is more lightweight and controller-based. ArgoCD offers better visualization and RBAC out of the box, whereas Flux integrates more tightly with Helm and has a smaller resource footprint. Choose ArgoCD for teams that value UI visibility and Flux for minimal overhead.

    How do I handle secrets in ArgoCD GitOps workflows?

    Never store plain-text secrets in Git. Use tools like Sealed Secrets, SOPS with age/GPG encryption, or External Secrets Operator that syncs secrets from AWS Secrets Manager or HashiCorp Vault. ArgoCD supports these through custom plugins or direct integration with external secret stores.

    Can ArgoCD deploy to multiple clusters?

    Yes, ArgoCD supports multi-cluster deployments from a single control plane. Register additional clusters using argocd cluster add <context-name> and reference the cluster in your Application spec's destination field. This enables centralized GitOps management across development, staging, and production clusters.