Skip to main content

Overview

With generators, the ApplicationSet controller provides powerful tools to automate the templating and modification of Argo CD Applications. Here are the primary use cases that ApplicationSet was designed to support.

Cluster Add-ons

An initial design focus of the ApplicationSet controller was to enable infrastructure teams to automatically create and manage a large set of Argo CD Applications across many clusters as a single unit.

The Problem

Infrastructure teams are responsible for provisioning cluster add-ons to multiple Kubernetes clusters:
  • Cluster add-ons are operators and controllers like:
    • Prometheus operator for metrics
    • Argo Workflows controller for workflow orchestration
    • Cert-manager for certificate management
    • Ingress controllers
  • Scale challenges:
    • Organizations may manage tens, hundreds, or thousands of clusters
    • New clusters are added/modified/removed regularly
    • Different add-ons need to target different cluster subsets (staging vs production)
  • Permission requirements:
    • Installing add-ons requires cluster-level permissions
    • Development teams (cluster tenants) don’t have these permissions
    • Infrastructure/ops teams need automation to scale

The Solution

Cluster add-on diagram The infrastructure team maintains a Git repository with application manifests for cluster add-ons. ApplicationSet automatically deploys these add-ons to target clusters.

Option 1: List Generator

Manually maintain a list of target clusters:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: prometheus-operator
spec:
  goTemplate: true
  goTemplateOptions: ["missingkey=error"]
  generators:
  - list:
      elements:
      - cluster: engineering-dev
        url: https://dev.example.com
      - cluster: engineering-prod
        url: https://prod.example.com
  template:
    metadata:
      name: '{{.cluster}}-prometheus'
    spec:
      project: cluster-addons
      source:
        repoURL: https://github.com/infra-team/cluster-addons.git
        targetRevision: HEAD
        path: prometheus-operator
      destination:
        server: '{{.url}}'
        namespace: monitoring
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
        - CreateNamespace=true
Adding/removing clusters requires manually updating the ApplicationSet’s list elements.
Automatically detect all clusters defined in Argo CD:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: prometheus-operator
spec:
  goTemplate: true
  goTemplateOptions: ["missingkey=error"]
  generators:
  - clusters:
      selector:
        matchLabels:
          environment: production
  template:
    metadata:
      name: '{{.name}}-prometheus'
    spec:
      project: cluster-addons
      source:
        repoURL: https://github.com/infra-team/cluster-addons.git
        targetRevision: HEAD
        path: prometheus-operator
      destination:
        server: '{{.server}}'
        namespace: monitoring
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
        - CreateNamespace=true
Adding/removing a cluster from Argo CD automatically creates/removes the corresponding Application.

Option 3: Git Generator

Most flexible approach using Git as the source of truth: Using files field:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: cluster-addons
spec:
  goTemplate: true
  goTemplateOptions: ["missingkey=error"]
  generators:
  - git:
      repoURL: https://github.com/infra-team/cluster-config.git
      revision: HEAD
      files:
      - path: "clusters/**/config.json"
  template:
    metadata:
      name: '{{.cluster.name}}-prometheus'
    spec:
      project: cluster-addons
      source:
        repoURL: https://github.com/infra-team/cluster-addons.git
        targetRevision: HEAD
        path: prometheus-operator
      destination:
        server: '{{.cluster.address}}'
        namespace: monitoring
