Skip to content

ArgoCD ApplicationSet

KSail’s ArgoCD bootstrap creates a single Application CR that syncs manifests from a local OCI registry. ApplicationSet extends this by generating multiple Applications from a single template — useful for multi-environment deployments, multi-tenant clusters, and directory-based project structures.

[!NOTE] The ApplicationSet controller ships with ArgoCD and is automatically available in every KSail ArgoCD cluster — no extra installation is needed.

Initialize a KSail project with ArgoCD as the GitOps engine:

Terminal window
ksail project init --gitops-engine ArgoCD
ksail cluster create

This scaffolds a ksail.yaml with spec.cluster.gitOpsEngine: ArgoCD and creates the cluster with:

  • ArgoCD installed via Helm
  • A bootstrap Application CR named ksail pointing to the local OCI registry
  • Automated sync, pruning, and self-healing enabled

During ksail cluster create, KSail creates a single Application that syncs everything in the k8s/ source directory:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: ksail
namespace: argocd
spec:
project: default
source:
repoURL: oci://<registry>/<project>
targetRevision: dev
path: "."
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

You can add ApplicationSet manifests to your source directory to generate additional Applications alongside the bootstrap one.

The directory generator creates one Application per subdirectory in a given path. This is useful for managing multiple independent workloads from a monorepo layout.

my-app/
├── ksail.yaml # gitOpsEngine: ArgoCD
├── k8s/
│ ├── kustomization.yaml
│ ├── applicationset.yaml # ApplicationSet manifest
│ └── apps/
│ ├── frontend/
│ │ ├── deployment.yaml
│ │ └── service.yaml
│ ├── backend/
│ │ ├── deployment.yaml
│ │ └── service.yaml
│ └── monitoring/
│ ├── deployment.yaml
│ └── service.yaml

Create k8s/applicationset.yaml:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: apps
namespace: argocd
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- git:
repoURL: oci://<registry>/<project>
revision: dev
directories:
- path: apps/*
template:
metadata:
name: "{{.path.basename}}"
spec:
project: default
source:
repoURL: oci://<registry>/<project>
targetRevision: dev
path: "{{.path.path}}"
destination:
server: https://kubernetes.default.svc
namespace: "{{.path.basename}}"
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

This generates three Applications (frontend, backend, monitoring), each deployed to its own namespace.

[!TIP] When using the local OCI registry, match the repoURL and targetRevision to what KSail configures in the bootstrap Application. You can inspect the bootstrap Application with:

Terminal window
ksail workload get application ksail -n argocd -o yaml

Add the ApplicationSet to your kustomization and push:

Terminal window
ksail workload push
ksail workload reconcile

The git generator can also use config.json files in each directory to pass parameters to the template. This enables per-application configuration without duplicating manifests.

my-app/
├── ksail.yaml
├── k8s/
│ ├── kustomization.yaml
│ ├── applicationset.yaml
│ └── envs/
│ ├── dev/
│ │ └── config.json
│ ├── staging/
│ │ └── config.json
│ └── production/
│ └── config.json

Each config.json contains environment-specific parameters:

{
"env": "dev",
"namespace": "app-dev",
"replicas": 1
}
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: multi-env
namespace: argocd
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- git:
repoURL: oci://<registry>/<project>
revision: dev
files:
- path: envs/*/config.json
template:
metadata:
name: "app-{{.env}}"
spec:
project: default
source:
repoURL: oci://<registry>/<project>
targetRevision: dev
path: "envs/{{.env}}"
destination:
server: https://kubernetes.default.svc
namespace: "{{.namespace}}"
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

KSail’s tenant management generates ArgoCD AppProject and Application resources per tenant. You can replace per-tenant Applications with a single ApplicationSet that auto-discovers tenant directories:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: tenants
namespace: argocd
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- git:
repoURL: oci://<registry>/<project>
revision: dev
directories:
- path: tenants/*
template:
metadata:
name: "tenant-{{.path.basename}}"
spec:
project: "{{.path.basename}}"
source:
repoURL: oci://<registry>/<project>
targetRevision: dev
path: "{{.path.path}}"
destination:
server: https://kubernetes.default.svc
namespace: "{{.path.basename}}"
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

[!IMPORTANT] Each tenant’s AppProject must exist before the ApplicationSet creates the corresponding Application. Use ksail tenant create to scaffold the AppProject first, or include AppProject manifests in your source directory.

When SOPS is enabled (or auto-detected) and an Age key is available, KSail automatically:

  1. Creates a sops-age Secret in the argocd namespace containing your Age private key
  2. Installs a Config Management Plugin (CMP) sidecar on the ArgoCD repo-server that decrypts SOPS-encrypted manifests before rendering

Encrypted secrets in your source directory are decrypted transparently during sync — no manual plugin setup required. For key resolution details and all spec.cluster.sops options, see Secret Management.

SOPS is auto-detected. If SOPS_AGE_KEY is set or an Age key file exists, KSail enables SOPS automatically. To configure explicitly:

spec:
cluster:
gitOpsEngine: ArgoCD
sops:
env:
var: SOPS_AGE_KEY # env var containing your Age private key
# enabled: true # require SOPS key — error if none found

[!NOTE] sops.ageKeyEnvVar is deprecated — use sops.env.var instead. For all spec.cluster.sops options, see Declarative Configuration.

Terminal window
ksail workload cipher encrypt k8s/apps/backend/secret.yaml

When the ApplicationSet syncs the backend Application, the CMP sidecar detects the sops: metadata block and decrypts the file before applying it to the cluster.

  • Use ksail workload push and ksail workload reconcile to deploy ApplicationSet manifests — they flow through the same OCI push pipeline as regular manifests.

  • Combine with multi-environment workflows by using separate ksail.<env>.yaml configs and ApplicationSet generators. See Multi-Environment Workflows for the --config pattern.