Create and Manage Tenants
A Tenant represents an organization or major division within your company and serves as the top-level organizational unit in PaletteAI.
A default Tenant is automatically created when you install PaletteAI with global.featureFlags.systemDefaultResources: true in your Helm values. For production workloads with multiple organizations or divisions that require separate GPU quota pools or isolated administrative boundaries, we recommend creating separate Tenants. For guidance on when to create multiple Tenants, refer to When to Create Multiple Tenants.
PaletteAI automatically creates a tenant-<name> namespace for internal operations when you create a Tenant. The Settings controller merges Tenant and Project Settings automatically when Tenant.spec.settingsRef (a fully-qualified name-and-namespace reference) is configured. For details on the namespace model, refer to Automatic Resource Creation.
You can create Tenants using the PaletteAI UI or using YAML Kubernetes manifests.
Limitations
- A newly created Tenant does not appear to end users in the PaletteAI UI until at least one Project exists within it and the user's OIDC group is listed in that Project's
roleMapping. Tenant visibility is derived from Project access, not from the Tenant itself. Users with no OIDC group membership in any Project under the Tenant cannot access the Tenant in the UI.
Prerequisites
- UI Workflow
- YAML Workflow
-
Access to the system scope of the PaletteAI console with permission to create Tenants. If the Create Tenant button is not displayed, your role does not include the required permission.
-
Palette API credentials for the Tenant's Settings integration.
-
Install kubectl.
-
Set the
KUBECONFIGenvironment variable to the path of the PaletteAI hub clusterkubeconfigfile.export KUBECONFIG=<kubeconfig-location> -
Ensure you have cluster-admin permissions to manage cluster-scoped Tenant resources.
Create Tenant
Create a Tenant to define organizational boundaries, set GPU quotas, and control access across multiple Projects.
Enablement
- UI Workflow
- YAML Workflow
-
Log in to the PaletteAI console, and switch to the system scope.
-
From the left main menu, select Tenants.
-
Select Create Tenant.
-
On the Basic information page, enter a display name for the Tenant. (Optional) Add a description, labels, and annotations. Select Next.
-
On the Tenant Settings page, configure the Tenant's default Settings and integrations. A Palette integration is required; you can also add Hugging Face and NVIDIA NGC integrations to enable model management features. Select Next.
-
On the Access control page, enter the Tenant Admin Groups. Enter the IdP group name as it appears in your identity provider (for example,
Okta Admins), not the canonicalpai:tenant:…string. PaletteAI persists the tenant-scoped canonical form on the Tenant resource when you create the Tenant. After upgrade, configure user impersonationgroupMapentries so those IdP names map to the canonical strings unless your IdP emits the canonical names directly. Select Next. -
(Optional) On the Resource Limits page, set GPU limits and requests per GPU variant to control tenant-wide resource allocation. Select Next.
-
On the Review page, confirm your configuration, and then select Create Tenant.
-
Create a directory for the Tenant manifests, and then navigate to it.
mkdir <tenant-name>
cd <tenant-name> -
(Optional) Create Tenant-level Settings and Secrets.
Tenant-level Settings live in the auto-created
tenant-<tenant-name>namespace and are referenced byTenant.spec.settingsRef. The Settings controller automatically merges Tenant and Project Settings. If you skip this step, each Project under the Tenant must define its ownsettingsRef. Refer to Settings for more information.Because
Tenant.spec.computeConfigRefrequires aComputeConfigin thetenant-<tenant-name>namespace, create that namespace before you apply the ComputeConfig, Tenant, and optional Settings manifests:kubectl create namespace tenant-<tenant-name>-
Create a Palette Secret in the Tenant namespace. The Secret must be in the same namespace as the Settings resource that references it.
cat << EOF > palette-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: <palette-secret-name>
namespace: tenant-<tenant-name>
type: Opaque
stringData:
palette: |
{
"apiKey": "<your-palette-api-key>",
"defaultProjectID": "<your-default-project-id>",
"hostUrl": "<your-palette-host-url>",
"skipSSLCertificateVerification": false
}
EOF -
Create a Settings resource in the Tenant namespace that references the Palette Secret.
cat << EOF > settings.yaml
apiVersion: spectrocloud.com/v1alpha1
kind: Settings
metadata:
name: <settings-name>
namespace: tenant-<tenant-name>
spec:
integrations:
palette:
name: <palette-secret-name>
namespace: tenant-<tenant-name>
EOF
infoAfter you create the Tenant, you can manage Tenant-level Settings from the Settings Ref tab on the Tenant settings page. Use the tab to create Tenant-level Settings resources, set default Settings references, and review the Settings used across Projects.
-
-
Create a
ComputeConfigresource that defines the default configurations to apply to nodes provisioned to Compute Pools. Use the following command to create a basicComputeConfigmanifest with the required parameters. Replace all placeholders with the necessarymetadataandspec.paletteClusterConfigvalues.The
ComputeConfigmust live in thetenant-<tenant-name>namespace. If you have not created it yet, runkubectl create namespace tenant-<tenant-name>before you apply this manifest and the Tenant (the same namespace is used in step 2 for optional Settings and Secrets).For a complete list of parameters, refer to the
ComputeConfigresource spec.cat << EOF > compute-config.yaml
apiVersion: spectrocloud.com/v1alpha1
kind: ComputeConfig
metadata:
name: <compute-config-name>
namespace: tenant-<tenant-name>
spec:
paletteClusterConfig:
defaults:
controlPlanePool:
nodeCount: <node-count>
EOFComputeConfigMetadataParameter Description Example Value nameThe name of the ComputeConfigresource. TheComputeConfigname must be unique within the Tenant namespace (tenant-<tenant-name>).defaultnamespaceThe namespace under which to create the ComputeConfigresource. For Tenant defaults, create theComputeConfigin the Tenant namespace.tenant-primary-devComputeConfigspec.paletteClusterConfigParameter Description Example Value defaults.controlPlanePool.nodeCountThe number of control plane nodes deployed per Compute Pool. Must be 1,3, or5to maintain quorum.1 -
Create the Tenant manifest.
For a complete list of parameters, refer to the Tenant resource spec.
If you created a Settings resource in step 2, set
spec.settingsRefto point to it so theSETTINGScolumn during validation shows the referenced Settings name. Replace<settings-name>with the value you used in step 2.The
computeConfigReffield is required and references a ComputeConfig in the Tenant namespace. Setspec.computeConfigRef.nameto themetadata.nameyou used in the previous step. PaletteAI uses it to populate defaults for ComputePools and AIWorkloads within the Tenant.cat << EOF > tenant.yaml
apiVersion: spectrocloud.com/v1alpha1
kind: Tenant
metadata:
name: <tenant-name>
spec:
displayName: '<tenant-display-name>'
computeConfigRef:
name: <compute-config-name>
settingsRef:
namespace: tenant-<tenant-name>
name: <settings-name>
tenantRoleMapping:
groups:
- 'pai:tenant:<tenant-name>:role:tnt-adm-<tenant-admin-group-1>'
- 'pai:tenant:<tenant-name>:role:tnt-adm-<tenant-admin-group-2>'
EOFEach entry in
tenantRoleMapping.groupsmust be scoped to this Tenant's ownmetadata.namein the formpai:tenant:<tenant-name>:role:tnt-admorpai:tenant:<tenant-name>:role:tnt-adm-<suffix>; the Tenant validating webhook rejects unscoped or cross-tenant group strings. When creating a Tenant through the PaletteAI UI, enter IdP group names on the Access control step; Canvas generates the canonical strings automatically.For when IdP groups must match these canonical strings exactly versus when you can map human-readable IdP names through
canvas.impersonationProxy.groupMap, refer to Tenant Admin OIDC Groups. -
Apply the applicable manifests. If you skipped step 2, omit the Settings and Secret commands.
# Apply ComputeConfig (always required; apply after the tenant namespace exists)
kubectl apply --filename compute-config.yaml
# Apply Tenant (always required)
kubectl apply --filename tenant.yaml
# Apply Palette Secret (if created in step 2)
kubectl apply --filename palette-secret.yaml
# Apply Settings resource (if created in step 2)
kubectl apply --filename settings.yamlExample Outputtenant.spectrocloud.com/primary-dev created
secret/dev-palette-secret created
settings.spectrocloud.com/dev-settings created
Validate
- UI Workflow
- YAML Workflow
-
From the left main menu, select Tenants.
-
Confirm the new Tenant appears in the list and reports a ready status. The Tenant becomes visible to end users once it contains at least one Project that their OIDC groups can access.
-
Verify the
tenant-<name>namespace was created by the controller.kubectl get namespace tenant-<tenant-name>kubectl get namespace tenant-primary-devExample OutputNAME STATUS AGE
tenant-primary-dev Active 5minfoEach Project under the Tenant must specify its own
settingsRefand Palette integration regardless of whether you create a Tenant-level Settings resource. -
Verify the Tenant exists and is ready. If the Tenant references a Settings resource, the
SETTINGScolumn shows the Settings name.kubectl get tenantsExample OutputNAME DISPLAYNAME READY SETTINGS NAMESPACE PROJECTS AGE
default Default Tenant true default default 1 21d
primary-dev Primary Dev true dev-settings primary-dev 0 5m -
Verify the Tenant configuration.
kubectl describe tenant <tenant-name>The following example shows
Display Name,Settings Ref, andTenant Role MappingunderSpec. It also shows that the Tenant is ready and has0child Projects underStatus. InStatus.Conditions,TenantNamespaceCreatedrefers to the controller-createdtenant-<tenant-name>namespace.kubectl describe tenant primary-devExample OutputName: primary-dev
Namespace:
Labels: <none>
Annotations: <none>
API Version: spectrocloud.com/v1alpha1
Kind: Tenant
Metadata:
Creation Timestamp: 2026-02-07T15:26:04Z
Finalizers: spectrocloud.com/tenant-finalizer
Generation: 1
Resource Version: 12345678
UID: abc-123-def-456
Spec:
Display Name: Primary Dev
Settings Ref:
Name: dev-settings
Namespace: tenant-primary-dev
Tenant Role Mapping:
Groups:
pai:tenant:primary-dev:role:tnt-adm-admin
pai:tenant:primary-dev:role:tnt-adm-sre
Status:
Child Project Count: 0
Conditions:
Last Transition Time: 2026-02-07T15:26:05Z
Message: Settings configured and ready
Reason: SettingsConfigured
Status: True
Type: SettingsConfigured
Last Transition Time: 2026-02-07T15:26:05Z
Message: Tenant namespace created successfully
Reason: TenantNamespaceCreated
Status: True
Type: TenantNamespaceCreated
Ready: true
Events: <none>
Modify Tenant
In the PaletteAI console, modify a Tenant from its Tenant Settings page, which covers display values, integrations, user access, resource limits, and more. Refer to Configure Tenant Settings for each tab.
Using the YAML workflow, update your manifests and apply them with kubectl apply to track changes in version control.
Enablement
-
Open the manifest you want to update and make the necessary changes.
Use the following table to identify what manifest to update for common changes. For the full parameter list, refer to the applicable Resource page.
Resource Modifications Additional Information Tenant Update displayName. UpdatetenantRoleMapping.groups. UpdategpuResources.Tenants and Projects - GPU Quotas Settings Create a Settings resource in the Tenant namespace ( tenant-<name>), and then update the TenantsettingsRef. To remove shared Settings, deletesettingsReffrom the Tenant spec.Settings The following example adds a Tenant admin group named
operationsand sets GPU resource limits.vi tenant.yamlUpdated TenantapiVersion: spectrocloud.com/v1alpha1
kind: Tenant
metadata:
name: primary-dev
spec:
displayName: 'Primary Dev'
computeConfigRef:
name: default
settingsRef:
name: dev-settings
namespace: tenant-primary-dev
tenantRoleMapping:
groups:
- 'pai:tenant:primary-dev:role:tnt-adm-admin'
- 'pai:tenant:primary-dev:role:tnt-adm-sre'
- 'pai:tenant:primary-dev:role:tnt-adm-operations'
gpuResources:
limits:
'NVIDIA A100-SXM4-80GB | 80 GB': 64
'NVIDIA H100 PCIe | 80 GB': 48
'Default': 24
requests:
'NVIDIA A100-SXM4-80GB | 80 GB': 8
'NVIDIA H100 PCIe | 80 GB': 8
'Default': 4
# Optional. Reserve GPUs exclusively for tenant-namespace resources.
# Each entry must be <= the matching `limits` entry. Projects share (limits - tenantReservations).
tenantReservations:
'NVIDIA A100-SXM4-80GB | 80 GB': 8
# Optional. Cap individual Projects below the Project's own `limits`.
# The Project's effective cap is the lower of its own `limits` and the matching `projectLimits` entry.
projectLimits:
project-a:
'NVIDIA A100-SXM4-80GB | 80 GB': 32
Default:
'NVIDIA A100-SXM4-80GB | 80 GB': 8 -
Save the file and apply the updated manifest.
kubectl apply --filename <manifest-location>kubectl apply --filename tenant.yaml
Validate
Verify that the Tenant reflects your updates.
kubectl describe tenant <tenant-name>
The following example shows the updated Tenant admin group and GPU resource limits.
kubectl describe tenant primary-dev
Name: primary-dev
# ... metadata omitted for readability
Spec:
Display Name: Primary Dev
Gpu Resources:
Limits:
Default: 24
NVIDIA A100-SXM4-80GB | 80 GB: 64
NVIDIA H100 PCIe | 80 GB: 48
Requests:
Default: 4
NVIDIA A100-SXM4-80GB | 80 GB: 8
NVIDIA H100 PCIe | 80 GB: 8
Settings Ref:
Name: dev-settings
Namespace: tenant-primary-dev
Tenant Role Mapping:
Groups: pai:tenant:primary-dev:role:tnt-adm-admin
pai:tenant:primary-dev:role:tnt-adm-sre
pai:tenant:primary-dev:role:tnt-adm-operations
Status:
Ready: true
Events: <none>
Namespace Reference Validation
To enforce Project isolation, PaletteAI admission webhooks validate that resources cannot reference namespaces belonging to other Projects. This prevents scenarios where deleting Project B could break resources in Project A that hold references to namespaces in Project B.
Validation Levels
PaletteAI validates namespace references at four levels, depending on the resource type and field:
-
Same namespace only: Some resource references must be in the same namespace as the owning resource. For example,
ComputePool.spec.clusterVariant.imported.environmentRef.namespacemust match the owning Project's namespace. -
Same or System namespace: Some references can be in the resource's namespace or a system namespace (
pai-systemormural-system) for shared resources. For example,ProfileBundle.spec.*.workloadProfileRefs[].namespacecan reference either the ProfileBundle's namespace or a system namespace. -
Same, System, or Tenant namespace: Some references can also use the Tenant namespace (
tenant-<name>) for Tenant-wide shared resources. For example,AIWorkload.spec.computePoolRef.namespacecan reference the Project namespace,pai-system, or the Tenant namespace when the referencedComputePoolis shared with the Project (viaComputePool.spec.sharedWith).AIWorkload.spec.profileBundles[].namespace,ComputePool.spec.profileBundleRef.namespace, and theworkloadDeploymentConfigs[].workloadProfileRef.namespacefield on bothAIWorkloadandComputePoolcan reference the Project namespace,pai-system, or the Tenant namespace. -
Same or Tenant namespace only: Some references can be in the resource's own namespace or the owning Tenant's namespace (
tenant-<name>), but not the system namespace. For example,Project.spec.computeConfigRef.namespacemust be either the Project namespace or the Tenant namespace.
Affected Resources
The following table shows which resources and fields are subject to namespace reference validation:
| Resource | Field | Validation Level |
|---|---|---|
AIWorkload | spec.computePoolRef.namespace | Same namespace, System namespace, or Tenant namespace (Tenant when ComputePool is shared with the project) |
AIWorkload | spec.profileBundles[].namespace | Same, system, or Tenant namespace |
AIWorkload | spec.workloadDeploymentConfigs[].workloadProfileRef.namespace | Same, system, or Tenant namespace |
AIWorkload | spec.clusterVariant.*.scalingPolicyRef.namespace | Same, system, Built-in, or Tenant namespace |
ComputePool | spec.clusterVariant.imported.environmentRef.namespace | Same namespace only |
ComputePool | spec.profileBundleRef.namespace | Same, system, or Tenant namespace |
ComputePool | spec.workloadDeploymentConfigs[].workloadProfileRef.namespace | Same, system, or Tenant namespace |
ComputePool | spec.clusterVariant.*.scalingPolicyRef.namespace | Same, system, Built-in, or Tenant namespace |
ProfileBundle | spec.*.workloadProfileRefs[].namespace | Same or system namespace |
Project | spec.computeConfigRef.namespace | Same or Tenant namespace only |
Settings | spec.integrations.palette.namespace | Same namespace only |
Behavior
If you attempt to create or update a resource with an invalid cross-Project namespace reference, the admission webhook rejects the request with a clear error message. For example, if an AIWorkload in Project namespace project-a references a ComputePool in Project namespace project-b, the webhook returns an error similar to the following:
spec.computePoolRef.namespace: namespace "project-b" must match owner namespace "project-a"
To resolve this error, ensure that the namespace reference is valid according to the validation level for that field. Refer to the table above for the permitted namespaces per field.
Next Steps
After you create a Tenant, create at least one Project under it. End users can access a Tenant in the UI only after at least one Project exists within it and their OIDC group is listed in that Project's roleMapping. Each Project must define its own Settings resource and Palette integration. Hugging Face and NVIDIA NGC integrations from the Tenant Settings Ref can be shared with Projects when configured. GPU quotas set at the Tenant level apply to all Projects under that Tenant. PaletteAI grants Tenant admin groups admin permissions in all Project namespaces.
If you encounter issues when creating or managing Tenants, refer to Troubleshooting Tenants.