clusters/prod-us-east/config.json:
{
  "cluster": {
    "name": "prod-us-east",
    "address": "https://prod.us-east.example.com"
  }
}
Using directories field:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: cluster-addons
spec:
  goTemplate: true
  goTemplateOptions: ["missingkey=error"]
  generators:
  - git:
      repoURL: https://github.com/infra-team/cluster-config.git
      revision: HEAD
      directories:
      - path: clusters/*
  template:
    metadata:
      name: '{{.path.basename}}-prometheus'
    spec:
      project: cluster-addons
      source:
        repoURL: https://github.com/infra-team/cluster-addons.git
        targetRevision: HEAD
        path: prometheus-operator
      destination:
        name: '{{.path.basename}}'
        namespace: monitoring

Monorepos

In the monorepo use case, cluster administrators manage the entire state of a Kubernetes cluster from a single Git repository.

The Problem

  • Single Git repository contains manifests for multiple applications
  • Changes to the repository should automatically deploy to the cluster
  • Need to avoid creating individual Application resources for each service manually
  • Want GitOps workflow where merging to main triggers deployment

The Solution

Monorepo diagram ApplicationSet automatically discovers applications in the monorepo and creates Application resources for each.

Git Directory Approach

Repository structure:
cluster-config/
├── argo-workflows/
│   ├── deployment.yaml
│   └── service.yaml
├── prometheus-operator/
│   └── values.yaml
├── cert-manager/
│   └── kustomization.yaml
└── backend-services/
    ├── api/
    │   └── deployment.yaml
    └── worker/
        └── deployment.yaml
ApplicationSet:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: cluster-apps
spec:
  goTemplate: true
  goTemplateOptions: ["missingkey=error"]
  generators:
  - git:
      repoURL: https://github.com/org/cluster-config.git
      revision: HEAD
      directories:
      - path: "*"
      - path: "backend-services/*"
  template:
    metadata:
      name: '{{.path.basename}}'
    spec:
      project: default
      source:
        repoURL: https://github.com/org/cluster-config.git
        targetRevision: HEAD
        path: '{{.path.path}}'
      destination:
        server: https://kubernetes.default.svc
        namespace: '{{.path.basename}}'
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
        - CreateNamespace=true
This creates Applications for argo-workflows, prometheus-operator, cert-manager, api, and worker.

Git Files Approach

Repository structure:
apps/
├── argo-workflows/
│   ├── config.json
│   └── manifests/
├── prometheus/
│   ├── config.json
│   └── manifests/
└── cert-manager/
    ├── config.json
    └── manifests/
apps/argo-workflows/config.json:
{
  "appName": "argo-workflows",
  "namespace": "workflows",
  "syncPolicy": "automated"
}
ApplicationSet:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: cluster-apps
spec:
  goTemplate: true
  goTemplateOptions: ["missingkey=error"]
  generators:
  - git:
      repoURL: https://github.com/org/cluster-config.git
      revision: HEAD
      files:
      - path: "apps/**/config.json"
  template:
    metadata:
      name: '{{.appName}}'
    spec:
      project: default
      source:
        repoURL: https://github.com/org/cluster-config.git
        targetRevision: HEAD
        path: '{{.path.path}}/manifests'
      destination:
        server: https://kubernetes.default.svc
        namespace: '{{.namespace}}'
      syncPolicy:
        automated:
          prune: true
          selfHeal: true

Self-Service Multi-Tenant Deployments

The self-service use case allows developers on multi-tenant Kubernetes clusters to deploy applications without requiring cluster administrator intervention.

The Problem

Developers want to:
  • Deploy multiple applications to a single cluster
  • Deploy to multiple clusters
  • Do so without involving cluster administrators for each deployment
Traditional app-of-apps approach issues:
  • Developers define Argo CD Application resources in Git
  • Admins review/accept via merge requests
  • Security risk: Application spec contains sensitive fields:
    • project - controls permissions
    • cluster - target cluster
    • namespace - target namespace
  • Inadvertent merge could grant excessive permissions

The Solution

ApplicationSet allows admins to restrict sensitive fields while letting developers customize safe fields.

Safe Self-Service Pattern

Repository structure (developer-controlled):
apps/
├── team-a/
│   ├── frontend/
│   │   └── config.json
│   └── backend/
│       └── config.json
└── team-b/
    └── api/
        └── config.json
