Writing Your First Katalog

3 min read

A Katalog is a YAML file that tells Orkestra what to do when a Custom Resource is created, updated, or deleted. This guide builds one from scratch.


The Minimal Katalog

Create katalog.yaml:

apiVersion: orkestra.orkspace.io/v1
kind: Katalog
metadata:
  name: my-first-katalog

spec:
  crds:
    myapp:
      crdFile: ./crd.yaml
      crFiles:
        - ./cr.yaml
      operatorBox:
        default: true

This tells Orkestra: read the CRD from crd.yaml, apply it to the cluster, and watch for MyApp CRs. No resources are created yet.

crdFile is how you declare a CRD. Orkestra reads group, version, kind, and plural directly from the file and applies it to the cluster when ork run starts. No separate kubectl apply -f crd.yaml needed.

crFiles is how you declare a CR. Orkestra applies it at startup. No separate kubectl apply -f cr.yaml needed.


The CRD

Create crd.yaml:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: myapps.demo.myorg.io
spec:
  group: demo.myorg.io
  versions:
    - name: v1alpha1
      served: true
      storage: true
      subresources:
        status: {}
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              required: [image]
              properties:
                image:
                  type: string
                replicas:
                  type: integer
                  default: 1
                port:
                  type: integer
                  default: 80
  scope: Namespaced
  names:
    plural: myapps
    singular: myapp
    kind: MyApp

Adding a Deployment

spec:
  crds:
    myapp:
      crdFile: ./crd.yaml
      crFiles:
        - ./cr.yaml
      operatorBox:
        default: true
        onCreate:
          deployments:
            - name: "{{ .metadata.name }}"
              image: "{{ .spec.image }}"
              replicas: "{{ .spec.replicas }}"
              port: "{{ .spec.port }}"
              reconcile: true

Values inside {{ }} are Go templates evaluated against the live CR. reconcile: true means the Deployment is drift-corrected on every reconcile — if someone edits it manually, Orkestra corrects it back.


Adding a Service

onCreate:
  deployments:
    - name: "{{ .metadata.name }}"
      image: "{{ .spec.image }}"
      replicas: "{{ .spec.replicas }}"
      port: "{{ .spec.port }}"
      reconcile: true
  services:
    - name: "{{ .metadata.name }}-svc"
      port: "80"
      targetPort: "{{ .spec.port }}"
      reconcile: true

Conditional Resources

Use when: to create a resource only when a condition is met:

services:
  - name: "{{ .metadata.name }}-public"
    port: "80"
    targetPort: "{{ .spec.port }}"
    when:
      - field: spec.exposePublicly
        equals: "true"

Status Fields

Write values back to the CR after every reconcile:

operatorBox:
  default: true
  status:
    fields:
      - path: phase
        value: "Running"
      - path: observedReplicas
        value: "{{ .spec.replicas }}"
  onCreate:
    deployments:
      - name: "{{ .metadata.name }}"
        image: "{{ .spec.image }}"
        replicas: "{{ .spec.replicas }}"
        reconcile: true

The CRD must declare subresources: status: {} for status writes to work.


Dependencies Between CRDs

If your Katalog manages multiple CRDs and one must reconcile before the other:

spec:
  crds:
    database:
      crdFile: ./database-crd.yaml
      crFiles:
        - ./database-cr.yaml
        - ./mongodb-cr.yaml
      operatorBox:
        default: true
    application:
      crdFile: ./application-crd.yaml
      crFiles:
        - ./application-cr.yaml
      dependsOn:
        database:
          condition: healthy   # wait until database workers are running and failure-free
      operatorBox:
        default: true
    analytics:
      crdFile: ./analytics-crd.yaml
      crFiles:
        - ./analytics-cr.yaml
      dependsOn:
        database:
          condition: started   # wait only until database workers are running
      operatorBox:
        default: true

condition: healthy waits until the dependency’s workers are running and its consecutive failure count is zero — use this when the downstream CRD needs the upstream to be stable before it starts. condition: started waits only until workers are running — useful when you want ordering without blocking on health (for example, when the dependency might legitimately fail during startup and you want the downstream to proceed anyway).


Running It

ork run
# Orkestra reads katalog.yaml from the current directory and starts the runtime.

Verify it works:

kubectl get deployments
kubectl get services

Validate Without Running

ork validate
# Orkestra reads katalog.yaml from the current directory and reports every error without touching the cluster.