Skip to main content
RunBook Academy

Git, CI/CD & GitOpsLXXVII · Argo CDApplicationSet

ApplicationSet and cluster generation — one resource, many Applications

Advanced⏱ ~28 mingitargocd

What you'll learn

  • Describe the ApplicationSet controller and the role it plays relative to the application controller
  • Configure a cluster generator that produces one Application per registered cluster
  • Configure a git directory generator that produces one Application per subdirectory in a repo
  • Use template fields and a matrix generator to parameterise Applications across dimensions

Prerequisites

Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x

Not yet marked complete on this device.

The ApplicationSet controller is a controller that generates Argo CD Applications. It runs alongside the application controller and reads ApplicationSet CRDs; for each CRD it expands a generator (a list, a cluster registry, a git directory, a git file) into a set of Applications, then the application controller reconciles those Applications like any other. The ApplicationSet is the right answer when the same Application shape needs to exist many times - across many clusters, overlays, or tenants.

Why the ApplicationSet exists

The plain Application CRD is one-to-one: one Application, one source, one destination. A team that runs the same workload across 30 clusters needs 30 Applications. A team with 50 overlays in a monorepo needs 50 Applications. A team with both needs 1500 Applications. Writing 1500 Application CRDs by hand is unmaintainable; generating them with a CI pipeline that writes to etcd is fragile (the controller does not own the generated Applications, so a deletion in the generator does not propagate). The ApplicationSet controller owns the generation; a deletion in the generator propagates to the generated Applications automatically.

argocd appset create -f applicationset.yaml
flowchart LR
    AS["ApplicationSet CRD"] --> Gen["Generator"]
    Gen -->|expands| L["List of elements"]
    L --> Tmpl["Template"]
    Tmpl --> A1["Application 1"]
    Tmpl --> A2["Application 2"]
    Tmpl --> A3["Application N"]
    A1 --> AC["Application controller"]
    A2 --> AC
    A3 --> AC

Generators

The generator decides what to expand. ApplicationSet ships with five generator types:

  • List. A static list of elements in the ApplicationSet spec. Each element is a key/value map. Useful for a small number of fixed cases.
  • Cluster. Reads the Argo CD cluster registry and produces one element per registered cluster. The right answer for fleet-wide deployments.
  • Git directory. Reads a Git repo and produces one element per subdirectory under a path. The right answer for monorepo overlays.
  • Git file. Reads a Git repo and produces one element per YAML/JSON file under a path. The right answer for a repo that lists Applications as explicit files.
  • Matrix. Cross-products two generators. The right answer for combinations - clusters * regions, clusters * overlays.
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: payment-api
  namespace: argocd
spec:
  generators:
    - list:
        elements:
          - cluster: prod-us-east
            url: https://prod-us-east.example.com
            region: us-east
          - cluster: prod-eu-west
            url: https://prod-eu-west.example.com
            region: eu-west
  template:
    metadata:
      name: 'payment-api-{cluster}'
    spec:
      project: payments
      source:
        repoURL: https://github.com/example/payment-api
        targetRevision: HEAD
        path: overlays/production
      destination:
        server: '{url}'
        namespace: payments

The fields and what they own:

  • generators — the list of generators. ApplicationSet supports multiple generators; the controller unions the elements.
  • template — the Application template. Fields wrapped in {...} are substituted from the element.

The cluster generator

The cluster generator is the highest-leverage ApplicationSet pattern for a multi-cluster team. It reads the Argo CD cluster registry (the same list of clusters the UI shows) and produces one element per cluster:

generators:
  - cluster:
      selector:
        matchLabels:
          env: production

The selector filters the cluster set. Without a selector, the generator produces one Application for every registered cluster; with a selector, only clusters whose labels match are included. The labels are set when the cluster is registered with Argo CD.

The substitution {name}, {server}, and {metadata.labels.X} are available in the template. A team that runs 30 clusters registers them with the right labels (env, region, tier) and writes one ApplicationSet that covers the fleet.

flowchart TB
    AS["ApplicationSet"] --> CG["Cluster generator"]
    CG --> CR["Cluster registry"]
    CR --> C1["prod-us-east"]
    CR --> C2["prod-eu-west"]
    CR --> C3["staging-us-east"]
    CG --> Sel["Selector: env=production"]
    Sel --> A1["Application: prod-us-east"]
    Sel --> A2["Application: prod-eu-west"]

