Publishing Katalogs

4 min read

A Katalog published to the registry is a complete operator. The consumer imports it and gets a full CRD + reconciler — simulate assertions, E2E proof, and all. This page covers the publish flow from local development to a verified artifact.


The katalog directory

02-katalog-api/
  katalog.yaml    ← required
  crd.yaml        ← CRD manifest
  cr.yaml         ← sample CR for testing
  simulate.yaml   ← reconciler assertions (gates push)
  e2e.yaml        ← cluster verification (gates push)
  README.md       ← shown in ork patterns and ork inspect

katalog.yaml is the only required file. Adding simulate.yaml means the artifact carries a proof. Adding e2e.yaml means it was verified against a real cluster. Consumers see both before importing.


Local development: use file paths for motif imports

While developing locally, reference Motifs by relative file path:

# katalog.yaml — local development
spec:
  crds:
    webapp:
      imports:
        - motif: ../01-motifs/web-service/motif.yaml
          with:
            image: "{{ .spec.image }}"
            port: "{{ .spec.port }}"

This lets ork simulate, ork template, and ork validate resolve the Motif without needing it in the registry. Before pushing the Katalog, replace the path with the OCI ref (see below).


Write and validate

ork validate -f katalog.yaml

Validates the merged Katalog, checks CRD entry types, confirms all Motif inputs declared in with: are present in the imported Motif.

Render the expanded Katalog to see what a real reconcile would produce:

ork template -f katalog.yaml

The output shows every resource that would be created by the Deployment, Service, and Ingress — with all template expressions evaluated against the sample CR.


Write simulate assertions

ork simulate init      # scaffolds simulate.yaml from a live reconcile run
ork simulate           # run and verify

Simulate is unit testing for the reconciler — no cluster, no Helm, no Docker. It runs in under a second and catches template errors, wrong resource names, and incorrect cycle ordering before any gate runs.

Simulating webapp/my-webapp

  Cycle 1:
    + deployments/my-webapp
    + services/my-webapp-svc
    ~ status/my-webapp
  Cycle 2:
    ~ status/my-webapp

  ✓ 2/2 assertions passed
  Steady state at cycle 2 in 14ms

The simulate assertions are the behavioral contract you publish to consumers. A pattern with ✓ Verified · N assertions is one you proved works. See Gate Mechanics for the full gate story.


Run E2E

ork e2e

Provisions a kind cluster, installs Orkestra, applies the CRD and CR, and verifies all expect: conditions. Tears down when done. Takes 2–5 minutes. Use --cluster to run against an existing context instead.


Before pushing: replace local Motif imports with OCI refs

# katalog.yaml — before publishing
spec:
  crds:
    webapp:
      imports:
        - motif: oci://ghcr.io/myorg/motifs/web-service:v1.0.0
          with:
            image: "{{ .spec.image }}"
            port: "{{ .spec.port }}"

ork push will block with a clear error if any local file imports remain:

✗ Push blocked: local file imports in katalog.yaml

  spec.crds.webapp.imports[0]: "../01-motifs/web-service/motif.yaml"

  Local imports work for ork simulate and ork template, but cannot
  be resolved by consumers after the katalog is published.

  Before publishing:
    1. Push the motif:  ork push <motif-dir>/
    2. Replace the local path with the OCI ref:
       motif: oci://<your-registry>/motifs/<name>:<version>

Push

export ORK_REGISTRY=ghcr.io/myorg/katalogs

ork push ./

With both simulate.yaml and e2e.yaml present, push runs the full gate sequence:

Pushing webapp-operator:v1.0.0 (Katalog) to ghcr.io/myorg/katalogs...
  ✓ katalog.yaml         valid
  ✓ crd.yaml             valid
  ✓ cr.yaml              (512 B)
  ✓ simulate.yaml        (872 B)
  ✓ e2e.yaml             (1.4 KB)
  ✓ README.md            (3.1 KB)

Running simulate gate (simulate.yaml)...
  ✓ Simulate passed (2 assertions · 14ms)

Running E2E gate (e2e.yaml)...
  ✓ E2E passed (48s)

✓ Pushed: oci://ghcr.io/myorg/katalogs/webapp-operator:v1.0.0
  Digest: sha256:a3f1c8d20e4b7f9c...

To import:
  imports:
    registry:
      - oci://ghcr.io/myorg/katalogs/webapp-operator:v1.0.0

Push with a version override (useful in CI where the version is set by the pipeline):

ork push webapp-operator:v1.0.0 ./

Verify the artifact

ork inspect webapp-operator:v1.0.0
webapp-operator:v1.0.0
  Kind:        Katalog
  Simulate:    ✓ Verified · 2 assertions · 14ms
  E2E:         ✓ Verified · 48s

To import:
  imports:
    registry:
      - oci://ghcr.io/myorg/katalogs/webapp-operator:v1.0.0

Try it

ork init --pack registry-guide
cd 02-katalog-api

# Local development
ork template
ork simulate
ork e2e

# Update motif import to OCI ref, then push
export ORK_REGISTRY=ghcr.io/myorg/katalogs
ork push ./
ork inspect webapp-operator:v1.0.0