Kubernetes Manifests

The manifests resource type lets you apply raw Kubernetes YAML manifests to your clusters. Manifests support Go templating for dynamic configuration.

Basic Usage

spec:
  resources:
    manifests:
      - name: app-config
        cluster: main
        namespace: default
        template: |
          apiVersion: v1
          kind: ConfigMap
          metadata:
            name: app-config
          data:
            environment: production
            log_level: info

Schema Reference

manifests:
  - name: string              # Required: Unique identifier for this manifest
    cluster: string           # Required: Target cluster name
    namespace: string         # Optional: Target namespace (default: default)
    enabled: boolean          # Optional: Enable/disable this manifest (default: true)
    count: integer            # Optional: Create multiple instances with {{ .Index }}
    template: string          # Required: Kubernetes manifest YAML with Go templates
    waitFor:                  # Optional: Wait conditions
      ready: boolean          # Wait for resources to be ready
      timeout: duration       # Timeout for wait (default: 5m)
    dependsOn:                # Optional: Resource dependencies
      - string                # Names of resources that must be created first

Templating

Manifests support full Go template syntax with access to variables, environment data, and other resources.

Variable Substitution

spec:
  variables:
    - name: app_replicas
      type: integer
      default: 3
    - name: app_image
      type: string
      default: "nginx:1.25"

  resources:
    manifests:
      - name: deployment
        cluster: main
        template: |
          apiVersion: apps/v1
          kind: Deployment
          metadata:
            name: web-app
          spec:
            replicas: {{ .Variables.app_replicas }}
            selector:
              matchLabels:
                app: web
            template:
              metadata:
                labels:
                  app: web
              spec:
                containers:
                  - name: app
                    image: {{ .Variables.app_image }}
                    ports:
                      - containerPort: 80

Environment Context

Access environment-level data:

template: |
  apiVersion: v1
  kind: ConfigMap
  metadata:
    name: env-info
  data:
    environment_id: "{{ .Environment.ID }}"
    environment_name: "{{ .Environment.Name }}"
    domain: "{{ .Environment.Domain }}"
    created_at: "{{ .Environment.CreatedAt }}"

Resource References

Reference outputs from other resources:

spec:
  resources:
    secrets:
      - name: db-password
        type: generated
        spec:
          length: 24

    manifests:
      - name: db-secret
        cluster: main
        template: |
          apiVersion: v1
          kind: Secret
          metadata:
            name: database-credentials
          type: Opaque
          stringData:
            password: "{{ .Resources.secrets.db-password.value }}"

Per-Participant Manifests

Create resources for each participant using count and {{ .Index }}:

spec:
  variables:
    - name: participant_count
      type: integer
      default: 10

  resources:
    manifests:
      # Create a namespace for each participant
      - name: participant-namespaces
        cluster: main
        count: "{{ .Variables.participant_count }}"
        template: |
          apiVersion: v1
          kind: Namespace
          metadata:
            name: participant-{{ .Index }}
            labels:
              teabar.dev/participant: "{{ .Index }}"
              teabar.dev/environment: "{{ .Environment.ID }}"

      # Create resource quotas for each participant
      - name: participant-quotas
        cluster: main
        count: "{{ .Variables.participant_count }}"
        template: |
          apiVersion: v1
          kind: ResourceQuota
          metadata:
            name: participant-quota
            namespace: participant-{{ .Index }}
          spec:
            hard:
              requests.cpu: "2"
              requests.memory: 4Gi
              limits.cpu: "4"
              limits.memory: 8Gi
              pods: "20"

Multi-Document Manifests

Include multiple Kubernetes resources in a single manifest using YAML document separators:

manifests:
  - name: monitoring-stack
    cluster: main
    namespace: monitoring
    template: |
      apiVersion: v1
      kind: Namespace
      metadata:
        name: monitoring
      ---
      apiVersion: v1
      kind: ServiceAccount
      metadata:
        name: prometheus
        namespace: monitoring
      ---
      apiVersion: rbac.authorization.k8s.io/v1
      kind: ClusterRole
      metadata:
        name: prometheus
      rules:
        - apiGroups: [""]
          resources: ["nodes", "pods", "services"]
          verbs: ["get", "list", "watch"]

Conditional Resources

Use Go template conditionals to include or exclude resources:

spec:
  variables:
    - name: enable_network_policies
      type: boolean
      default: true

  resources:
    manifests:
      - name: network-policies
        cluster: main
        template: |
          {{- if .Variables.enable_network_policies }}
          apiVersion: networking.k8s.io/v1
          kind: NetworkPolicy
          metadata:
            name: deny-all
            namespace: default
          spec:
            podSelector: {}
            policyTypes:
              - Ingress
              - Egress
          {{- end }}

