Creating Bundles
Learn how to create bundles that generate multiple related files together.
Bundles allow you to group multiple templates together and deploy them as a cohesive unit. This guide shows you how to create bundles from scratch.
By the end of this guide, you'll have created a complete bundle that:
- Generates multiple related Kubernetes manifests
- Shares common variables across templates
- Includes template-specific variables
- Can be deployed with a single command
A bundle is a directory containing:
- Bundle metadata (
conjure.json) - Defines the bundle and its variables - Template files (
*.tmpl) - The actual templates to generate
Unlike standalone templates, bundles:
- Generate multiple files at once
- Share variables across all templates
- Support template-specific overrides
- Maintain consistent configuration
Use bundles when you need to:
- Deploy multiple related resources together (Deployment + Service + Ingress)
- Maintain consistency across multiple files
- Manage environment-specific configurations
- Deploy complete applications or infrastructure
Use standalone templates when you need:
- A single file
- Maximum flexibility
- Quick one-off generation
Navigate to your bundles directory:
cd ~/conjure-workspace/bundles
Create the versioned bundle directory:
mkdir -p web-app/1.0.0
cd web-app/1.0.0
Create the conjure.json file that defines your bundle:
touch conjure.json
Open conjure.json and add the metadata:
{
"schema_version": "v1",
"version": "1.0.0",
"bundle_type": "kubernetes",
"bundle_name": "web-app",
"bundle_description": "Complete web application deployment with service and ingress",
"shared_variables": [
{
"name": "app_name",
"description": "Application name",
"type": "string",
"default": ""
},
{
"name": "namespace",
"description": "Kubernetes namespace",
"type": "string",
"default": "default"
},
{
"name": "image",
"description": "Container image (e.g., nginx:latest)",
"type": "string",
"default": ""
}
],
"template_variables": {
"deployment.yaml.tmpl": [
{
"name": "replicas",
"description": "Number of pod replicas",
"type": "int",
"default": "3"
},
{
"name": "cpu_limit",
"description": "CPU limit (e.g., 500m)",
"type": "string",
"default": "500m"
},
{
"name": "memory_limit",
"description": "Memory limit (e.g., 512Mi)",
"type": "string",
"default": "512Mi"
}
],
"service.yaml.tmpl": [
{
"name": "service_type",
"description": "Service type (ClusterIP, NodePort, LoadBalancer)",
"type": "string",
"default": "ClusterIP"
},
{
"name": "service_port",
"description": "Service port",
"type": "int",
"default": "80"
}
],
"ingress.yaml.tmpl": [
{
"name": "enable_tls",
"description": "Enable TLS",
"type": "bool",
"default": "false"
},
{
"name": "host",
"description": "Ingress hostname",
"type": "string",
"default": "example.com"
}
]
}
}
All bundle metadata files (conjure.json) must include these required fields:
{
"schema_version": "v1",
"bundle_type": "kubernetes",
"bundle_name": "web-app",
"bundle_description": "Bundle description",
"shared_variables": [],
"template_variables": {}
}
- schema_version - Currently only "v1" is supported
- bundle_name - Bundle identifier used in commands
- bundle_type - Classification type (e.g., "kubernetes", "terraform")
- bundle_description - Describes the purpose of the bundle
- shared_variables - Variables available to all templates in the bundle (can be empty)
- template_variables - Template-specific variables (can be empty)
The version field is optional. Version is determined by the directory name (e.g., 1.0.0/).
All variables (both shared and template-specific) must include three required fields:
type - Must be one of: "string", "int", or "bool"
description - Helps users understand the variable's purpose
name - The variable identifier used in templates
Variables with "default": "" are required. The user must provide a value via CLI flags, values files, or interactive prompts. Variables with non-empty defaults are optional.
{
"name": "app_name",
"description": "Application name",
"type": "string",
"default": ""
}
Now create the template files referenced in the bundle metadata.
touch deployment.yaml.tmpl service.yaml.tmpl ingress.yaml.tmpl
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{.app_name}}-deployment
namespace: {{.namespace}}
labels:
app: {{.app_name}}
spec:
replicas: {{.replicas}}
selector:
matchLabels:
app: {{.app_name}}
template:
metadata:
labels:
app: {{.app_name}}
spec:
containers:
- name: {{.app_name}}
image: {{.image}}
ports:
- containerPort: 8080
resources:
limits:
cpu: {{.cpu_limit}}
memory: {{.memory_limit}}
requests:
cpu: 100m
memory: 128Mi
apiVersion: v1
kind: Service
metadata:
name: {{.app_name}}-service
namespace: {{.namespace}}
labels:
app: {{.app_name}}
spec:
type: {{.service_type}}
selector:
app: {{.app_name}}
ports:
- port: {{.service_port}}
targetPort: 8080
protocol: TCP
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{.app_name}}-ingress
namespace: {{.namespace}}
labels:
app: {{.app_name}}
{{- if .enable_tls}}
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
{{- end}}
spec:
{{- if .enable_tls}}
tls:
- hosts:
- {{.host}}
secretName: {{.app_name}}-tls
{{- end}}
rules:
- host: {{.host}}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {{.app_name}}-service
port:
number: {{.service_port}}
Navigate to your workspace:
cd ~/conjure-workspace
Create test values file:
touch web-app-test.yaml
Add test values:
# Shared variables (used by all templates)
app_name: my-api
namespace: testing
image: nginx:latest
# Template-specific overrides
template_overrides:
deployment.yaml.tmpl:
replicas: 2
cpu_limit: 200m
memory_limit: 256Mi
service.yaml.tmpl:
service_type: NodePort
service_port: 8080
ingress.yaml.tmpl:
enable_tls: true
host: my-api.example.com
# Generate with values file
conjure bundle web-app -o ./test-output -f ./web-app-test.yaml
# View generated files
ls -l ./test-output
Expected output:
test-output/
├── deployment.yaml
├── service.yaml
└── ingress.yaml
# Check deployment
cat ./test-output/deployment.yaml
# Validate with kubectl (if available)
kubectl apply --dry-run=client -f ./test-output/
bundle_type
- Categorizes the bundle (kubernetes, terraform, etc.)
- Used for filtering with
conjure list bundles -t kubernetes
bundle_name
- How you reference the bundle in commands
- Must be unique across all bundles
bundle_description
- Displayed in
conjure list bundles - Helps users understand the bundle's purpose
shared_variables
- Variables available to all templates in the bundle
- Use for values needed across multiple templates (app_name, namespace, etc.)
template_variables
- Variables specific to individual templates
- Organized by template filename
- Use for template-specific configuration
Shared variables are for:
- Application name
- Namespace
- Image name/tag
- Environment
- Common labels
Template-specific variables are for:
- Replica counts (Deployment only)
- Service types (Service only)
- Resource limits (Deployment only)
- Ingress settings (Ingress only)
{
"bundle_name": "web-app", // Good - clear and descriptive
"bundle_name": "k8s-bundle-1", // Bad - unclear purpose
"bundle_name": "my-bundle" // Bad - too generic
}
{
"bundle_description": "Complete web application deployment with service and ingress", // Good
"bundle_description": "Kubernetes stuff", // Bad - too vague
"bundle_description": "Bundle" // Bad - not helpful
}
Include templates that work together:
- Deployment + Service + Ingress = Complete web app
- VPC + Subnet + Security Group = Network infrastructure
- ConfigMap + Secret + Deployment = App with config
Don't mix unrelated templates in one bundle.
Good shared variable:
{
"name": "app_name",
"description": "Application name used across all resources",
"type": "string",
"default": ""
}
Bad shared variable:
{
"name": "deployment_replicas", // This is deployment-specific
"description": "Number of replicas",
"type": "int"
}
{
"name": "namespace",
"description": "Kubernetes namespace",
"type": "string",
"default": "default" // Sensible default
}
{
"name": "replicas",
"description": "Number of replicas",
"type": "int",
"default": "3" // Works for most cases
}
{
"name": "cpu_limit",
"description": "CPU limit in millicores (e.g., 500m, 1000m, 2000m)",
"type": "string",
"default": "500m"
}
Include examples in descriptions to help users.
# Generate
conjure bundle web-app -o ./test-output -f ./test-values.yaml
# Validate all files
kubectl apply --dry-run=client -f ./test-output/
# Check specific file
kubectl apply --dry-run=client -f ./test-output/deployment.yaml
What You'll Create
What is a Bundle?
When to Use Bundles
Step 1: Create Bundle Directory
Step 2: Create Bundle Metadata
Bundle Metadata Requirements
Variable Requirements
Required Default Variables
Step 3: Create Template Files
deployment.yaml.tmpl
service.yaml.tmpl
ingress.yaml.tmpl
Step 4: Test Your Bundle
Create Test Values File
Generate Bundle
Verify Output
Understanding Bundle Structure
Bundle Metadata Fields
Variable Organization
Bundle Best Practices
1. Use Meaningful Bundle Names
2. Provide Good Descriptions
3. Group Related Templates
4. Use Shared Variables Wisely
5. Provide Sensible Defaults
6. Document Variable Purposes
Testing Bundles
Validation Testing
Next Steps