Writing Your First Motif

3 min read

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
  • inputs names are unique and do not clash with reserved keys
  • Template expressions in resources are syntactically valid
  • with: bindings in any imports satisfy required: true inputs

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:

  1. The redis-creds Secret is created once (via onCreate.secrets)
  2. The <cr-name>-redis Deployment and Service are reconciled every cycle
  3. The <cr-name> Deployment from the Katalog’s own operatorBox is 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 in with: are caught at startup by ork run, not at reconcile time.
  • Optional inputs not in with: use the Motif’s default:.

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