Skip to main content
Version: main 🚧

Create a Stack template

A Stack template defines applications that vCluster Platform deploys as one unit. It also defines the order in which those applications become ready. Use a Stack when applications have dependencies, need health gates, or pass values to later tasks. Each application is a task, tasks declare which other tasks they wait for, and the platform rolls them out in that order.

A StackTemplate is a reusable, cluster-scoped blueprint. A StackInstance applies that blueprint to one tenant cluster or control plane cluster. Each task creates either an AppInstance or an ArgoCDApplication.

Understand licensing​

Stacks don't have a separate license feature. Each task requires the feature for the resource it creates:

  • An App task requires the Apps feature.
  • An Argo CD Application task requires the Argo CD Integration feature.
  • A Stack that mixes task types requires both features.

Both features are available on every plan, including the free plan. If a task's feature is ever removed from the active plan, the StackInstance remains blocked with the FeatureNotAllowed reason. Learn more about Apps and the Argo CD integration.

Create a Stack template in the UI​

  1. Go to Management > Stacks & Apps, and select the Stacks tab.

  2. Click .

  3. Enter a name, display name, and description for the template.

  4. Add App or Argo CD Application tasks. Configure dependencies between tasks in the graph.

  5. Add parameters and published outputs when the Stack needs configurable or returned values.

  6. Click .

You can also apply the resource through the management API using the YAML editor.

Deploy a starter stack​

Use this complete example as a starting point for your own Stack. It has no external chart or infrastructure dependencies.

The first inline App task creates a ConfigMap from a Stack parameter. The second task waits for the first task to become healthy, then writes the captured greeting to another ConfigMap.

  1. Connect kubectl to the vCluster Platform management API:

    vcluster platform connect management
  2. Save the following resources as starter-stack.yaml. Set the project namespace and tenant cluster name for your destination, and set spec.owner to a user or team that can reach it. App tasks deploy as their owner, so a Stack with an App task needs one.

    Modify the following with your specific values to generate a copyable command:
    starter-stack.yaml
    # Define a reusable Stack template with two inline App tasks.
    apiVersion: management.loft.sh/v1
    kind: StackTemplate
    metadata:
    name: starter-stack
    spec:
    displayName: Starter Stack
    description: Deploys two ConfigMaps to demonstrate task ordering and output handoff.
    parameters:
    - variable: greeting
    label: Greeting
    description: Message passed from the first task to the second task.
    type: string
    defaultValue: Hello from a Stack
    tasks:
    - name: backend
    app:
    template:
    spec:
    displayName: Starter backend
    description: Creates the greeting consumed by the frontend task.
    defaultNamespace: stack-starter
    wait: true
    config:
    manifests: |-
    apiVersion: v1
    kind: ConfigMap
    metadata:
    name: starter-backend
    data:
    greeting: {{ .Values.greeting | quote }}
    outputs:
    - name: greeting
    fromResource:
    apiVersion: v1
    kind: ConfigMap
    namespace: stack-starter
    name: starter-backend
    jsonPath: "{.data.greeting}"
    - name: frontend
    # Don't create this AppInstance until the backend task is healthy.
    dependsOn:
    - backend
    app:
    template:
    spec:
    displayName: Starter frontend
    description: Records the greeting captured from the backend task.
    defaultNamespace: stack-starter
    wait: true
    config:
    manifests: |-
    apiVersion: v1
    kind: ConfigMap
    metadata:
    name: starter-frontend
    data:
    backendGreeting: {{ .Outputs.backend.greeting | quote }}
    publishedOutputs:
    - name: greeting
    fromTask:
    task: backend
    output: greeting
    ---
    # Apply the template to an existing tenant cluster.
    apiVersion: management.loft.sh/v1
    kind: StackInstance
    metadata:
    name: starter-stack
    namespace: p-my-project
    spec:
    displayName: Starter Stack
    # App tasks deploy as this user or team, so an owner is required.
    owner:
    user: admin
    destination:
    virtualCluster:
    name: development
    templateRef:
    name: starter-stack
    parameters:
    greeting: Hello from my first Stack
    # Delete child AppInstances if their tasks are removed from the template.
    prunePolicy: Prune
  3. Create the StackTemplate and StackInstance:

    kubectl apply -f starter-stack.yaml
  4. Wait for both tasks to report Healthy:

    kubectl get stackinstance starter-stack -n <project-namespace> \
    -o jsonpath='{.status.phase}{"\n"}{range .status.tasks[*]}{.name}{"\t"}{.phase}{"\n"}{end}'

    The StackInstance reports Healthy. The destination contains starter-backend and starter-frontend ConfigMaps in the stack-starter namespace.

Change spec.parameters.greeting on the StackInstance to test reconciliation. Replace either inline App template with your own manifests, or use templateRef to reference a reusable App.

Define a stack template​

The following example deploys a database App before an API App. Create the referenced postgresql and example-api Apps before you create this template.

stack-template.yaml
apiVersion: management.loft.sh/v1
kind: StackTemplate
metadata:
name: example-platform
spec:
displayName: Example application platform
description: Deploys a database before the API that uses it.
parameters:
- variable: databaseStorage
label: Database storage
description: Persistent storage allocated to the database.
type: string
defaultValue: 10Gi
tasks:
- name: database
app:
templateRef:
name: postgresql
parameters:
storage: "{{ .Values.databaseStorage }}"
- name: api
# Wait until the database AppInstance reports Healthy.
dependsOn:
- database
timeout: 20m
app:
templateRef:
name: example-api

