App-of-apps gets you to 30 services. Past that, you want ApplicationSets — Argo’s generator-based way to manage many apps from one declaration. This post is the production playbook.

The shape

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata: { name: services, namespace: argocd }
spec:
  generators:
    - git:
        repoURL: https://github.com/example/gitops
        revision: HEAD
        directories:
          - path: services/*
  template:
    metadata:
      name: '{{.path.basename}}'
    spec:
      project: default
      source:
        repoURL: https://github.com/example/gitops
        targetRevision: HEAD
        path: '{{.path}}'
      destination:
        server: https://kubernetes.default.svc
        namespace: '{{.path.basename}}'
      syncPolicy:
        automated: { prune: true, selfHeal: true }

This generates one Application per directory under services/. Add a directory; it auto-deploys.

Generators

Git directories

The above. Each subdirectory of services/ becomes one app.

Git files

generators:
  - git:
      repoURL: ...
      files:
        - path: "envs/**/config.yaml"

Each config.yaml → one app. The file’s contents become parameters.

Cluster

generators:
  - clusters:
      selector:
        matchLabels: { env: prod }

One app per registered cluster matching the selector. Useful for fleet-wide deployments.

List

generators:
  - list:
      elements:
        - { name: api, replicas: "3" }
        - { name: worker, replicas: "10" }

Hardcoded list. Simple cases.

Matrix

generators:
  - matrix:
      generators:
        - clusters: { selector: { matchLabels: { env: prod } } }
        - git: { directories: [{ path: services/* }] }

Cartesian product. Every service × every prod cluster. Manage 5 services × 3 clusters = 15 deployments from one declaration.

Multi-cluster patterns

For a fleet of services across many clusters:

spec:
  generators:
    - matrix:
        generators:
          - clusters:
              selector:
                matchLabels: { env: prod }
          - git:
              directories: [{ path: services/* }]
  template:
    metadata:
      name: '{{.name}}-{{.path.basename}}'
    spec:
      destination:
        server: '{{.server}}'
        namespace: '{{.path.basename}}'
      source:
        path: '{{.path}}/overlays/{{.metadata.labels.env}}'

Each cluster + each service combo → one Application. The path includes an overlay matching the cluster’s env label.

For GitOps with Argo CD the broader patterns.

Per-environment configs

gitops/
├── services/
│   ├── api/
│   │   ├── base/
│   │   ├── overlays/dev/
│   │   ├── overlays/staging/
│   │   └── overlays/prod/
│   └── worker/
│       └── ...

ApplicationSet renders per (service × env). Kustomize handles env differences.

Gotchas

Sync waves for ApplicationSet itself

When you change the ApplicationSet, it can recreate apps. Use preserveResourcesOnDeletion: true to avoid disasters during edits.

Resource sprawl

A bad generator produces N apps. With matrix generators, accidents balloon (service × cluster). Use applicationSetTerminalErrors to cap.

Auth across clusters

Each cluster needs Argo CD credentials. Register via argocd cluster add.

Project quotas

Bound projects to prevent ApplicationSet from spawning into namespaces you don’t intend:

apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata: { name: services }
spec:
  destinations:
    - server: '*'
      namespace: '*'
  sourceRepos: ["https://github.com/example/gitops"]

Common patterns

Onboard a new service

Create services/new-svc/. Add kustomization.yaml and overlays. ApplicationSet auto-generates. No control-plane edits.

Onboard a new cluster

Register cluster with the right labels. ApplicationSet picks it up; deploys all matching services.

Roll a config change

Edit the overlay; PR; merge. Argo CD syncs across all matching apps.

What I’d ship today

For a 2026 GitOps fleet:

  1. Kustomize + ApplicationSet matrix generator for service × env.
  2. Per-cluster registration with env labels.
  3. Auto-sync with prune + selfHeal for safety.
  4. Project quotas for blast-radius bounds.
  5. Per-app Slack notifications on failure.

Read this next

If you want a working ApplicationSet repo with multi-cluster matrix, it’s at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .