Explanation
Server-side apply and drift
In a live Kubernetes cluster, your deployment tool is rarely the only thing modifying objects. HorizontalPodAutoscalers adjust spec.replicas, operators inject webhook conversion blocks into CRDs, and the Kubernetes API server fills in default values on every pod spec. Kix uses Kubernetes Server-Side Apply to track exactly which fields Kix owns, so it can detect real configuration drift without fighting in-cluster controllers or flagging harmless API server defaults.
Per-field ownership in Server-Side Apply
Section titled “Per-field ownership in Server-Side Apply”When kix deploy applies a manifest, it uses Kubernetes Server-Side Apply with the field manager name kix. Instead of storing a giant “last-applied-configuration” JSON blob in an annotation, the Kubernetes API server records which manager owns each individual field inside metadata.managedFields.
Because ownership is tracked per field rather than per object, Kix and an in-cluster controller can safely co-own the same Deployment:
- Kix can own the Deployment’s container image, environment variables, and resource requests.
- A HorizontalPodAutoscaler can own
spec.replicason that same Deployment. - Fields that Kix never included in its manifest (such as
statusor API server defaults) stay outside Kix’s owned field set.
When Kix tries to change a field that another manager currently owns, the Kubernetes API server returns a field ownership conflict instead of silently overwriting the value. That conflict is a useful safety check: it tells you that your Nix manifest and a live controller (or a manual kubectl edit) are both trying to control the same setting.
Two ways to resolve an ownership conflict
Section titled “Two ways to resolve an ownership conflict”How you resolve a conflict depends on who should control the field going forward:
- Kix should own the field: If someone ran a one-off
kubectl editor you are migrating a resource from Helm to Kix, use--force-conflictsonkix deployfor that transition. A forced Server-Side Apply transfers ownership of the conflicting fields tokixand writes the values from your build. Avoid leaving--force-conflictsenabled on every routine deploy, because it would also steamroll legitimate conflicts from controllers. - A controller should own the field (
ownership.cede): Sometimes a resource must include an initial value when it is first created, even though an in-cluster controller will take over that field afterward (for example,spec.replicason an autoscaled workload, or webhookspec.conversionfields on a CRD). In those cases, the package declares the field path underownership.cedeonscope.mkResource. Kix sends the field when creating the object, and if a later apply conflicts only on declared ceded paths, Kix automatically retries without those fields so the controller keeps ownership.
Making ownership.cede an explicit list of field paths keeps conflicts honest: expected controller takeovers happen smoothly, while an unexpected conflict on any other field still stops the deployment and asks you to decide.
Drift detection uses the same ownership boundary
Section titled “Drift detection uses the same ownership boundary”Most diff and drift tools compare your desired YAML against the entire live object returned by the cluster, which creates constant noise whenever controllers update status fields or the API server adds default fields.
Kix uses the kix field-ownership entries in metadata.managedFields across both kix drift and kix diff:
- Right after
kix deployapplies a resource, Kix extracts the live fields owned bykix, hashes them, and writes that digest to thekix.run/applied-hashannotation. - When you run
kix drift, Kix reads the live object, selects the fields owned bykix, hashes their current values, and compares them tokix.run/applied-hash. If a controller updatesstatus, or if a field was cleanly ceded viaownership.cede, Kix ignores it. If someone changes a Kix-owned field out-of-band (even through a subresource likekubectl scale),kix driftflags the exact field that changed. - When you run
kix diffagainst a live cluster, Kix compares the desired build against the cluster’s state: resources whosekix.run/identity-hashmatches the build are treated as unchanged by the build, changed resources are compared against theirkix-owned projection frommetadata.managedFields, and a separate drift section checkskix.run/applied-hashandmetadata.managedFieldsto report out-of-band edits and the field manager that made them.
Why deploy inputs are committed
Section titled “Why deploy inputs are committed”Just as Kix avoids hidden state in the cluster, it avoids hidden inputs at deploy time. Every input that affects a compiled manifest, including frequently updated values like container image digests, lives in a committed file in your repository and is read during evaluation:
- Content-addressed identity hashes: During evaluation, Kix computes each resource’s
kix.run/identity-hashfrom its serialized JSON in Kix’s local store, andkix deployuses those hashes (recorded in the cluster’sActivationresource) to determine which objects changed since the last deployment. Reading all inputs during evaluation guarantees that the hashes in your build match the manifests applied to the cluster. - Committed JSON image pins (
pins.json): To make automated image updates easy without letting a CI bot rewrite Nix code, Kix stores image versions in a structuredpins.jsonfile managed bykix pin. Runningkix pin set <key> --tag <tag>queries the container registry, records the immutablesha256digest alongside the tag, and writes the update topins.json. - Verifiable deploy receipts (
status.receipt): Whenkix deployfinishes applying anActivation, it records a deploy receipt on theActivationstatus subresource containing the activation hash, timestamp, Git commit, locked flake inputs, and container image digests. Because every input was committed before evaluation,kix compliance attest --from-clustercan generate a verifiable SLSA provenance statement directly from the live cluster receipt.