The git directory generator

The git directory generator reads a Git repository and produces one element per subdirectory under a path. The right answer for a monorepo with one overlay per workload:

generators:
  - git:
      repoURL: https://github.com/example/monorepo
      revision: HEAD
      directories:
        - path: overlays/*

Each subdirectory under overlays/ becomes an element. The directory’s path is available as {path} in the template. A team that adds a new overlay under overlays/new-service/ gets a new Application without touching the ApplicationSet.

The git file generator is the same shape but iterates files instead of directories. The right answer when the repo lists Applications as explicit YAML files:

generators:
  - git:
      repoURL: https://github.com/example/apps
      revision: HEAD
      files:
        - path: apps/*

The matrix generator

The matrix generator cross-products two generators. The right answer for combinations:

generators:
  - matrix:
      generators:
        - cluster:
            selector:
              matchLabels:
                env: production
        - list:
            elements:
              - overlay: payments
              - overlay: identity
              - overlay: data

The matrix produces one element per (cluster, overlay) combination. Three overlays across ten clusters produce 30 Applications. The substitutions {name} (cluster), {server}, and {overlay} are all available in the template.

flowchart LR
    M["Matrix generator"] --> G1["Cluster generator"]
    M --> G2["List generator: overlays"]
    G1 --> C1["prod-us-east"]
    G1 --> C2["prod-eu-west"]
    G2 --> O1["payments"]
    G2 --> O2["identity"]
    G2 --> O3["data"]
    M --> A1["payments-prod-us-east"]
    M --> A2["payments-prod-eu-west"]
    M --> A3["identity-prod-us-east"]
    M --> A4["identity-prod-eu-west"]
    M --> A5["data-prod-us-east"]
    M --> A6["data-prod-eu-west"]

Sync policy and pruning

The ApplicationSet has its own syncPolicy that propagates to the generated Applications. Setting syncPolicy.automated on the ApplicationSet sets automated sync on every generated Application; setting syncPolicy.applications.sync prunes generated Applications whose element no longer exists in the generator.

This is the operational property that makes ApplicationSet useful at scale. A team that removes a cluster from the registry gets the corresponding Applications pruned automatically. A team that removes an overlay from the monorepo gets the corresponding Application pruned. The ApplicationSet is the single source of truth; the generated Applications are derived state.

Under the hood

The ApplicationSet controller is a separate controller that reads ApplicationSet CRDs, expands them via generators, and writes Application CRDs. The application controller does not know which Applications were generated; it sees a uniform set of Application CRDs and reconciles them. The split means the generator logic and the reconcile logic evolve independently.

Production discipline

The rules for production ApplicationSets:

  1. One ApplicationSet per shape. A team that runs the same workload across 30 clusters writes one ApplicationSet with a cluster generator, not 30 Application CRDs.
  2. Selector labels drive generation. Clusters are labelled at registration time; the ApplicationSet’s selector picks them up. The labels are the production contract.
  3. Generated Applications are reviewable. The template shows up in PRs as a single resource; the generated set is visible in the ApplicationSet’s status.applications.
  4. Deleting an ApplicationSet cascades. Production teams convert long-lived Applications to plain CRDs before deleting the ApplicationSet that generated them.

Cross-course references

  • Kubernetes for Production Sysadmins - Part XIII (Operators) covers the controller pattern that the ApplicationSet controller participates in.
  • Git, CI/CD & GitOps for Infrastructure Engineers - Part LXXVI-02 (Monorepo with Overlays) is the repository layout the git directory generator implements; Part LXXVI-05 (Promotion Models) is the operational pattern the matrix generator implements across environments.

Quiz

Knowledge check · 4 questions

  1. Q1. A team operates 30 production clusters and runs the same chart on each. Which ApplicationSet generator is the right answer?

  2. Q2. When the generator no longer produces an element, the ApplicationSet controller automatically deletes the corresponding generated Application.

  3. Q3. Name the generator that reads a Git repository and produces one element per subdirectory, and identify the field in the template that gets substituted with the directory path.

  4. Q4. Diagnose why the new cluster is missing the workload and recommend a fix.

    A team registers a new production cluster with the label env: staging (not env: production). The ApplicationSet's cluster generator has a selector of matchLabels: env: production. After registration, the new cluster is missing the workload that the ApplicationSet deploys to every other production cluster. The team is asked why.

Passing score: 75%. Answers are checked in this browser.