Writing Your First Motif
A Motif is a reusable building block. It declares named inputs and a set of Kubernetes resources. You write it once, publish it, and import it into any Katalog — with different inputs each time.
Motifs are not operators. They do not watch CRDs and they cannot run alone. A Motif is the what; a Katalog is the when and who.
What you will build
A redis Motif that creates a Redis Deployment, a headless Service, and a credentials Secret. Any Katalog that needs Redis imports it with two lines.
Step 1 — Create the Motif file
Create motif.yaml:
apiVersion: orkestra.orkspace.io/v1
kind: Motif
metadata:
name: redis
version: v0.1.0
description: Redis Deployment with headless Service and credentials Secret.
author: platform-team
inputs:
- name: image
description: Redis image tag
default: "redis:7-alpine"
- name: password
description: Redis password (leave empty to disable auth)
default: ""
resources:
onCreate:
secrets:
- name: "{{ .metadata.name }}-redis-creds"
once: true
data:
password: "{{ .inputs.password }}"
deployments:
- name: "{{ .metadata.name }}-redis"
image: "{{ .inputs.image }}"
replicas: "1"
port: "6379"
reconcile: true
services:
- name: "{{ .metadata.name }}-redis"
port: "6379"
targetPort: "6379"
reconcile: true
status:
fields:
- path: redisReady
value: "{{ replicasReady .children.deployment }}"
The template context is the CR being reconciled — .metadata.name is the CR’s name, not the Motif’s name. The Motif does not know which CRD will import it.
Step 2 — Validate it
ork validate -f motif.yaml
ork validate checks:
- Required fields are present
inputsnames are unique and do not clash with reserved keys- Template expressions in
resourcesare syntactically valid with:bindings in any imports satisfyrequired: trueinputs
No cluster is needed.
Step 3 — Import the Motif into a Katalog
Create katalog.yaml alongside motif.yaml:
apiVersion: orkestra.orkspace.io/v1
kind: Katalog
metadata:
name: myapp
spec:
crds:
myapp:
crdFile: ./crd.yaml
crFiles:
- ./cr.yaml
imports:
- motif: ./motif.yaml
with:
image: "{{ .spec.redisImage | default \"redis:7-alpine\" }}"
password: "{{ .spec.redisPassword }}"
operatorBox:
deployments:
- name: "{{ .metadata.name }}"
image: "{{ .spec.image }}"
replicas: "{{ .spec.replicas }}"
reconcile: true
imports is where Motifs are bound. Each with: value is a Go template evaluated in the CR’s reconcile context — the same context available in operatorBox templates.
Step 4 — Run the Katalog
ork run
Orkestra loads the Katalog, expands the Motif import, and starts the operator. When a CR is applied:
- The
redis-credsSecret is created once (viaonCreate.secrets) - The
<cr-name>-redisDeployment and Service are reconciled every cycle - The
<cr-name>Deployment from the Katalog’s ownoperatorBoxis also reconciled
The CR’s status will show redisReady: "true" once the Redis Deployment is available.
How inputs flow
CR spec Motif
.spec.redisImage → with.image → inputs.image → {{ .inputs.image }}
.spec.redisPassword → with.password → inputs.password → {{ .inputs.password }}
- A
with:value can be a Go template:"{{ .spec.redisImage }}", a static string:"redis:7-alpine", or empty string:"". - Required inputs (no
default:) that are not inwith:are caught at startup byork run, not at reconcile time. - Optional inputs not in
with:use the Motif’sdefault:.
Publishing to the registry
Once the Motif works locally, push it:
ork registry push ./motif.yaml --registry oci://ghcr.io/myorg/patterns/redis --tag v0.1.0
Other Katalogs can then import it by OCI reference:
imports:
- motif: oci://ghcr.io/myorg/patterns/redis:v0.1.0
oci: true
with:
image: "{{ .spec.redisImage }}"
Project layout
redis/
motif.yaml # the Motif
example/
katalog.yaml # example Katalog importing this Motif
crd.yaml
cr.yaml
Next steps
- Writing Your First Katalog — import this Motif into a full operator
- Motif schema reference — complete field reference
- Orkestra Registry — browse published Motifs