Skip to content
kix /docs
Install the CLI

Reference CLI

diff

Compare a cluster build with the live cluster, a previous build, or a saved manifest directory, package by package.

kix diff evaluates and builds a cluster, then compares the result with an old side: the live cluster by default, or a directory passed with --from. It only reads, so it needs get and list on the cluster, or no cluster at all with --from. It never changes the cluster.

A read cannot see what shows only on the write path: admission webhooks and policies, field ownership conflicts, and what the deploy itself refuses. To preview a deploy you are about to run, use kix plan, which sends each write as a server-side dry run.

This page is hand-maintained. Check cli/kix-cli/src/cli.rs and cli/kix-cli/src/commands/diff.rs when in doubt.

kix diff <CLUSTER> [--from <DIR>] [-o text|json|yaml|markdown]
[--detail plain|fields|manifests]
[--flake <FLAKE>] [--context <CONTEXT>]
Argument or flagMeaning
<CLUSTER>Cluster name to evaluate and build from the flake. This is the new side of the comparison.
--from <DIR>Use a directory as the old side instead of the live cluster. See Old side.
-o, --outputtext (default), json, yaml (the JSON document as YAML), or markdown for pull request comments.
--detail <LEVEL>How much json and yaml say about each change: plain (default) names each changed resource and counts its changed fields, fields also lists each field change, and manifests also includes the old and new manifest of each changed, added, or removed resource. Text and Markdown are the same at every level.
--flake <FLAKE>Flake to evaluate. Defaults to the current directory.
--context <CONTEXT>Kubeconfig context for the live cluster. Ignored with --from.
Old sideHow to select itNeeds cluster access
Live clusterOmit --fromYes
Previous build--from <DIR> where <DIR> contains kix-packages.json, such as the result of nix build .#cluster-<name>-activationNo
Saved manifests--from <DIR> where <DIR> holds YAML written by kix snapshot save or kix export --for auditNo

For the live cluster, Kix reads only the fields it applied to each managed resource. Kubernetes records which tool set each field through server-side apply, and Kix keeps its own fields and drops the rest: server defaults, status, fields set by controllers or kubectl, and the bookkeeping annotations Kix writes after every apply. A cluster that matches its build reports no differences.

The live side holds only the named cluster’s resources: those whose kix.run/activations annotation names one of its Activation records. When the Kubernetes cluster also runs other Kix clusters, their resources are not reported as removals, and stderr says how many were skipped per cluster. A resource the build drops that another Kix cluster’s current build still contains is left out too, since kix deploy --prune keeps it.

Against a live cluster, kix diff also prints an error for each build resource whose kind the cluster serves as namespaced but whose manifest has no metadata.namespace. kix deploy refuses to apply a build that contains one.

A manifest directory must carry the Kix annotations that name each resource’s package and identity hash. The default kix export mode strips them, so use --for audit when the old side comes from an export.

Every old side goes through the same comparison. The live cluster and a snapshot of it produce the same report.

The report is organised by package. Each package lands in one group:

GroupMeaning
addedThe package is in the build but not on the old side
removedThe package is on the old side but not in the build
changedThe package exists on both sides and at least one resource differs
unchangedEvery resource in the package matches

Within a changed package, each resource is reported as one of:

Resource lineMeaning
+ added or - removedThe resource exists on one side only
~ modifiedThe content differs. Text output shows each differing path with a unified diff of the values.
~ via dependencyOnly the identity hash changed, because a dependency of this resource changed. The content is the same. Text output marks these “via dependency”; JSON and Markdown call them dep-affected.

Two more sections follow the packages:

  • Cluster-level resources groups resources that belong to no package, such as namespaces and custom resource definitions. Text output labels the group cluster-level resources; JSON uses the key _cluster.
  • Activation shows the old and new activation record names when they differ. A build whose content matches the old side keeps the same record name, so this line appears only alongside other changes.

When a stateful resource appears to have moved between packages or names, the report adds a “Stateful migrations detected” section. It is informational; Kix does not migrate data.