Apply the template through the vCluster Platform management API:

vcluster platform connect management
kubectl apply -f stack-template.yaml

Tasks without dependency edges can run concurrently. A dependent task starts only after every task in dependsOn becomes healthy. The following sections explain the task, parameter, and output syntax used in this example in detail.

Configure tasks​

Give each task a unique DNS-label-safe name. A duplicate name is rejected when the Stack is applied. A Stack supports up to 20 tasks. Each task can set its own timeout. A task without one uses the Stack's defaults.taskTimeout, or 10 minutes if that isn't set either. Set exactly one task type:

  • app creates an AppInstance.
  • argoCDApplication creates an ArgoCDApplication.

Within the selected type, set either an inline template or a reusable templateRef. Don't set both. Parameters are valid only with templateRef. They pass through to the referenced template.

caution

For an app task, keep the combined StackInstance and task name short. The generated child name only enforces the 63-character Kubernetes object-name limit, but Helm's own release-name limit is 53 characters. A longer combination fails to deploy with a release name is invalid error that doesn't mention length.

This task uses a reusable ArgoCDApplicationTemplate:

tasks:
- name: workloads
argoCDApplication:
templateRef:
name: application-workloads
parameters:
environment: production
note

The Argo CD connector must be enabled before an Argo CD Application task can become healthy. See Connect Argo CD.

There's no field to skip a task based on a parameter. A Stack always resolves its full task set. To make a task a no-op under some condition, interpolate the templateRef name so it points at an App or ArgoCDApplicationTemplate that does nothing:

- name: gpu
app:
templateRef:
name: 'gpu-operator{{ if eq .Values.gpuProvider "gke" }}-skip{{ end }}'

Pass parameters to tasks​

Declare optional parameters under spec.parameters. Tasks read their values with {{ .Values.<variable> }}. A parameter declaration renders as a labeled form field in the UI, with a default value, and can enforce validation such as required, a min/max range, or a validation regular expression.

Values that don't have declarations are still available to tasks. However, vCluster Platform can't validate those values or supply defaults.

Pass outputs between tasks​

A task can capture one scalar value from a Secret or namespaced Kubernetes resource. A later task reads the value with {{ .Outputs.<task>.<output> }} and must list the source task in dependsOn. See Template values and outputs in Stacks for more Go template examples.

tasks:
- name: database
app:
templateRef:
name: postgresql
outputs:
- name: password
fromSecret:
namespace: database
name: database-credentials
key: password
- name: serviceip
fromResource:
apiVersion: v1
kind: Service
namespace: database
name: postgresql
# Select one scalar field with kubectl JSONPath syntax.
jsonPath: "{.spec.clusterIP}"
- name: api
dependsOn:
- database
app:
templateRef:
name: example-api
parameters:
databasePassword: "{{ .Outputs.database.password }}"
databaseAddress: "{{ .Outputs.database.serviceip }}"
publishedOutputs:
# Expose the service address without exposing the database password.
- name: databaseAddress
fromTask:
task: database
output: serviceip

Output names and the names of tasks that declare outputs can contain only lowercase letters and digits. Other task names can also contain hyphens. Resource outputs can't read cluster-scoped resources, and fromResource rejects kind: Secret outright. Read a Secret value with fromSecret, which also keeps it flagged as sensitive. The source namespace must be a namespace this Stack has already deployed an App or Argo CD Application into. vCluster Platform rejects a resource output whose source is in any other namespace before it reads the resource, even if the controller could otherwise reach it.

Output references support template functions and pipelines. For example, {{ .Outputs.database.password | quote }} is valid. Keep the reference in the canonical .Outputs.<task>.<output> form. An output reference can't control an if, range, or with action, declare a variable, or share one action with a .Values reference because parameters and outputs are rendered at different times. Parameter-driven control flow can contain output references in its body:

endpoint: '{{ if .Values.tls }}https://{{ .Outputs.api.hostname }}{{ else }}http://{{ .Outputs.api.hostname }}{{ end }}'

The text <% is reserved for internal output rendering and isn't valid in a task payload.

Published outputs are available through the StackInstance outputs subresource. They aren't written to status. See the StackInstanceOutputs reference for the response schema. Access to sensitive values requires separate permission to retrieve StackInstance outputs. See Stack permissions.

Test a stack template​

Create a StackInstance that references the template. A destination and, once set, an owner are both immutable after creation. A tenant cluster destination must be in the same project as the StackInstance. There's no cross-project form of this field, so deploying the same Stack into tenant clusters across several projects takes one StackInstance per project, all referencing the same template.

stack-instance.yaml
apiVersion: management.loft.sh/v1
kind: StackInstance
metadata:
name: example-platform
namespace: p-example
spec:
displayName: Example application platform
# App tasks deploy as this user or team, so an owner is required.
owner:
user: admin
destination:
virtualCluster:
name: development
templateRef:
name: example-platform
parameters:
databaseStorage: 20Gi
defaults:
# Apply this timeout to tasks that don't set their own timeout.
taskTimeout: 15m
# Delete children removed from the resolved task graph.
prunePolicy: Prune
kubectl apply -f stack-instance.yaml
kubectl get stackinstance example-platform -n p-example

The default prunePolicy is Retain. If a task is removed from the template, its existing child remains and appears in status.orphanedApplications. Set Prune to delete removed children in reverse dependency order.

warning

A referenced StackTemplate is resolved during every reconciliation. Stack templates don't have Stack revisions. Review template changes before applying them, to avoid an unwanted rollout to existing StackInstances.

Next steps​