Skip to content

Tenant Management

KSail can generate multi-tenancy manifests for platform teams that need to onboard workload teams onto a shared cluster. Each tenant gets namespace isolation, RBAC, and optional GitOps sync resources.

TypeRBACGitOps sync resourcesRequires GitOps engine
fluxOCIRepository / GitRepository + KustomizationFlux
argocdAppProject + ApplicationArgoCD
kubectlNoneNo

The --type flag defaults to auto-detect from ksail.yaml gitOpsEngine.

Terminal window
# Auto-detect type (reads gitOpsEngine from ksail.yaml)
ksail tenant create my-team
# Explicit type
ksail tenant create my-team --type flux
# Custom namespace(s) — default is the tenant name
ksail tenant create my-team --namespace my-team --namespace my-team-staging
# Custom ClusterRole — default is 'edit'
ksail tenant create my-team --cluster-role view
# Output to a specific directory
ksail tenant create my-team --output platform/tenants/

The command writes manifests to <output>/<tenant-name>/:

platform/tenants/my-team/
├── kustomization.yaml
├── namespace.yaml
├── serviceaccount.yaml
├── rolebinding.yaml
└── (flux or argocd sync resources if applicable)

Add --register to automatically append the tenant to the nearest kustomization.yaml:

Terminal window
ksail tenant create my-team --register

Use --delivery pr to deliver platform changes as a pull request against the platform repo:

Terminal window
ksail tenant create my-team --register --delivery pr \
--git-provider github

The platform repo is auto-detected from the local git remote origin. Override with --platform-repo:

Terminal window
ksail tenant create my-team --register --delivery pr \
--git-provider github \
--platform-repo my-org/platform-repo

Set the PR target branch with --target-branch (defaults to the repo’s default branch):

Terminal window
ksail tenant create my-team --register --delivery pr \
--git-provider github \
--target-branch develop

For Flux tenants, specify the sync source type and registry or Git repo:

Terminal window
# OCI source (default)
ksail tenant create my-team --type flux \
--sync-source oci \
--registry oci://ghcr.io \
--tenant-repo my-org/my-team
# OCI source with a path suffix (avoids tag collisions when Docker images and
# Kubernetes manifests are published to the same registry path)
ksail tenant create my-team --type flux \
--sync-source oci \
--registry oci://ghcr.io \
--tenant-repo my-org/my-team \
--oci-path manifests
# Git source — also scaffolds a tenant Git repo when --git-provider github is set
ksail tenant create my-team --type flux \
--sync-source git \
--git-provider github \
--tenant-repo my-org/my-team-infra

KSail resolves the GitHub token automatically using the following fallback chain:

  1. --git-token flag (highest priority)
  2. GH_TOKEN or GITHUB_TOKEN environment variable
  3. GitHub CLI config (e.g., from gh auth login)

If no token can be resolved through any of these sources, repo scaffolding is skipped with a warning and the command succeeds. In CI, ensure a non-empty token is available via --git-token, GH_TOKEN, or GITHUB_TOKEN if you want scaffolding to run. Only ksail tenant delete --delete-repo fails when no token can be resolved.

Pass --git-token explicitly only when you need to override the auto-detected token:

Terminal window
ksail tenant create my-team --type flux \
--git-provider github \
--tenant-repo my-org/my-team-infra \
--git-token "${MY_BOT_TOKEN}"

ArgoCD tenants generate RBAC isolation manifests, an AppProject, and an Application:

Terminal window
ksail tenant create my-team --type argocd \
--git-provider github \
--tenant-repo my-org/my-team-infra
platform/tenants/my-team/
├── kustomization.yaml
├── namespace.yaml
├── serviceaccount.yaml
├── rolebinding.yaml
├── project.yaml
└── app.yaml

Add --register to also merge the tenant RBAC policy into the shared argocd-rbac-cm ConfigMap:

Terminal window
ksail tenant create my-team --type argocd \
--git-provider github \
--tenant-repo my-org/my-team-infra \
--register

KSail scans the kustomization directory for an argocd-rbac-cm ConfigMap (by content, not filename). If found, it merges the tenant policy. If not found, it creates argocd-rbac-cm.yaml in the same directory and registers it in kustomization.yaml.

kubectl tenants generate RBAC-only platform manifests (no GitOps sync resources). When --git-provider github and --tenant-repo are provided, KSail also scaffolds a tenant repository without GitOps-specific resources — provided a GitHub token can be resolved (see the token resolution fallback chain above). If no token is available, the command still succeeds and only the local platform manifests are generated:

