ArgoCD ApplicationSets revolutionize how enterprises manage applications across multiple environments, clusters, and tenants. This comprehensive guide explores advanced ApplicationSet patterns, generators, and automation strategies for production-scale GitOps implementations.

ArgoCD ApplicationSets for Multi-Environment Deployment: Enterprise GitOps at Scale

Executive Summary

ApplicationSets extend ArgoCD’s capabilities to automatically generate and manage Applications across multiple targets using templated specifications. This guide covers advanced ApplicationSet patterns including cluster generators, matrix generators, progressive delivery strategies, and enterprise-scale multi-tenancy implementations that enable teams to manage thousands of applications across hundreds of clusters.

Understanding ApplicationSets Architecture

Core Concepts

ApplicationSets introduce a controller that watches ApplicationSet resources and generates Application resources based on specified generators:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: multi-environment-app
  namespace: argocd
spec:
  # Generators produce parameters for templating
  generators:
  - list:
      elements:
      - cluster: prod-us-east
        environment: production
        replicas: "5"
        resources:
          cpu: "1000m"
          memory: "2Gi"
      - cluster: prod-eu-west
        environment: production
        replicas: "5"
        resources:
          cpu: "1000m"
          memory: "2Gi"
      - cluster: staging
        environment: staging
        replicas: "2"
        resources:
          cpu: "500m"
          memory: "1Gi"
      - cluster: dev
        environment: development
        replicas: "1"
        resources:
          cpu: "250m"
          memory: "512Mi"

  # Template for generating Applications
  template:
    metadata:
      name: '{{cluster}}-myapp'
      labels:
        environment: '{{environment}}'
      annotations:
        notifications.argoproj.io/subscribe.on-sync-succeeded.slack: 'deployments'
    spec:
      project: default
      source:
        repoURL: https://github.com/example/myapp
        targetRevision: HEAD
        path: deploy/{{environment}}
        helm:
          parameters:
          - name: replicas
            value: '{{replicas}}'
          - name: resources.cpu
            value: '{{resources.cpu}}'
          - name: resources.memory
            value: '{{resources.memory}}'
      destination:
        server: https://kubernetes.default.svc
        namespace: myapp-{{environment}}
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
        - CreateNamespace=true
        retry:
          limit: 5
          backoff:
            duration: 5s
            factor: 2
            maxDuration: 3m

ApplicationSet Controller Configuration

apiVersion: apps/v1
kind: Deployment
metadata:
  name: argocd-applicationset-controller
  namespace: argocd
spec:
  replicas: 2
  selector:
    matchLabels:
      app.kubernetes.io/name: argocd-applicationset-controller
  template:
    metadata:
      labels:
        app.kubernetes.io/name: argocd-applicationset-controller
    spec:
      serviceAccountName: argocd-applicationset-controller
      containers:
      - name: argocd-applicationset-controller
        image: quay.io/argoproj/argocd:v2.9.0
        command:
        - argocd-applicationset-controller
        args:
        - --metrics-addr=:8080
        - --probe-addr=:8081
        - --webhook-addr=:7000
        # Performance tuning
        - --concurrent-reconciliations=10
        - --max-reconciliation-rate=100
        # Enable all generators
        - --enable-progressive-syncs=true
        - --enable-scm-providers=true
        # Policy configuration
        - --policy=create-update
        - --enable-policy-override=false
        # Logging
        - --loglevel=info
        - --log-format=json
        env:
        - name: NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_PROGRESSIVE_SYNCS
          value: "true"
        - name: ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_NEW_GIT_FILE_GLOBBING
          value: "true"
        resources:
          requests:
            cpu: 500m
            memory: 512Mi
          limits:
            cpu: 1000m
            memory: 1Gi
        ports:
        - containerPort: 8080
          name: metrics
        - containerPort: 8081
          name: probe
        - containerPort: 7000
          name: webhook
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8081
        readinessProbe:
          httpGet:
            path: /readyz
            port: 8081

Generator Patterns

Cluster Generator

Automatically discover and deploy to clusters:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: cluster-addons
  namespace: argocd