When the deployed build’s manifests are on this machine, each field change is labelled intended (the deployed and new builds differ here), reverts_drift (the live object differs from the deployed build and the deploy puts the deployed value back), or drift_kept (the live object differs, and the deploy leaves the object alone because its identity hash did not change). kix plan defines them in full. With --from <build> every change is intended. Without the deployed build nothing is labelled, and the drift section is not checked.

A Drift section lists each field where the live object differs from the deployed build, with its live and deployed values, the field managers that own it live, and whether the deploy reverts it. kix deploy --reconcile reverts the fields the deploy would otherwise leave alone.

A summary line, one block per namespace with each package’s status and resource lines, the cluster-level group, the activation line, then either the migration section or No differences found. A field change that puts back a drifted field is tagged (reverts drift), and one the deploy leaves alone is tagged (drift; deploy leaves this object alone). The Drift section follows when any field drifted. When there are changes, the output ends with a note that kix diff cannot see admission webhooks, field ownership conflicts, or what the deploy refuses. Colour follows --no-color and the NO_COLOR environment variable.

The Plan document kix plan prints, with JSON Schema cli/kix-cli/schema/plan.schema.json. That page describes the top-level keys, the checked-section shape, and the not_checked reasons. For kix diff:

KeyValue from kix diff
made_bylive_read against the cluster or a snapshot; builds with --from <build>
buildThe new build, as for kix plan
deployedThe old side’s Activation record: the cluster’s current record, the --from build’s record, or the record in a --from snapshot. null when there is none
flagsnull: a diff plans no deploy
migrationsChecked: the stateful migrations the comparison found
driftChecked against the live cluster when the deployed build is on this machine. Otherwise not_checked: deployed_build_unavailable for a snapshot or a missing deployed build, builds_only with --from <build>
conflicts, admission, prunenot_checked, reason read_only (or builds_only with --from <build>)
readinessnot_checked, reason not_previewable
not_checkedAlways empty

packages holds the package-grouped changes:

KeyContent
packages.summaryCounts of added, removed, changed, and unchanged packages
packages.added, packages.removedArrays of { key, version, resources, kept_resources, manifests }. resources counts every resource in the package; kept_resources lists the resources that stay on the cluster: Namespaces, CustomResourceDefinitions, and objects another Kix cluster’s current build contains (always empty on added). manifests is present at --detail manifests: the new manifest of each resource of an added package, or the old manifest of each resource a removed package deletes
packages.changedArray of package changes: key, old_version, new_version, added_resources, removed_resources, kept_resources, modified_resources, dep_affected_resources, and at --detail manifests manifests. A resource that left the build and stays on the cluster is listed in kept_resources, not removed_resources
packages.changed[].manifestsObject keyed by resource, each { old, new }, for every modified, added, and removed resource. A side is absent when the resource does not exist there
packages.changed[].modified_resourcesArray of { resource, diff_count, changes }. changes is present at --detail fields and --detail manifests. Each entry of changes is { path, change, old, new, labels }; change is added, removed, or changed, old or new is absent when that side has no value, and labels is absent when the deployed build is not on this machine
packages.cluster_levelThe same shape as one changed package, with key _cluster, or null
packages.activation{ old, new } record names, or null when unchanged
packages.unchangedNumber of unchanged packages

Stateful migrations are under the top-level migrations section, each { slot, source, target, reason }.

A Secret value, or a hash of one, reads (secret, not shown), or (secret, changed, not shown) on the new side when the value changes. A field value larger than 16 KiB, or a manifest larger than 256 KiB, reads (N bytes, not shown).

Resource identifiers have the form Kind/name@namespace. Package keys have the form namespace/name.

GitHub-flavoured Markdown: a plan summary, a <details> block per changed package, the cluster-level and activation sections, and the migration section when present.

CodeMeaning
0No differences
2At least one difference, or a stateful migration was detected
1Evaluation, build, or cluster error

The status is the same for every output format, so a CI job can write Markdown to a file and still branch on the result.