Blueprint Best Practices
Follow these guidelines to create blueprints that are maintainable, secure, reusable, and cost-effective.
Structure & Organization
Use Descriptive Names
# ✅ Good - Clear, descriptive names
metadata:
name: kubernetes-cicd-workshop
resources:
clusters:
- name: training-cluster
vms:
- name: participant-workstation-{{ .Index }}
# ❌ Avoid - Vague names
metadata:
name: workshop-1
resources:
clusters:
- name: main
vms:
- name: vm-{{ .Index }} Organize Variables Logically
Group related variables and use UI hints:
variables:
# Group 1: General settings
- name: participant_count
type: integer
default: 10
ui:
label: "Participant Count"
group: "General"
order: 1
- name: environment_ttl
type: string
default: "8h"
ui:
label: "Environment Lifetime"
group: "General"
order: 2
# Group 2: Cluster configuration
- name: k8s_version
type: string
default: "1.28"
ui:
label: "Kubernetes Version"
group: "Cluster"
order: 1
- name: worker_count
type: integer
default: 3
ui:
label: "Worker Nodes"
group: "Cluster"
order: 2
# Group 3: Advanced (hidden by default)
- name: custom_cni
type: string
default: "calico"
ui:
label: "CNI Plugin"
group: "Advanced"
hidden: true Document Everything
metadata:
name: cicd-workshop
description: |
Complete CI/CD training environment with GitLab, runners,
and participant workstations.
Includes:
- GitLab CE with container registry
- Configurable number of CI runners
- Individual participant VMs with dev tools
Recommended for groups of 5-30 participants.
variables:
- name: gitlab_version
type: string
default: "16.8"
ui:
label: "GitLab Version"
description: |
GitLab CE version to deploy. Check compatibility
with your runner images before changing. Security
Never Hardcode Secrets
# ❌ NEVER do this
resources:
secrets:
- name: api-key
data:
token: "sk-abc123def456" # Hardcoded secret!
# ✅ Generate secrets
resources:
secrets:
- name: api-key
type: generated
spec:
length: 32
charset: alphanumeric
# ✅ Reference external secrets
resources:
secrets:
- name: api-key
type: external
spec:
provider: vault
path: secret/data/api-keys/myservice
key: token Use Minimal Permissions
# ✅ Restrict participant permissions
namespaceConfig:
# Limited resource quotas
resourceQuota:
cpu: "4"
memory: "8Gi"
pods: "20"
# Network isolation
networkPolicy:
isolate: true
allowEgress: true
allowIngress: false
# ✅ Don't give cluster-admin to participants
access:
authentication:
defaultRole: edit # Not admin or cluster-admin Validate Input
variables:
- name: domain_prefix
type: string
required: true
constraints:
minLength: 3
maxLength: 20
# Only allow safe characters
pattern: "^[a-z][a-z0-9-]*$"
ui:
description: "Letters, numbers, and hyphens only. Must start with a letter."
- name: admin_email
type: string
required: true
constraints:
# Validate email format
pattern: "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$" Restrict Image Sources
# ✅ Use specific image tags, not :latest
resources:
vms:
- name: workstation
image: ubuntu-22.04 # Specific version
# Not: ubuntu:latest
helm:
- name: app
values:
image:
repository: myregistry.com/app
tag: "1.5.2" # Pinned version
# Not: latest Cost Optimization
Set Appropriate Defaults
# ✅ Default to smaller resources
variables:
- name: worker_size
type: string
default: "cx21" # Small by default
constraints:
enum: ["cx21", "cx31", "cx41", "cx51"]
ui:
description: "Larger instances cost more. Start small."
- name: worker_count
type: integer
default: 2 # Minimum viable
constraints:
min: 1
max: 10 Always Set TTL
# ✅ Prevent runaway costs
environment:
ttl: 8h # Auto-destroy after 8 hours
# Or make it configurable with a reasonable default
variables:
- name: ttl
type: string
default: "8h"
constraints:
enum: ["4h", "8h", "24h", "48h", "72h"]
ui:
label: "Environment Lifetime"
description: "Environment auto-destroys after this time"
environment:
ttl: "{{ .Variables.ttl }}" Enable Sleep Mode
# ✅ Auto-sleep inactive environments
environment:
sleepMode:
enabled: true
idleTimeout: 30m # Sleep after 30 min of inactivity
mode: scale-to-zero Right-Size Resources
# ✅ Scale resources based on participant count
resources:
clusters:
- name: main
nodes:
# Scale workers with participants, but within limits
workers: "{{ min 10 (max 2 (div .Variables.participant_count 5)) }}"
# Example:
# 5 participants → 2 workers (minimum)
# 20 participants → 4 workers
# 50 participants → 10 workers (maximum) Use Appropriate Isolation
# ✅ Choose isolation based on actual needs
# For basic training: namespace (cheapest)
environment:
isolation: namespace
# For admin training: vcluster (moderate)
environment:
isolation: vcluster
# Only when necessary: dedicated cluster (expensive)
environment:
isolation: dedicated-cluster Reusability
Use Variables for Everything Configurable
# ❌ Hardcoded values
resources:
clusters:
- name: main
nodes:
workers: 3
version: "1.28"
# ✅ Everything is configurable
variables:
- name: cluster_name
default: "main"
- name: worker_count
default: 3
- name: k8s_version
default: "1.28"
resources:
clusters:
- name: "{{ .Variables.cluster_name }}"
nodes:
workers: "{{ .Variables.worker_count }}"
version: "{{ .Variables.k8s_version }}" Create Composable Blueprints
# base-cluster.yaml - Reusable base
apiVersion: teabar.dev/v1
kind: Blueprint
metadata:
name: base-cluster
spec:
variables:
- name: cluster_name
default: "main"
- name: worker_count
default: 3
resources:
clusters:
- name: "{{ .Variables.cluster_name }}"
# Base configuration
# workshop.yaml - Extends base
spec:
extends:
- name: org/base-cluster
# Only add workshop-specific resources
resources:
helm:
- name: gitlab Make Extension Points Clear
metadata:
description: |
Base Kubernetes cluster blueprint.
Extension points:
- Override `worker_count` for different sizes
- Override `addons` to change installed components
- Add resources in extending blueprint
variables:
- name: worker_count
ui:
description: "Override in extending blueprint for different sizes"
- name: addons
type: object
default:
metricsServer: true
ingressNginx: true
certManager: false
ui:
description: "Override to enable/disable cluster addons" Maintainability
Use Semantic Versioning
metadata:
name: my-blueprint
version: 1.2.3
# Major.Minor.Patch
#
# Increment MAJOR when you make breaking changes
# Increment MINOR when you add features (backward compatible)
# Increment PATCH when you fix bugs (backward compatible) Keep a Changelog
metadata:
name: cicd-workshop
version: 2.1.0
description: |
CI/CD workshop environment.
Changelog:
- 2.1.0: Added ArgoCD support, updated GitLab to 16.x
- 2.0.0: BREAKING: Changed isolation to vcluster
- 1.5.0: Added participant VM option
- 1.4.0: Added monitoring stack Test Before Publishing
# 1. Validate syntax
teactl blueprint validate -f blueprint.yaml
# 2. Render with edge cases
teactl blueprint render -f blueprint.yaml --var participant_count=1
teactl blueprint render -f blueprint.yaml --var participant_count=50
# 3. Create test environment
teactl env create --blueprint ./blueprint.yaml --name test --dry-run
teactl env create --blueprint ./blueprint.yaml --name test
# 4. Verify all resources
teactl env status test --verbose
# 5. Test participant access
teactl participant create test --name tester --generate-password
# 6. Clean up
teactl env destroy test --yes Handle Deprecation Gracefully
variables:
# Old variable (deprecated)
- name: num_workers
type: integer
ui:
label: "Worker Count (DEPRECATED)"
description: "Use worker_count instead. Will be removed in v3.0"
hidden: true
# New variable
- name: worker_count
type: integer
default: "{{ default 3 .Variables.num_workers }}"
ui:
label: "Worker Count" Performance
Minimize Provisioning Time
# ✅ Provision independent resources in parallel
# Teabar automatically parallelizes when there are no dependencies
resources:
# These provision in parallel (no dependencies)
clusters:
- name: cluster-a
- name: cluster-b
vms:
- name: bastion
# These wait for clusters (have dependencies)
helm:
- name: argocd
cluster: cluster-a # Depends on cluster-a Use Efficient Images
# ✅ Use minimal images
resources:
vms:
- name: workstation
image: ubuntu-22.04-minimal # Smaller image = faster boot
helm:
- name: app
values:
image:
repository: myapp
tag: "1.0-alpine" # Alpine = smaller image Lazy-Load Optional Components
variables:
- name: enable_monitoring
type: boolean
default: false
ui:
description: "Enable monitoring stack (adds 2-3 min to provisioning)"
resources:
helm:
{{- if .Variables.enable_monitoring }}
- name: prometheus
chart: prometheus-community/kube-prometheus-stack
{{- end }} Common Patterns
Participant Numbering
# 1-based numbering (more natural for users)
resources:
vms:
- name: "participant-{{ add .Index 1 }}"
count: "{{ .Variables.participant_count }}"
# Creates: participant-1, participant-2, ... Environment-Aware URLs
resources:
helm:
- name: app
values:
ingress:
hosts:
- "app.{{ .Environment.Domain }}"
env:
BASE_URL: "https://app.{{ .Environment.Domain }}"
ENVIRONMENT: "{{ .Environment.Name }}" Conditional Components
variables:
- name: tier
type: string
default: "basic"
constraints:
enum: ["basic", "standard", "premium"]
resources:
helm:
# Always included
- name: core-app
# Standard and above
{{- if or (eq .Variables.tier "standard") (eq .Variables.tier "premium") }}
- name: monitoring
{{- end }}
# Premium only
{{- if eq .Variables.tier "premium" }}
- name: advanced-analytics
{{- end }} Default + Override Pattern
variables:
- name: helm_values_override
type: object
default: {}
ui:
label: "Helm Values Override"
description: "Deep-merged with default values"
component: code
resources:
helm:
- name: app
values: |
{{- $defaults := dict "replicas" 2 "debug" false }}
{{- $merged := merge .Variables.helm_values_override $defaults }}
{{- toYaml $merged | nindent 8 }} Checklist
Before publishing a blueprint, verify:
file-text
Documentation
- [ ] Meaningful name and description
- [ ] All variables have descriptions
- [ ] UI labels and groups set
- [ ] Changelog maintained
Security
- [ ] No hardcoded secrets
- [ ] Input validation on all variables
- [ ] Minimal permissions for participants
- [ ] Network policies configured
dollar-sign
Cost
- [ ] TTL configured
- [ ] Sleep mode enabled (if applicable)
- [ ] Resource limits set
- [ ] Reasonable defaults
Testing
- [ ] Validates without errors
- [ ] Renders with edge case values
- [ ] Successfully creates environment
- [ ] All resources provision correctly
Next Steps
- Blueprint Syntax - Full schema reference
- Blueprint Templating - Dynamic configuration
- Blueprint Catalog - Publish your blueprints