spec:
  generators:
  - clusters:
      selector:
        matchLabels:
          argocd.argoproj.io/secret-type: cluster
        matchExpressions:
        - key: environment
          operator: In
          values: [production, staging]
      values:
        # Default values
        revision: main
        logLevel: info
        # Template override values
        clusterResourceWhitelist: |
          - group: '*'
            kind: '*'

  template:
    metadata:
      name: '{{name}}-addons'
      labels:
        cluster: '{{name}}'
        environment: '{{metadata.labels.environment}}'
    spec:
      project: cluster-addons
      source:
        repoURL: https://github.com/example/cluster-addons
        targetRevision: '{{values.revision}}'
        path: 'addons/{{metadata.labels.environment}}'
        directory:
          recurse: true
          jsonnet:
            extVars:
            - name: clusterName
              value: '{{name}}'
            - name: environment
              value: '{{metadata.labels.environment}}'
            - name: logLevel
              value: '{{values.logLevel}}'
      destination:
        server: '{{server}}'
        namespace: kube-system
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
        - CreateNamespace=true
        - ServerSideApply=true
      ignoreDifferences:
      - group: apps
        kind: Deployment
        jsonPointers:
        - /spec/replicas
---
apiVersion: v1
kind: Secret
metadata:
  name: prod-us-east-cluster
  namespace: argocd
  labels:
    argocd.argoproj.io/secret-type: cluster
    environment: production
    region: us-east-1
    tier: production
type: Opaque
stringData:
  name: prod-us-east
  server: https://prod-us-east.k8s.example.com
  config: |
    {
      "bearerToken": "<token>",
      "tlsClientConfig": {
        "insecure": false,
        "caData": "<base64-ca-cert>"
      }
    }

Git File Generator

Generate applications from files in Git repositories:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: tenant-applications
  namespace: argocd
spec:
  generators:
  - git:
      repoURL: https://github.com/example/tenant-config
      revision: HEAD
      files:
      - path: "tenants/*/config.json"

  template:
    metadata:
      name: '{{tenant}}-{{app}}'
      labels:
        tenant: '{{tenant}}'
        app: '{{app}}'
      finalizers:
      - resources-finalizer.argocd.argoproj.io
    spec:
      project: '{{tenant}}'
      source:
        repoURL: '{{repoUrl}}'
        targetRevision: '{{targetRevision}}'
        path: '{{path}}'
        helm:
          parameters:
          - name: namespace
            value: '{{namespace}}'
          - name: replicas
            value: '{{replicas}}'
          valueFiles:
          - 'values-{{environment}}.yaml'
      destination:
        server: '{{clusterUrl}}'
        namespace: '{{namespace}}'
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
        - CreateNamespace=true
        retry:
          limit: 5
          backoff:
            duration: 5s
            factor: 2
            maxDuration: 3m
---
# Example tenant config file: tenants/acme/config.json
{
  "tenant": "acme",
  "app": "web-portal",
  "repoUrl": "https://github.com/acme/web-portal",
  "targetRevision": "main",
  "path": "deploy/k8s",
  "environment": "production",
  "clusterUrl": "https://prod-cluster.k8s.example.com",
  "namespace": "acme-web",
  "replicas": "3"
}

Matrix Generator

Combine multiple generators for complex scenarios:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: multi-cluster-multi-env
  namespace: argocd
