---
title: "One model, two runtimes"
description: "How the monedula-gitops CLI and Kubernetes operator share one resource model and one diff engine, and where credentials and gating differ."
url: https://monedula.dev/flock/docs/gitops/concepts/one-model-two-runtimes/
---
The CLI runs from a terminal or a CI/CD pipeline, and the Kubernetes operator runs inside a cluster. Both work with the same six kinds, all with `apiVersion: gitops.monedula.dev/v1alpha1`: `KafkaCluster`, `KafkaTopic`, `KafkaAccessPolicy`, `KafkaQuota`, `KafkaRoleBinding` and `KafkaUser`. These are Kubernetes-native types, so a manifest you pass to the CLI has the shape of a custom resource the operator watches.

## One engine

The diff, the risk gates and the executor are shared code. The operator runs one reconciler per kind on the same engine as `apply`, so plans, approval gates, [reconciliation modes](/flock/docs/gitops/concepts/drift-and-reconciliation/) and the definition of drift are identical in both runtimes.

## Where the runtimes differ

| | CLI | Operator |
| --- | --- | --- |
| Runs | when you run a command, for example in CI | continuously, on resource changes and on every resync |
| `KafkaCluster` | file or directory from `--cluster-config-file` | custom resource in the referencing resource's namespace, or in the [`clusterNamespace`](/flock/docs/gitops/how-to/configure-the-operator/) |
| Credentials | `valueFrom.env`, `valueFrom.file` relative to the cluster config directory, or `inline` | `valueFrom.secretKeyRef`, a Secret in the resource's namespace, or `inline`; `env` and `file` are rejected |
| `KafkaUser` passwords | `valueFrom.env`, `valueFrom.file` | `valueFrom.secretKeyRef`, or `generate` |
| Schema bodies | `valueFrom.file` (relative to the manifest), `inline` | `inline`, `configMapKeyRef`, `secretKeyRef` |
| Approve destructive changes | `apply --allow-destructive` | annotation `gitops.monedula.dev/allow-destructive` |
| Opt in to pruning | `apply --prune` | `spec.prune: true` |
| Removing a resource | deletes no topic, user or quota; grants still in the [managed scope](/flock/docs/gitops/concepts/safety-gates/) are removed only with `--prune` | finalizer under `spec.deletionPolicy`; topics and access policies also need the annotation `gitops.monedula.dev/allow-delete` |
| Tenancy policy | checks its shape only | [enforced per namespace](/flock/docs/gitops/how-to/enforce-tenancy/) |
| Admission webhooks | none | [optional, off by default](/flock/docs/gitops/how-to/configure-the-operator/) |
| Only here | `import cluster`, `doctor` | status conditions, Kubernetes events, Prometheus metrics |

Because `env`, `file` and `secretKeyRef` each work in one runtime only, a `KafkaCluster` or `KafkaUser` that uses them resolves in one runtime only. A `KafkaCluster` with `inline` credentials resolves in both, with the values in plain text in the manifest. Topics, access policies, quotas and role bindings carry no credentials and work in both, except schema bodies read from files.

The CLI reads an absolute `valueFrom.file` path as it is. It rejects a relative path that `..` takes out of its base: the cluster config directory for credentials and passwords, or the parent of the manifest's directory for schema bodies, so `file: ../schemas/orders-value.avsc` still works.

## Related

- [Get started with the CLI](/flock/docs/gitops/tutorials/getting-started-cli/) and [Get started with the operator](/flock/docs/gitops/tutorials/getting-started-operator/) walk through each runtime.