Create a template
Templates standardize virtual cluster, namespace, and app configurations. Create templates through the vCluster Platform UI or by applying manifests directly to your cluster.
Create a template using the UI​
You can create templates directly in the UI:
- Virtual cluster template
- App template
- Namespace template
In the vCluster Platform, select Templates from the left menu.
- From the Templates menu, select the Virtual Clusters option.
Click Add Virtual Cluster Template.
Provide your virtual cluster template with a name by replacing the
my-template
placeholder name, or by updating the manifest YAMLmetadata.name
field.[Optional] Update the template with any additional configuration options.
Click Create Template.
Apps are Helm charts, Kubernetes manifests, or bash scripts that define an application to be deployed into a cluster, namespace, or virtual cluster.
App templates can be created through the UI.
Select Templates in the left menu.
- Click the Apps option.
Click .
Provide your virtual cluster template with a name by replacing the
my-template
placeholder name, or by updating the manifest YAMLmetadata.name
field.On the Definition pane, select the type of app (
kubectl
, Helm, or bash), and provide the necessary details. These vary depending on your application type and environment.[Optional] Use the Display Options tab to upload an app icon, which appears in the UI.
[Optional] Use the Recommended App drop-down list to choose whether the app appears as recommended in the UI.
Click .
Namespace templates can be created directly in the UI to define default labels, annotations, and resource settings for new namespaces.
Select Templates in the left menu.
From the Templates menu, select the Namespaces.
Click Add Namespace Template .
Provide your virtual cluster template with a name by replacing the
my-template
placeholder name, or by updating the manifest YAMLmetadata.name
field.[Optional] Update the template with any additional desired configuration options.
Click .
Create a template from a manifest​
Alternatively, you can create a manifest and apply it to your cluster using kubectl
, Helm, or ArgoCD.
For example, to create a VirtualClusterTemplate
manifest:
Log in to the vCluster Platform:
vcluster platform login <PLATFORM_URL>
Connect your kubeconfig to the platform management API:
vcluster platform connect management
Create a manifest file named
virtualclustertemplate.yaml
that defines your template. You can use an example manifest as a starting point or export one from the UI.Apply the manifest to the cluster:
kubectl apply -f virtualclustertemplate.yaml
Example virtual cluster manifest​
apiVersion: management.loft.sh/v1
kind: VirtualClusterTemplate
metadata:
name: development-template
labels:
team: platform
environment: development
spec:
displayName: "Development environment template"
description: "Standardized development vCluster with appropriate resource limits and development tools pre-installed"
template:
metadata:
labels:
environment: development
cost-center: engineering
backup: enabled
helmRelease:
chart:
version: 0.25.0
values: |
controlPlane:
distro:
k8s:
version: "1.28.4"
resources:
limits:
cpu: "2000m"
memory: "4Gi"
requests:
cpu: "200m"
memory: "512Mi"
statefulSet:
highAvailability:
replicas: 3
persistence:
volumeClaim:
enabled: true
size: "10Gi"
storageClass: "fast-ssd"
networking:
resolveDNS:
- hostname: host.cluster.local
target:
hostname: cluster.local
ingress:
enabled: true
rbac:
clusterRole:
enabled: true
extraRules:
- apiGroups: [""]
resources: ["pods", "services"]
verbs: ["get", "list", "create", "update", "delete"]
sync:
fromHost:
nodes:
enabled: true
toHost:
persistentVolumes:
enabled: true
Configure template options​
Templates include configuration sections that define behavior, content, and access. The following options apply to both virtual cluster templates and namespace templates.
Template parameters​
Template parameters make templates dynamic by exposing configurable fields that users can customize during deployment. When creating parameters, you define the variable name, data type, label, and description.
Parameters can include validation rules such as allowed options, required fields, and default values. For example, you might create parameters for admin passwords, environment types, resource limits, or feature toggles. Users see these parameters as form fields. They can customize deployments without modifying the underlying template.
Apps and objects​
The Apps section lets you select pre-built applications that install automatically when virtual clusters are created from the template. Choose from available apps such as ArgoCD, Prometheus Stack, Ingress controllers, or custom applications specific to your organization.
Selected apps deploy immediately after virtual cluster creation. Development teams have required tools and services available without manual installation. This approach standardizes the application stack across virtual clusters. It also reduces setup time for development teams.
The Objects section defines Kubernetes manifests that are applied during virtual cluster creation. These include ConfigMaps, Secrets, or NetworkPolicies that establish baseline configurations.
Management access​
Management access defines who can view, edit, or delete the template. It applies to the template itself, not to the virtual clusters created from it.
You can assign an owner with full permissions and grant view-only or full-access roles to other users or teams. This ensures that only authorized administrators can modify templates while allowing teams to use them for deployment.
The access model follows role-based access control (RBAC) to prevent unauthorized changes that could compromise consistency or security.
Delete a template​
If a template is outdated, misconfigured, or no longer needed, you can remove it to keep your workspace clean and reduce confusion for users. Deleting a template removes it from the platform and prevents it from being used for future cluster creation. This does not affect any virtual clusters or resources that were already created from the template.
If you delete a template, any virtual cluster, app, or namespace that references it can no longer be edited or upgraded through the Platform UI’s form-based editor. To make changes, you must manually update the corresponding custom resource, by making direct edits to the relevant CustomResourceDefinition (CRD).
Navigate to Templates in the left menu.
Select the template type from the Templates menu.
Find the template you want to delete.
Click Delete and confirm the deletion.
Next steps​
- Configure template parameters to make templates customizable for different environments and use cases.
- Set up template versioning to manage template updates and maintain deployment compatibility.
Examples​
Virtual cluster template
The following template creates a virtual cluster with ResourceQuota and LimitRange objects to control resource usage. It configures persistent storage, includes RBAC rules for operational tasks, and enables ingress support for apps.
apiVersion: management.loft.sh/v1
kind: VirtualClusterTemplate
metadata:
name: development-template
labels:
team: platform
environment: development
spec:
displayName: "Development Environment Template"
description: "Standardized development vCluster with appropriate resource limits and operational tools pre-installed"
template:
metadata:
labels:
environment: development
cost-center: engineering
backup: enabled
objects: |-
apiVersion: v1
kind: Namespace
metadata:
name: dev-tools
labels:
environment: development
---
apiVersion: v1
kind: ResourceQuota
metadata:
name: development-quota
namespace: prod-tools
spec:
hard:
requests.cpu: "4"
requests.memory: 8Gi
limits.cpu: "8"
limits.memory: 16Gi
pods: "20"
---
apiVersion: v1
kind: LimitRange
metadata:
name: development-limits
namespace: prod-tools
spec:
limits:
- default:
cpu: 500m
memory: 512Mi
defaultRequest:
cpu: 100m
memory: 128Mi
type: Container
helmRelease:
chart:
version: 0.25.0
values: |
controlPlane:
distro:
k8s:
version: "1.28.4"
resources:
limits:
cpu: "2000m"
memory: "4Gi"
requests:
cpu: "200m"
memory: "512Mi"
statefulSet:
persistence:
volumeClaim:
enabled: true
size: "10Gi"
storageClass: "fast-ssd"
networking:
resolveDNS:
- hostname: host.cluster.local
target:
hostname: cluster.local
ingress:
enabled: true
rbac:
clusterRole:
enabled: true
extraRules:
- apiGroups: [""]
resources: ["pods", "services"]
verbs: ["get", "list", "create", "update", "delete"]
sync:
fromHost:
nodes:
enabled: true
toHost:
persistentVolumes:
enabled: true
accessPoint:
ingress: {}
spaceTemplate:
metadata: {}
access:
- verbs:
- get
- list
- create
- update
- delete
users:
- '*'
Preview environment template
The following template creates a temporary preview environment with automatic sleep and deletion policies. It includes ingress authentication requirements, scheduled sleep and wake cycles, and baseline security policies for short-lived preview deployments.
kind: VirtualClusterTemplate
apiVersion: management.loft.sh/v1
metadata:
name: preview-template
spec:
displayName: vCluster Preview Template
template:
metadata:
labels:
loft.sh/import-argocd: 'true'
annotations:
sleepmode.loft.sh/delete-after: '72000'
loft.sh/require-ingress-authentication: 'true'
sleepmode.loft.sh/sleep-after: '600'
sleepmode.loft.sh/ignore-user-agents: 'argo*'
objects: |
apiVersion: v1
kind: Namespace
metadata:
name: preview-hello-world-app
---
apiVersion: v1
kind: Secret
metadata:
name: ghcr-login-secret
namespace: preview-hello-world-app
labels:
loft.sh/project-secret-name: ghcr-login-secret
data:
.dockerconfigjson: e30K
type: kubernetes.io/dockerconfigjson
access:
rules:
- teams:
- loft-admins
clusterRole: cluster-admin
pro:
enabled: true
helmRelease:
chart:
version: 0.22.4
values: |-
external:
platform:
autoSleep:
afterInactivity: 10800
autoWakeup:
schedule: 11 8-16 * *1-5
autoDelete:
afterInactivity: 120000
sync:
toHost:
ingresses:
enabled: true
secrets:
all: true
controlPlane:
backingStore:
etcd:
embedded:
enabled: true
coredns:
embedded: true
policies:
limitRange:
enabled: true
podSecurityStandard: baseline
resourceQuota:
enabled: true
accessPoint:
ingress: {}
spaceTemplate:
metadata: {}
access:
- verbs:
- '*'
subresources:
- '*'
users:
- admin
Argo CD virtual cluster template
The following template automatically installs Argo CD with ApplicationSet controller and configures TLS-enabled ingress with team-based RBAC. It includes scheduled sleep mode, enterprise features with embedded etcd and CoreDNS, and provides custom links for quick access to Argo CD UI and GitOps repository.
apiVersion: management.loft.sh/v1
kind: VirtualClusterTemplate
metadata:
name: argo-cd-vcluster
spec:
displayName: Argo CD vCluster
description: >-
Creates a vCluster instance with Argo CD for use within a vCluster Platform
Project.
owner:
team: loft-admins
template:
metadata: {}
instanceTemplate:
metadata:
annotations:
loft.sh/custom-links: |-
Argo-CD=https://{{ .Values.loft.virtualClusterName }}-{{ .Values.loft.project }}-{{ .Values.loft.clusterAnnotations.domainPrefix }}.{{ .Values.loft.clusterAnnotations.domain }}
GitOps-Repo=https://github.com/loft-demos/{{ .Values.loft.clusterAnnotations.domainPrefix }}-app/
apps:
- name: argocd
version: 2.14.10
parameters: |
appValues: |-
global:
domain: {{ .Values.loft.virtualClusterName }}-{{ .Values.loft.project }}-{{ .Values.loft.clusterAnnotations.domainPrefix }}.{{ .Values.loft.clusterAnnotations.domain }}
configs:
cm:
url: https://{{ .Values.loft.virtualClusterName }}-{{ .Values.loft.project }}-{{ .Values.loft.clusterAnnotations.domainPrefix }}.{{ .Values.loft.clusterAnnotations.domain }}
create: true
application.resourceTrackingMethod: annotation
params:
"server.insecure": true
timeout.reconciliation: 0
rbac:
policy.csv: |-
g, Administration, role:admin
g, Sales, role:admin
g, Engineering, role:admin
server:
ingress:
enabled: true
annotations:
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
ingressClassName: "nginx"
hostname: {{ .Values.loft.virtualClusterName }}-{{ .Values.loft.project }}-{{ .Values.loft.clusterAnnotations.domainPrefix }}.{{ .Values.loft.clusterAnnotations.domain }}
tls: true
ingressGrpc:
enabled: false
applicationSet:
enabled: true
logLevel: "debug"
ingress:
enabled: true
ingressClassName: "nginx"
annotations:
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
hostname: {{ .Values.loft.virtualClusterName }}-{{ .Values.loft.project }}-{{ .Values.loft.clusterAnnotations.domainPrefix }}-appset.{{ .Values.loft.clusterAnnotations.domain }}
tls: true
notifications:
argocdUrl: https://{{ .Values.loft.virtualClusterName }}-{{ .Values.loft.project }}-{{ .Values.loft.clusterAnnotations.domainPrefix }}.{{ .Values.loft.clusterAnnotations.domain }}
cm:
create: true
logLevel: "debug"
secret:
labels:
loft.sh/project-secret-name: argocd-notifications-secret
- name: argo-cd-cluster-application-set
objects: |-
apiVersion: v1
kind: Namespace
metadata:
name: argocd
---
apiVersion: v1
kind: Secret
metadata:
name: loft-demo-org-cred
namespace: argocd
labels:
loft.sh/project-secret-name: loft-demo-org-cred
argocd.argoproj.io/secret-type: repo-creds
type: Opaque
---
pro:
enabled: true
helmRelease:
chart:
version: 0.24.1
values: |
sleepMode:
enabled: true
autoSleep:
schedule: 0 22 * * *
timeZone: America/New_York
autoWakeup:
schedule: 0 6 * * 1-5
external:
platform:
autoDelete:
afterInactivity: 18000000
sync:
toHost:
ingresses:
enabled: true
customResources:
argocdwebhooks.demo.loft.sh:
enabled: true
controlPlane:
backingStore:
etcd:
embedded:
enabled: true
coredns:
enabled: true
embedded: true
accessPoint:
ingress: {}
spaceTemplate:
metadata: {}
objects: |-
apiVersion: v1
kind: Secret
metadata:
name: argo-webhook-url
type: Opaque
stringData:
url: "https://{{ .Values.loft.virtualClusterName }}-{{ .Values.loft.project }}-{{ .Values.loft.clusterAnnotations.domainPrefix }}.{{ .Values.loft.clusterAnnotations.domain }}/api/webhook"
---
apiVersion: v1
kind: Secret
metadata:
name: argo-appset-webhook-url
type: Opaque
stringData:
url: "https://{{ .Values.loft.virtualClusterName }}-{{ .Values.loft.project }}-{{ .Values.loft.clusterAnnotations.domainPrefix }}-appset.{{ .Values.loft.clusterAnnotations.domain }}/api/webhook"
Parameterized virtual cluster template
The following template demonstrates parameters for sleep timeout and Kubernetes version selection during deployment. Users can choose from predefined options with default values, and the template uses embedded etcd and CoreDNS for resource efficiency.
apiVersion: management.loft.sh/v1
kind: VirtualClusterTemplate
metadata:
name: sleepy-vcluster-template
annotations:
argocd.argoproj.io/sync-options: Validate=false
spec:
displayName: Sleepy vCluster Template
description: This template deploys a sleepy virtual cluster
template:
metadata: {}
instanceTemplate:
metadata: {}
pro:
enabled: true
helmRelease:
chart:
name: vcluster
version: 0.25.1
values: |
sync:
toHost:
ingresses:
enabled: true
controlPlane:
backingStore:
etcd:
embedded:
enabled: true
coredns:
embedded: true
distro:
k8s:
image:
tag: "{{ .Values.k8sVersion }}"
sleepMode:
enabled: true
autoSleep:
afterInactivity: "{{ .Values.sleepAfter }}m"
accessPoint:
ingress: {}
spaceTemplate:
metadata: {}
parameters:
- variable: sleepAfter
label: Sleep After Inactivity (minutes)
type: number
options:
- '30'
- '45'
- '60'
- '120'
defaultValue: '45'
- variable: k8sVersion
label: k8sVersion
description: Please select Kubernetes version
options:
- v1.33.1
- v1.32.5
- v1.31.9
- v1.30.13
defaultValue: v1.32.5
access:
- verbs:
- get
users:
- '*'
Versioned virtual cluster template
The following template demonstrates versioning with a default base template and versioned variations. Version 1.0.0 includes enhanced features such as sleep mode configuration, custom links to Helm Dashboard, pre-installed helm-dashboard app, and user-configurable parameters for Kubernetes version and environment selection.
kind: VirtualClusterTemplate
apiVersion: management.loft.sh/v1
metadata:
name: default-template
spec:
displayName: Default Virtual Cluster Template
description: This virtual cluster template deploys a vCluster with embedded etcd and integrated CoreDNS.
owner:
team: loft-admins
template:
metadata: {}
instanceTemplate:
metadata: {}
pro: {}
helmRelease:
chart:
version: 0.24.0
values: |-
sync:
toHost:
ingresses:
enabled: true
accessPoint:
ingress: {}
spaceTemplate:
metadata: {}
versions:
- version: 1.0.0
template:
metadata:
annotations:
sleepmode.loft.sh/ignore-user-agents: argo*
instanceTemplate:
metadata:
annotations:
loft.sh/custom-links: >-
Helm-Dashboard=https://helm-dashboard-{{ .Values.loft.virtualClusterName }}-{{ .Values.loft.clusterAnnotations.domainPrefix }}.{{ .Values.loft.clusterAnnotations.domain }}
labels:
env: '{{ .Values.env }}'
demos.loft.sh/project: '{{ .Values.loft.project }}'
demos.loft.sh/owner: '{{ or (and .Values.loft.user .Values.loft.user.name) (and .Values.loft.team .Values.loft.team.name) }}'
apps:
- name: helm-dashboard
namespace: helm-dashboard
pro:
enabled: true
helmRelease:
chart:
name: vcluster
version: 0.26.0-beta-8
values: |
sleepMode:
enabled: true
autoSleep:
afterInactivity: 30m
timeZone: America/New_York
autoWakeup:
schedule: 11 7-17 * * 1-5
external:
platform:
autoDelete:
afterInactivity: 1800000
sync:
toHost:
pods:
patches:
- path: metadata.annotations
expression: 'value["demos.loft.sh/project"]="{{ .Values.loft.project }}";value'
- path: metadata.labels
expression: 'value["demos.loft.sh/owner"]="{{ or (and .Values.loft.user .Values.loft.user.name) (and .Values.loft.team .Values.loft.team.name) }}";value'
ingresses:
enabled: true
secrets:
all: true
controlPlane:
advanced:
workloadServiceAccount:
enabled: true
imagePullSecrets:
- name: "hook-image-pull-secret"
distro:
k8s:
image:
tag: "{{ .Values.k8sVersion }}"
service:
labels:
env: '{{ .Values.env }}'
demos.loft.sh/project: '{{ .Values.loft.project }}'
demos.loft.sh/owner: '{{ or (and .Values.loft.user .Values.loft.user.name) (and .Values.loft.team .Values.loft.team.name) }}'
statefulSet:
annotations:
demos.loft.sh/pvClaimName: '{{ .Values.pvClaimName }}'
labels:
env: '{{ .Values.env }}'
demos.loft.sh/project: '{{ .Values.loft.project }}'
demos.loft.sh/owner: '{{ or (and .Values.loft.user .Values.loft.user.name) (and .Values.loft.team .Values.loft.team.name) }}'
pods:
labels:
env: '{{ .Values.env }}'
demos.loft.sh/project: '{{ .Values.loft.project }}'
demos.loft.sh/owner: '{{ or (and .Values.loft.user .Values.loft.user.name) (and .Values.loft.team .Values.loft.team.name) }}'
resources:
limits:
ephemeral-storage: 8Gi
memory: 2Gi
cpu: 1
backingStore:
etcd:
embedded:
enabled: true
coredns:
embedded: true
accessPoint:
ingress: {}
spaceTemplate:
metadata: {}
objects: |
apiVersion: v1
kind: Secret
metadata:
name: hook-image-pull-secret
labels:
loft.sh/project-secret-name: ghcr-login-secret
data:
.dockerconfigjson: e30K
type: kubernetes.io/dockerconfigjson
parameters:
- variable: k8sVersion
label: k8sVersion
description: Please select Kubernetes version
options:
- v1.33.2
- v1.32.6
- v1.31.10
- v1.30.14
defaultValue: v1.32.6
section: Kubernetes Environment
- variable: env
label: Deployment Environment
description: >-
Environment for deployments for this vCluster used as cluster label
for Argo CD ApplicationSet Cluster Generator
options:
- dev
- qa
- prod
defaultValue: dev
section: Deployment Environment
- version: 0.0.0
template:
metadata: {}
instanceTemplate:
metadata: {}
pro:
enabled: true
helmRelease:
chart:
version: 0.24.0
values: |-
sync:
toHost:
ingresses:
enabled: true
accessPoint:
ingress: {}
spaceTemplate:
metadata: {}
access:
- verbs:
- '*'
subresources:
- '*'
users:
- admin