Declaring an ArgoCD Application by hand for every service works fine with ten teams. With a hundred, every new microservice becomes a manual ticket, every convention change (adding a label, an annotation) becomes an edit to replicate everywhere, and nobody notices an Application got forgotten until that service simply stops deploying. The problem isn’t ArgoCD: it’s the missing generation step where manual repetition itself becomes the source of error.
ApplicationSet: generate instead of declare
An ApplicationSet doesn’t describe one deployment, it describes how to generate several from a source of truth: an explicit list, the contents of a Git folder, or the inventory of clusters ArgoCD already knows about.
# One Application per folder detected under apps/, no manual declaration
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: team-services
spec:
generators:
- git:
repoURL: https://github.com/acme/gitops-config.git
revision: main
directories:
- path: apps/*
template:
metadata:
name: "{{path.basename}}"
spec:
source:
repoURL: https://github.com/acme/gitops-config.git
path: "{{path}}"
destination:
namespace: "{{path.basename}}"
Adding a service becomes adding a folder to the config repo: the matching Application appears automatically, no PR against the ArgoCD definition itself. A convention change happens once in the template, and propagates to every generated Application.
The real problem this shifts: isolation between teams
Automatically generating Applications solves nothing if any team, through its own Git repository, can define a deployment that reaches another team’s namespace. Control no longer happens at the individual deployment level but at the scope each team is allowed to touch, which is exactly what an AppProject is for:
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: team-payments
spec:
sourceRepos:
- https://github.com/acme/payments-gitops.git
destinations:
- namespace: "payments-*"
server: https://kubernetes.default.svc
clusterResourceWhitelist: [] # no cluster-scoped resource allowed
namespaceResourceWhitelist:
- group: "apps"
kind: "Deployment"
- group: ""
kind: "Service"
An AppProject restricts three axes at once: which Git repos can serve as a source, which namespaces can be targeted, and which resource types can be created. Without this constraint, a loosely scoped ApplicationSet lets any team deploy a cluster-scoped resource (a ClusterRole, say) from its own repo, which makes multi-tenancy purely theoretical.
The trap of an over-permissive generator
A Git generator based on a broad pattern (apps/*) trusts repository structure to delimit teams. If two teams share the same config repository without strict subfolder separation and a matching AppProject, any PR can create an Application targeting another team’s namespace. Apparent logical separation (different folders) only amounts to real isolation once it’s enforced by a technical control (AppProject + ArgoCD RBAC), never by convention alone.
App-of-apps: one more layer, one more risk
The app-of-apps pattern (a root ApplicationSet generating other ApplicationSets) pushes automation one step further, useful for platforms that manage their own GitOps tooling. The risk grows with depth: an error in the root generator propagates to every level beneath it, potentially several hundred Applications, before a reconciliation loop gets a chance to correct it. Every added layer of indirection needs to justify itself with a real reduction in operational load, not architectural elegance. This same declarative automation directly extends what the article on declarative deployment with ArgoCD describes, scaled up from one application to dozens of teams.
Takeaway
An ApplicationSet solves a manual-repetition problem, not a security problem: automatically generating Applications without a matching AppProject opens a purely decorative multi-tenant isolation. The technical constraint (allowed repos, targetable namespaces, resource types) has to exist at the same level as the automatic generation, otherwise automation propagates mistakes exactly as fast as it propagates correct configuration.