Wait Conditions

Wait for resources to be ready before continuing:

manifests:
  - name: database
    cluster: main
    waitFor:
      ready: true
      timeout: 10m
    template: |
      apiVersion: apps/v1
      kind: StatefulSet
      metadata:
        name: postgres
      spec:
        serviceName: postgres
        replicas: 1
        selector:
          matchLabels:
            app: postgres
        template:
          metadata:
            labels:
              app: postgres
          spec:
            containers:
              - name: postgres
                image: postgres:15
                ports:
                  - containerPort: 5432

Dependencies

Ensure manifests are applied in the correct order:

manifests:
  # First: Create the namespace
  - name: app-namespace
    cluster: main
    template: |
      apiVersion: v1
      kind: Namespace
      metadata:
        name: myapp

  # Second: Create the ConfigMap (depends on namespace)
  - name: app-config
    cluster: main
    dependsOn:
      - app-namespace
    template: |
      apiVersion: v1
      kind: ConfigMap
      metadata:
        name: app-config
        namespace: myapp
      data:
        config.yaml: |
          debug: false

  # Third: Create the Deployment (depends on ConfigMap)
  - name: app-deployment
    cluster: main
    dependsOn:
      - app-config
    template: |
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: myapp
        namespace: myapp
      spec:
        replicas: 2
        selector:
          matchLabels:
            app: myapp
        template:
          metadata:
            labels:
              app: myapp
          spec:
            containers:
              - name: app
                image: myapp:latest
                volumeMounts:
                  - name: config
                    mountPath: /etc/app
            volumes:
              - name: config
                configMap:
                  name: app-config

Built-in Template Functions

Manifests have access to all Go template functions plus Teabar helpers:

FunctionDescriptionExample
toYamlConvert to YAML{{ .data \| toYaml }}
toJsonConvert to JSON{{ .data \| toJson }}
indentIndent lines{{ .content \| indent 4 }}
nindentNewline + indent{{ .content \| nindent 4 }}
b64encBase64 encode{{ .secret \| b64enc }}
b64decBase64 decode{{ .encoded \| b64dec }}
quoteQuote string{{ .value \| quote }}
lowerLowercase{{ .name \| lower }}
upperUppercase{{ .name \| upper }}
replaceReplace string{{ .name \| replace "-" "_" }}

Example: Complex Templating

manifests:
  - name: participant-rbac
    cluster: main
    count: "{{ .Variables.participant_count }}"
    template: |
      apiVersion: rbac.authorization.k8s.io/v1
      kind: Role
      metadata:
        name: participant-role
        namespace: participant-{{ .Index }}
      rules:
        {{- range .Variables.allowed_resources }}
        - apiGroups: ["{{ .apiGroup | default "" }}"]
          resources: {{ .resources | toJson }}
          verbs: {{ .verbs | toJson }}
        {{- end }}
      ---
      apiVersion: rbac.authorization.k8s.io/v1
      kind: RoleBinding
      metadata:
        name: participant-binding
        namespace: participant-{{ .Index }}
      subjects:
        - kind: ServiceAccount
          name: participant-{{ .Index }}
          namespace: participant-{{ .Index }}
      roleRef:
        kind: Role
        name: participant-role
        apiGroup: rbac.authorization.k8s.io

Best Practices

Use Namespaces

Always specify namespaces explicitly rather than relying on defaults:

# Good
manifests:
  - name: config
    cluster: main
    namespace: myapp
    template: |
      apiVersion: v1
      kind: ConfigMap
      # ...

# Avoid
manifests:
  - name: config
    cluster: main
    template: |
      apiVersion: v1
      kind: ConfigMap
      metadata:
        namespace: myapp  # Harder to track

Separate Concerns

Split complex deployments into logical manifest groups:

manifests:
  - name: database-resources
    cluster: main
    template: |
      # Database-related resources

  - name: app-resources
    cluster: main
    dependsOn: [database-resources]
    template: |
      # Application resources

  - name: ingress-resources
    cluster: main
    dependsOn: [app-resources]
    template: |
      # Ingress and networking

Validate Templates

Use the CLI to validate your manifests before deploying:

# Validate blueprint including manifest templates
teactl blueprint validate my-blueprint.yaml

# Render manifests to see the output
teactl blueprint render my-blueprint.yaml --var participant_count=5

Related Resources

ende