spec:
  generators:
  # Matrix combines cluster and git generators
  - matrix:
      generators:
      # First dimension: clusters
      - clusters:
          selector:
            matchLabels:
              argocd.argoproj.io/secret-type: cluster
          values:
            # Cluster-specific defaults
            monitoring: "true"

      # Second dimension: applications from git
      - git:
          repoURL: https://github.com/example/apps
          revision: HEAD
          directories:
          - path: apps/*
          - path: apps/excluded-app
            exclude: true

  template:
    metadata:
      name: '{{path.basename}}-{{name}}'
      labels:
        app: '{{path.basename}}'
        cluster: '{{name}}'
        environment: '{{metadata.labels.environment}}'
    spec:
      project: default
      source:
        repoURL: https://github.com/example/apps
        targetRevision: HEAD
        path: '{{path}}'
        helm:
          parameters:
          # Cluster-specific parameters
          - name: cluster.name
            value: '{{name}}'
          - name: cluster.region
            value: '{{metadata.labels.region}}'
          # Environment-specific parameters
          - name: environment
            value: '{{metadata.labels.environment}}'
          - name: monitoring.enabled
            value: '{{values.monitoring}}'
          # Dynamic resource allocation
          - name: resources.requests.cpu
            value: '{{metadata.annotations.default-cpu}}'
          - name: resources.requests.memory
            value: '{{metadata.annotations.default-memory}}'
      destination:
        server: '{{server}}'
        namespace: '{{path.basename}}-{{metadata.labels.environment}}'
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
        - CreateNamespace=true
        - ApplyOutOfSyncOnly=true
      # Environment-specific sync waves
      info:
      - name: Environment
        value: '{{metadata.labels.environment}}'
      - name: Cluster
        value: '{{name}}'

Pull Request Generator

Automatically create preview environments for pull requests:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: pr-preview-environments
  namespace: argocd
spec:
  generators:
  - pullRequest:
      github:
        owner: example
        repo: myapp
        tokenRef:
          secretName: github-token
          key: token
        labels:
        - preview
      requeueAfterSeconds: 60

  template:
    metadata:
      name: 'pr-{{number}}-{{branch_slug}}'
      labels:
        preview: "true"
        pr-number: "{{number}}"
      annotations:
        argocd.argoproj.io/compare-options: IgnoreExtraneous
        notifications.argoproj.io/subscribe.on-deployed.github: ""
    spec:
      project: preview-environments
      source:
        repoURL: '{{head_repo_url}}'
        targetRevision: '{{head_sha}}'
        path: deploy/k8s
        helm:
          parameters:
          - name: image.tag
            value: 'pr-{{number}}'
          - name: ingress.host
            value: 'pr-{{number}}.preview.example.com'
          - name: resources.limits.cpu
            value: "500m"
          - name: resources.limits.memory
            value: "512Mi"
      destination:
        server: https://kubernetes.default.svc
        namespace: 'preview-pr-{{number}}'
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
        - CreateNamespace=true
      # Auto-cleanup after 7 days
      info:
      - name: PR
        value: '{{number}}'
      - name: Branch
        value: '{{branch}}'
      - name: Author
        value: '{{head_sha_short}}'
---
apiVersion: v1
kind: Secret
metadata:
  name: github-token
  namespace: argocd
type: Opaque
stringData:
  token: <github-personal-access-token>

SCM Provider Generator

Automatically discover repositories from SCM providers:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: all-microservices
  namespace: argocd
spec:
  generators:
  - scmProvider:
      github:
        organization: example
        tokenRef:
          secretName: github-token
          key: token
        allBranches: false
      filters:
      # Only repositories with specific topics
      - repositoryMatch: ".*-service$"
      - pathExists: deploy/kubernetes
      - labelMatch: "deploy-to-prod"
      cloneProtocol: https
      requeueAfterSeconds: 300

  template:
    metadata:
      name: '{{repository}}'
      labels:
        app: '{{repository}}'
        team: '{{organization}}'
      annotations:
        app-url: '{{url}}'
    spec:
      project: microservices
      source:
        repoURL: '{{url}}'
        targetRevision: '{{branch}}'
        path: deploy/kubernetes
        kustomize:
          namePrefix: '{{repository}}-'
          commonLabels:
            app: '{{repository}}'
            version: '{{sha_short}}'
      destination:
        server: https://kubernetes.default.svc
        namespace: '{{repository}}'
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
        - CreateNamespace=true
        - PruneLast=true
      # Resource customization
      ignoreDifferences:
      - group: apps
        kind: Deployment
        jsonPointers:
        - /spec/replicas

Multi-Environment Patterns

Environment Promotion Strategy

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: progressive-deployment
  namespace: argocd
spec:
  generators:
  - list:
      elements:
      # Development environment - auto-sync
      - environment: dev
        cluster: dev-cluster
        autoSync: "true"
        prune: "true"
        selfHeal: "true"
        namespace: myapp-dev
        replicaCount: "1"
        imageTag: "latest"

      # Staging environment - auto-sync with approval
      - environment: staging
        cluster: staging-cluster
        autoSync: "true"
        prune: "true"
        selfHeal: "false"
        namespace: myapp-staging
        replicaCount: "2"
        imageTag: "{{GIT_TAG}}"

      # Production environment - manual sync only
      - environment: prod
        cluster: prod-cluster
        autoSync: "false"
        prune: "false"
        selfHeal: "false"
        namespace: myapp-prod
        replicaCount: "5"
        imageTag: "{{APPROVED_TAG}}"

  template:
    metadata:
      name: 'myapp-{{environment}}'
      labels:
        environment: '{{environment}}'
      annotations:
        notifications.argoproj.io/subscribe.on-sync-running.slack: 'deployments-{{environment}}'
        notifications.argoproj.io/subscribe.on-sync-succeeded.slack: 'deployments-{{environment}}'
        notifications.argoproj.io/subscribe.on-sync-failed.slack: 'deployments-{{environment}}'
    spec:
      project: myapp
      source:
        repoURL: https://github.com/example/myapp
        targetRevision: HEAD
        path: deploy/{{environment}}
        helm:
          parameters:
          - name: replicaCount
            value: '{{replicaCount}}'
          - name: image.tag
            value: '{{imageTag}}'
          - name: environment
            value: '{{environment}}'
      destination:
        server: '{{cluster}}'
        namespace: '{{namespace}}'
      syncPolicy:
        automated:
          prune: '{{prune}}' == 'true'
          selfHeal: '{{selfHeal}}' == 'true'
        syncOptions:
        - CreateNamespace=true
        retry:
          limit: 3
          backoff:
            duration: 5s
            factor: 2
            maxDuration: 1m
---
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: myapp
  namespace: argocd
spec:
  description: MyApp project with environment-specific policies

  sourceRepos:
  - https://github.com/example/myapp

  destinations:
  # Development
  - namespace: myapp-dev
    server: https://dev-cluster.example.com
  # Staging
  - namespace: myapp-staging
    server: https://staging-cluster.example.com
  # Production with restrictions
  - namespace: myapp-prod
    server: https://prod-cluster.example.com

  clusterResourceWhitelist:
  - group: ''
    kind: Namespace

  namespaceResourceWhitelist:
  - group: apps
    kind: Deployment
  - group: ''
    kind: Service
  - group: ''
    kind: ConfigMap
  - group: ''
    kind: Secret
  - group: networking.k8s.io
    kind: Ingress

  # Deny certain resources in production
  namespaceResourceBlacklist:
  - group: ''
    kind: ResourceQuota

  # Require manual approval for production
  syncWindows:
  - kind: deny
    schedule: '* * * * *'
    duration: 24h
    applications:
    - myapp-prod
    manualSync: true

Blue-Green Deployment Pattern

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: blue-green-deployment
  namespace: argocd
spec:
  generators:
  - matrix:
      generators:
      # Environments
      - list:
          elements:
          - environment: production
            cluster: https://prod.k8s.example.com

      # Blue/Green slots
      - list:
          elements:
          - slot: blue
            active: "true"
            weight: "100"
          - slot: green
            active: "false"
            weight: "0"

  template:
    metadata:
      name: 'myapp-{{environment}}-{{slot}}'
      labels:
        environment: '{{environment}}'
        slot: '{{slot}}'
        active: '{{active}}'
    spec:
      project: default
      source:
        repoURL: https://github.com/example/myapp
        targetRevision: HEAD
        path: deploy/base
        kustomize:
          commonLabels:
            slot: '{{slot}}'
          patches:
          - target:
              kind: Service
              name: myapp
            patch: |
              - op: replace
                path: /spec/selector/slot
                value: {{slot}}
          - target:
              kind: Ingress
              name: myapp
            patch: |
              - op: add
                path: /metadata/annotations/nginx.ingress.kubernetes.io~1canary
                value: "{{active == 'false'}}"
              - op: add
                path: /metadata/annotations/nginx.ingress.kubernetes.io~1canary-weight
                value: "{{weight}}"
      destination:
        server: '{{cluster}}'
        namespace: 'myapp-{{environment}}'
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
        - CreateNamespace=true

Canary Deployment with Progressive Delivery

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: canary-deployment
  namespace: argocd
spec:
  generators:
  - list:
      elements:
      # Stable version
      - version: stable
        tag: v1.0.0
        weight: "90"
        replicas: "9"

      # Canary version
      - version: canary
        tag: v1.1.0
        weight: "10"
        replicas: "1"

  template:
    metadata:
      name: 'myapp-{{version}}'
      labels:
        version: '{{version}}'
      annotations:
        argocd.argoproj.io/sync-wave: '{{version == "stable" && "1" || "2"}}'
    spec:
      project: default
      source:
        repoURL: https://github.com/example/myapp
        targetRevision: '{{tag}}'
        path: deploy/k8s
        helm:
          parameters:
          - name: replicaCount
            value: '{{replicas}}'
          - name: image.tag
            value: '{{tag}}'
          - name: service.version
            value: '{{version}}'
          - name: canary.weight
            value: '{{weight}}'
      destination:
        server: https://kubernetes.default.svc
        namespace: myapp-production
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
        - CreateNamespace=true
      # Analysis template for automated promotion
      info:
      - name: Analysis
        value: |
          apiVersion: argoproj.io/v1alpha1
          kind: AnalysisTemplate
          metadata:
            name: success-rate
          spec:
            metrics:
            - name: success-rate
              interval: 1m
              successCondition: result >= 0.95
              provider:
                prometheus:
                  address: http://prometheus.monitoring:9090
                  query: |
                    sum(rate(http_requests_total{status=~"2.."}[5m]))
                    /
                    sum(rate(http_requests_total[5m]))

Multi-Cluster Management

Cluster Registration Automation

apiVersion: batch/v1
kind: CronJob
metadata:
  name: cluster-discovery
  namespace: argocd
spec:
  schedule: "*/5 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          serviceAccountName: cluster-discovery
          containers:
          - name: discover
            image: bitnami/kubectl:1.28
            command:
            - /bin/bash
            - -c
            - |
              #!/bin/bash
              set -e

              # Discover clusters from cloud provider
              CLUSTERS=$(kubectl get clusters.cluster.x-k8s.io -A -o json | \
                jq -r '.items[] | @base64')

              for cluster in $CLUSTERS; do
                _jq() {
                  echo ${cluster} | base64 -d | jq -r ${1}
                }

                NAME=$(_jq '.metadata.name')
                NAMESPACE=$(_jq '.metadata.namespace')
                SERVER=$(_jq '.spec.controlPlaneEndpoint.host')
                CA_CERT=$(_jq '.spec.controlPlaneRef.caData')

                # Get cluster token
                SA_SECRET=$(kubectl get sa argocd-manager -n kube-system \
                  --context=${NAME} -o jsonpath='{.secrets[0].name}')
                TOKEN=$(kubectl get secret ${SA_SECRET} -n kube-system \
                  --context=${NAME} -o jsonpath='{.data.token}' | base64 -d)

                # Register cluster with ArgoCD
                cat <<EOF | kubectl apply -f -
              apiVersion: v1
              kind: Secret
              metadata:
                name: cluster-${NAME}
                namespace: argocd
                labels:
                  argocd.argoproj.io/secret-type: cluster
                  environment: ${ENVIRONMENT:-production}
                  region: ${REGION:-us-east-1}
                  discovered: "true"
              type: Opaque
              stringData:
                name: ${NAME}
                server: https://${SERVER}
                config: |
                  {
                    "bearerToken": "${TOKEN}",
                    "tlsClientConfig": {
                      "insecure": false,
                      "caData": "${CA_CERT}"
                    }
                  }
              EOF

                echo "Registered cluster: ${NAME}"
              done
          restartPolicy: OnFailure
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: cluster-discovery
  namespace: argocd
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cluster-discovery
rules:
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get", "list", "create", "update", "patch"]
- apiGroups: ["cluster.x-k8s.io"]
  resources: ["clusters"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: cluster-discovery
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-discovery
subjects:
- kind: ServiceAccount
  name: cluster-discovery
  namespace: argocd

Multi-Region Deployment

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: global-application
  namespace: argocd
spec:
  generators:
  - matrix:
      generators:
      # Regions
      - list:
          elements:
          - region: us-east-1
            cluster: https://prod-us-east-1.k8s.example.com
            replicas: "5"
            storageClass: gp3

          - region: us-west-2
            cluster: https://prod-us-west-2.k8s.example.com
            replicas: "3"
            storageClass: gp3

          - region: eu-west-1
            cluster: https://prod-eu-west-1.k8s.example.com
            replicas: "5"
            storageClass: gp3

          - region: ap-southeast-1
            cluster: https://prod-ap-southeast-1.k8s.example.com
            replicas: "3"
            storageClass: gp3

      # Availability zones per region
      - list:
          elements:
          - zone: a
            priority: "high"
          - zone: b
            priority: "high"
          - zone: c
            priority: "medium"

  template:
    metadata:
      name: 'myapp-{{region}}-{{zone}}'
      labels:
        region: '{{region}}'
        zone: '{{zone}}'
        priority: '{{priority}}'
    spec:
      project: global-apps
      source:
        repoURL: https://github.com/example/myapp
        targetRevision: HEAD
        path: deploy/k8s
        helm:
          parameters:
          - name: replicaCount
            value: '{{replicas}}'
          - name: affinity.zone
            value: '{{region}}{{zone}}'
          - name: storage.class
            value: '{{storageClass}}'
          - name: tolerations
            value: |
              - key: "zone"
                operator: "Equal"
                value: "{{zone}}"
                effect: "NoSchedule"
      destination:
        server: '{{cluster}}'
        namespace: myapp
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
        - CreateNamespace=true
        - RespectIgnoreDifferences=true
      ignoreDifferences:
      # Allow regional differences
      - group: apps
        kind: Deployment
        jsonPointers:
        - /spec/replicas

Multi-Tenancy Implementation

Tenant Isolation Pattern

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: tenant-workloads
  namespace: argocd
spec:
  generators:
  - matrix:
      generators:
      # Tenant definitions
      - git:
          repoURL: https://github.com/example/tenant-config
          revision: HEAD
          files:
          - path: "tenants/*/tenant.yaml"

      # Application definitions per tenant
      - git:
          repoURL: https://github.com/example/tenant-config
          revision: HEAD
          files:
          - path: "tenants/{{tenant}}/apps/*/app.yaml"

  template:
    metadata:
      name: '{{tenant}}-{{app}}'
      labels:
        tenant: '{{tenant}}'
        app: '{{app}}'
      finalizers:
      - resources-finalizer.argocd.argoproj.io
    spec:
      project: 'tenant-{{tenant}}'
      source:
        repoURL: '{{repoUrl}}'
        targetRevision: '{{branch}}'
        path: '{{path}}'
        helm:
          parameters:
          - name: tenant
            value: '{{tenant}}'
          - name: app
            value: '{{app}}'
          # Tenant resource limits
          - name: resources.limits.cpu
            value: '{{quota.cpu}}'
          - name: resources.limits.memory
            value: '{{quota.memory}}'
          # Tenant network policies
          - name: networkPolicy.enabled
            value: 'true'
          - name: networkPolicy.ingress
            value: '{{networkPolicy.ingress}}'
      destination:
        server: '{{cluster}}'
        namespace: '{{tenant}}-{{app}}'
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
        - CreateNamespace=true
        - ApplyOutOfSyncOnly=true
      # Sync waves for proper ordering
      syncPolicy:
        syncOptions:
        - CreateNamespace=true
---
# Example tenant.yaml
apiVersion: v1
kind: Config
tenant: acme
quota:
  cpu: "4000m"
  memory: "8Gi"
  storage: "100Gi"
networkPolicy:
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          tenant: acme
cluster: https://kubernetes.default.svc
---
# Example app.yaml
apiVersion: v1
kind: Config
app: web-portal
repoUrl: https://github.com/acme/web-portal
branch: main
path: deploy/k8s

AppProject Per Tenant

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: tenant-projects
  namespace: argocd
spec:
  generators:
  - git:
      repoURL: https://github.com/example/tenant-config
      revision: HEAD
      files:
      - path: "tenants/*/tenant.yaml"

  template:
    metadata:
      name: 'project-{{tenant}}'
    spec:
      # Generate AppProject for tenant
      template:
        apiVersion: argoproj.io/v1alpha1
        kind: AppProject
        metadata:
          name: 'tenant-{{tenant}}'
          namespace: argocd
        spec:
          description: 'Project for tenant {{tenant}}'

          sourceRepos:
          - '{{sourceRepos}}'

          destinations:
          - namespace: '{{tenant}}-*'
            server: '{{cluster}}'

          clusterResourceWhitelist:
          - group: ''
            kind: Namespace

          namespaceResourceWhitelist:
          - group: '*'
            kind: '*'

          # Tenant-specific quotas
          resourceQuotas:
          - hard:
              requests.cpu: '{{quota.cpu}}'
              requests.memory: '{{quota.memory}}'
              persistentvolumeclaims: '{{quota.pvcs}}'

          # Role-based access
          roles:
          - name: developer
            description: Developer role for tenant {{tenant}}
            policies:
            - p, proj:tenant-{{tenant}}:developer, applications, get, tenant-{{tenant}}/*, allow
            - p, proj:tenant-{{tenant}}:developer, applications, sync, tenant-{{tenant}}/*, allow
            groups:
            - '{{tenant}}-developers'

          - name: admin
            description: Admin role for tenant {{tenant}}
            policies:
            - p, proj:tenant-{{tenant}}:admin, applications, *, tenant-{{tenant}}/*, allow
            - p, proj:tenant-{{tenant}}:admin, repositories, *, *, allow
            groups:
            - '{{tenant}}-admins'

Progressive Sync Strategies

Wave-Based Deployment

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: wave-deployment
  namespace: argocd
spec:
  generators:
  - list:
      elements:
      # Wave 0: Infrastructure
      - name: namespace
        wave: "0"
        path: infrastructure/namespace

      - name: secrets
        wave: "0"
        path: infrastructure/secrets

      # Wave 1: Dependencies
      - name: database
        wave: "1"
        path: dependencies/database

      - name: cache
        wave: "1"
        path: dependencies/cache

      # Wave 2: Application
      - name: backend
        wave: "2"
        path: application/backend

      - name: frontend
        wave: "2"
        path: application/frontend

      # Wave 3: Ingress
      - name: ingress
        wave: "3"
        path: infrastructure/ingress

  template:
    metadata:
      name: '{{name}}'
      annotations:
        argocd.argoproj.io/sync-wave: '{{wave}}'
    spec:
      project: default
      source:
        repoURL: https://github.com/example/myapp
        targetRevision: HEAD
        path: 'deploy/{{path}}'
      destination:
        server: https://kubernetes.default.svc
        namespace: myapp
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
        - CreateNamespace=true
        retry:
          limit: 3
          backoff:
            duration: 5s
            factor: 2
            maxDuration: 1m

Health-Based Progressive Rollout

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: progressive-rollout
  namespace: argocd
spec:
  generators:
  - list:
      elements:
      # Phase 1: 10% traffic
      - phase: "1"
        percentage: "10"
        duration: "5m"

      # Phase 2: 25% traffic
      - phase: "2"
        percentage: "25"
        duration: "10m"

      # Phase 3: 50% traffic
      - phase: "3"
        percentage: "50"
        duration: "15m"

      # Phase 4: 100% traffic
      - phase: "4"
        percentage: "100"
        duration: "0m"

  strategy:
    type: RollingSync
    rollingSync:
      steps:
      - matchExpressions:
        - key: phase
          operator: In
          values: ["1"]
      - matchExpressions:
        - key: phase
          operator: In
          values: ["2"]
        maxUpdate: 25%
      - matchExpressions:
        - key: phase
          operator: In
          values: ["3"]
        maxUpdate: 50%
      - matchExpressions:
        - key: phase
          operator: In
          values: ["4"]

  template:
    metadata:
      name: 'myapp-phase-{{phase}}'
      labels:
        phase: '{{phase}}'
      annotations:
        argocd.argoproj.io/sync-options: "SkipDryRunOnMissingResource=true"
    spec:
      project: default
      source:
        repoURL: https://github.com/example/myapp
        targetRevision: HEAD
        path: deploy/k8s
        helm:
          parameters:
          - name: canary.weight
            value: '{{percentage}}'
          - name: canary.duration
            value: '{{duration}}'
      destination:
        server: https://kubernetes.default.svc
        namespace: myapp
      syncPolicy:
        automated:
          prune: true
          selfHeal: false
        syncOptions:
        - CreateNamespace=true
      # Health assessment
      health:
        timeout: 300
        retries: 3

Monitoring and Observability

Metrics Collection

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-metrics-config
  namespace: argocd
data:
  application.metrics: |
    - name: applicationset_info
      help: Information about ApplicationSet
      type: gauge
      labels:
        name: $.metadata.name
        namespace: $.metadata.namespace
      value: 1

    - name: applicationset_applications_count
      help: Number of applications managed by ApplicationSet
      type: gauge
      labels:
        name: $.metadata.name
      value: $.status.applicationsCount

    - name: applicationset_conditions
      help: ApplicationSet conditions
      type: gauge
      labels:
        name: $.metadata.name
        type: $.status.conditions[*].type
        status: $.status.conditions[*].status
      value: 1
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: argocd-applicationset-controller
  namespace: argocd
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: argocd-applicationset-controller
  endpoints:
  - port: metrics
    interval: 30s
    path: /metrics
---
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: applicationset-alerts
  namespace: argocd
spec:
  groups:
  - name: applicationset
    interval: 30s
    rules:
    - alert: ApplicationSetSyncFailure
      expr: |
        sum by (name, namespace) (
          argocd_app_sync_total{phase="Failed"}
        ) > 0
      for: 5m
      labels:
        severity: critical
      annotations:
        summary: "ApplicationSet {{ $labels.name }} sync failed"
        description: "ApplicationSet {{ $labels.name }} in namespace {{ $labels.namespace }} has failed syncs"

    - alert: ApplicationSetHighReconciliationTime
      expr: |
        histogram_quantile(0.95,
          sum(rate(argocd_applicationset_reconcile_duration_seconds_bucket[5m])) by (le, name)
        ) > 30
      for: 10m
      labels:
        severity: warning
      annotations:
        summary: "ApplicationSet {{ $labels.name }} slow reconciliation"
        description: "95th percentile reconciliation time is {{ $value }} seconds"

Grafana Dashboard

apiVersion: v1
kind: ConfigMap
metadata:
  name: applicationset-dashboard
  namespace: argocd
data:
  dashboard.json: |
    {
      "dashboard": {
        "title": "ArgoCD ApplicationSets",
        "panels": [
          {
            "title": "ApplicationSets Count",
            "targets": [
              {
                "expr": "count(argocd_applicationset_info)"
              }
            ]
          },
          {
            "title": "Applications Per ApplicationSet",
            "targets": [
              {
                "expr": "argocd_applicationset_applications_count"
              }
            ]
          },
          {
            "title": "Reconciliation Duration",
            "targets": [
              {
                "expr": "histogram_quantile(0.95, sum(rate(argocd_applicationset_reconcile_duration_seconds_bucket[5m])) by (le, name))"
              }
            ]
          },
          {
            "title": "Sync Success Rate",
            "targets": [
              {
                "expr": "sum(rate(argocd_app_sync_total{phase=\"Succeeded\"}[5m])) / sum(rate(argocd_app_sync_total[5m]))"
              }
            ]
          },
          {
            "title": "Generator Errors",
            "targets": [
              {
                "expr": "sum(rate(argocd_applicationset_generator_errors_total[5m])) by (generator)"
              }
            ]
          }
        ]
      }
    }

Best Practices

Performance Optimization

  1. Use Appropriate Generators: Choose the most efficient generator for your use case
  2. Limit Generator Frequency: Set appropriate requeueAfterSeconds values
  3. Implement Caching: Use Git repository caching to reduce API calls
  4. Resource Limits: Set appropriate resource limits for ApplicationSet controller
  5. Selective Sync: Use ApplyOutOfSyncOnly to reduce unnecessary syncs

Security Considerations

  1. RBAC Configuration: Implement least-privilege access for ApplicationSets
  2. Secret Management: Use external secret managers for sensitive data
  3. Repository Access: Use read-only tokens for Git repositories
  4. Network Policies: Implement network segmentation for multi-tenancy
  5. Audit Logging: Enable comprehensive audit logging for compliance

Operational Excellence

  1. Monitoring: Implement comprehensive monitoring and alerting
  2. Testing: Test ApplicationSet changes in non-production environments
  3. Documentation: Document generator patterns and deployment strategies
  4. Backup: Regular backup of ApplicationSet configurations
  5. Disaster Recovery: Implement DR procedures for GitOps infrastructure

Conclusion

ArgoCD ApplicationSets provide powerful capabilities for managing applications at enterprise scale. By leveraging advanced generator patterns, progressive delivery strategies, and multi-tenancy implementations, organizations can achieve highly automated, scalable GitOps workflows.

Key takeaways:

  • Use matrix and cluster generators for complex multi-environment scenarios
  • Implement progressive sync strategies for safe production rollouts
  • Leverage multi-tenancy patterns for organizational isolation
  • Monitor ApplicationSet performance and application health
  • Follow security best practices for enterprise deployments

With proper implementation of these patterns, teams can manage thousands of applications across hundreds of clusters while maintaining consistency, security, and operational excellence.