apps/team-a/frontend/config.json:
{
  "app": {
    "source": "https://github.com/team-a/frontend",
    "revision": "v1.2.3",
    "path": "k8s"
  }
}
ApplicationSet (admin-controlled):
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: team-apps
spec:
  goTemplate: true
  goTemplateOptions: ["missingkey=error"]
  generators:
  - git:
      repoURL: https://github.com/org/app-configs.git
      revision: main
      files:
      - path: "apps/**/config.json"
  template:
    metadata:
      name: '{{.path.segments 1}}-{{.path.basename}}' # team-a-frontend
    spec:
      project: '{{.path.segments 1}}' # RESTRICTED: project based on team folder
      source:
        # CUSTOMIZABLE: developers control these via config.json
        repoURL: '{{.app.source}}'
        targetRevision: '{{.app.revision}}'
        path: '{{.app.path}}'
      destination:
        name: production-cluster # RESTRICTED: admins control target cluster
        namespace: '{{.path.segments 1}}' # RESTRICTED: namespace based on team folder
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
Security benefits:
  • Developers can customize: source repo, revision, path
  • Admins control: target cluster, namespace, project
  • Developers cannot escalate privileges
  • Git commits only contain config.json, not full Application specs

Multi-Cluster Self-Service

Extend to multiple clusters using Matrix generator:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: team-apps-multi-cluster
spec:
  goTemplate: true
  goTemplateOptions: ["missingkey=error"]
  generators:
  - matrix:
      generators:
      # Developer-controlled app configs
      - git:
          repoURL: https://github.com/org/app-configs.git
          revision: main
          files:
          - path: "apps/**/config.json"
      # Admin-controlled cluster list
      - list:
          elements:
          - cluster: staging
            url: https://staging.example.com
          - cluster: production
            url: https://production.example.com
  template:
    metadata:
      name: '{{.path.segments 1}}-{{.path.basename}}-{{.cluster}}'
    spec:
      project: '{{.path.segments 1}}'
      source:
        repoURL: '{{.app.source}}'
        targetRevision: '{{.app.revision}}'
        path: '{{.app.path}}'
      destination:
        server: '{{.url}}'
        namespace: '{{.path.segments 1}}'
This creates Applications for each app config on each cluster: team-a-frontend-staging, team-a-frontend-production, etc.

Multi-Cluster Deployment Patterns

Deploy Different Apps to Different Clusters

Use cluster labels to control which apps deploy where:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: selective-deployment
spec:
  goTemplate: true
  goTemplateOptions: ["missingkey=error"]
  generators:
  - matrix:
      generators:
      - git:
          repoURL: https://github.com/org/apps.git
          revision: HEAD
          files:
          - path: "apps/**/config.json"
      - clusters:
          selector:
            matchLabels:
              argocd.argoproj.io/secret-type: cluster
              app-tier: '{{.tier}}' # Matches tier from config.json
  template:
    metadata:
      name: '{{.name}}-{{.appName}}'
    spec:
      project: default
      source:
        repoURL: '{{.source}}'
        targetRevision: HEAD
        path: '{{.path}}'
      destination:
        server: '{{.server}}'
        namespace: '{{.namespace}}'

Progressive Rollout Across Clusters

Deploy to dev, then staging, then production:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: progressive-rollout
spec:
  goTemplate: true
  goTemplateOptions: ["missingkey=error"]
  generators:
  - clusters:
      selector:
        matchLabels:
          environment: dev
      values:
        revision: main
  - clusters:
      selector:
        matchLabels:
          environment: staging
      values:
        revision: release-candidate
  - clusters:
      selector:
        matchLabels:
          environment: production
      values:
        revision: stable
  template:
    metadata:
      name: '{{.name}}-myapp'
    spec:
      project: default
      source:
        repoURL: https://github.com/org/myapp.git
        targetRevision: '{{.values.revision}}'
        path: k8s
      destination:
        server: '{{.server}}'
        namespace: myapp

Best Practices

Begin with List or Cluster generators before moving to more complex Git-based generators. This helps you understand the fundamentals.
Enable automated sync with prune and selfHeal for true GitOps:
syncPolicy:
  automated:
    prune: true
    selfHeal: true
Use cluster labels extensively for flexible targeting:
  • environment: production/staging/dev
  • region: us-east/us-west/eu
  • tier: frontend/backend/data
Always hard-code project, destination.server, and destination.namespace when allowing developer customization.
When you need to deploy multiple apps to multiple clusters, Matrix generator eliminates duplication.

Next Steps

Generators

Dive deep into each generator type and their configuration options

Security

Understand security implications and access control for ApplicationSets

Build docs developers (and LLMs) love