Terminal window
ksail tenant create my-team --type kubectl
# With repo scaffolding
ksail tenant create my-team --type kubectl \
--git-provider github \
--tenant-repo my-org/my-team-manifests

The scaffolded repo contains a plain kustomize setup for manual workflows:

/
├── README.md
└── k8s/
└── kustomization.yaml

The tenant applies their manifests with kubectl apply -k k8s/.

By default KSail generates a minimal tenant (namespace, ServiceAccount, RoleBinding, and any GitOps sync resources). For shared production clusters you usually want stronger isolation. These hardening resources are opt-in — nothing below is generated unless you ask for it.

Enable the recommended baseline with a single flag:

Terminal window
ksail tenant create my-team --production

--production turns on Pod Security Standards (baseline), a default-deny NetworkPolicy, a ResourceQuota, a LimitRange, a hardened ServiceAccount, and (for Flux tenants) a hardened sync. Any granular flag you set explicitly overrides the umbrella default. The resulting tenant directory adds:

platform/tenants/my-team/
├── kustomization.yaml
├── namespace.yaml # + Pod Security Standards labels
├── serviceaccount.yaml # + automountServiceAccountToken: false
├── rolebinding.yaml
├── networkpolicy.yaml # default-deny + DNS + intra-namespace
├── resourcequota.yaml
└── limitrange.yaml
FlagEffect
--pod-security <restricted|baseline|privileged>Adds pod-security.kubernetes.io/{enforce,audit,warn} labels to each namespace
--with-network-policyDefault-deny NetworkPolicy plus allow rules for DNS and intra-namespace traffic
--with-quota (--quota-cpu, --quota-memory)ResourceQuota capping namespace requests/limits (defaults: 4 CPU, 8Gi)
--with-limit-range (--limit-default-cpu, --limit-default-memory, --limit-request-cpu, --limit-request-memory)LimitRange with default container requests/limits
--disable-token-automountSets automountServiceAccountToken: false on the ServiceAccount
--image-pull-secret <name>Adds an imagePullSecrets entry (repeatable)
--cluster-role <name>Bind multiple ClusterRoles (repeatable; one RoleBinding per role)

For Flux tenants you can additionally harden the sync:

FlagEffect
--flux-waitSets wait: true and timeout (default 5m) on the Flux Kustomization
--flux-timeoutFlux Kustomization timeout; setting it implies --flux-wait
--flux-retry-intervalSets the Kustomization retryInterval
--flux-decryptionAdds a SOPS decryption block referencing the sops-age secret

Portable by default, native CRDs when available

Section titled “Portable by default, native CRDs when available”

The generated manifests are portable upstream Kubernetes resources by default, so they work on any distribution KSail supports. When ksail.yaml opts into a richer control plane, KSail upgrades the output automatically:

  • If spec.cluster.cni is Cilium, --with-network-policy emits a CiliumNetworkPolicy (cilium.io/v2) instead of an upstream NetworkPolicy.
  • Pod Security Standards labels remain the portable baseline even when spec.cluster.policyEngine is Kyverno — Kyverno enforces additional policy server-side but does not replace NetworkPolicies.

The Cilium policy is an intentionally simple starting point (default-deny, plus intra-namespace and kube-dns allow rules) that platform teams can extend.

Terminal window
# Remove manifests and unregister from kustomization.yaml (default)
ksail tenant delete my-team
# Also delete the tenant Git repository (token resolved automatically;
# requires a valid token from the fallback chain above)
ksail tenant delete my-team --delete-repo \
--git-provider github \
--tenant-repo my-org/my-team-infra
# Keep kustomization.yaml entry
ksail tenant delete my-team --unregister=false

When deleting an ArgoCD tenant, KSail automatically removes the tenant’s policy entries from the argocd-rbac-cm ConfigMap. It scans YAML files in the output directory for a ConfigMap named argocd-rbac-cm (content-based detection, not filename-based) and removes lines belonging to the deleted tenant. If no RBAC ConfigMap file is found, the step is silently skipped.

After generating manifests, apply them to the cluster using the workflow that matches how the tenant was created:

Terminal window
# If you created the tenant without --register,
# apply the generated tenant directory directly
ksail workload apply -k platform/tenants/my-team/
# If you created the tenant with --register, or platform/tenants/kustomization.yaml
# already includes the tenant directory, apply the parent directory
ksail workload apply -k platform/tenants/

Or if using GitOps, push and reconcile:

Terminal window
ksail workload push
ksail workload reconcile