Skip to content
kix /docs
Install the CLI

Reference CLI

deploy / apply

Build a cluster, show the plan against what is running, and apply it in dependency order, waiting for each resource to become ready.

kix deploy builds a cluster, compares the build with the activation the cluster runs now, checks the plan with a server-side dry run, shows it, asks for confirmation, and applies the changed resources with server-side apply. Each resource starts as soon as the resources it depends on are ready. kix apply is an alias for the same command.

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

kix deploy <CLUSTER> [-y] [--dry-run] [--plan <FILE>] [--prune | --prune-mode default|no]
[--timeout <DURATION>] [--on-error stop|continue]
[--reconcile] [--force-conflicts] [--accept-migrations]
[--current-activation] [--operator]
[--log-format pretty|line|json] [--tui [--group-by-ns] [--fold-height <ROWS>]]
[--flake <FLAKE>] [--context <CONTEXT>]
Argument or flagMeaning
<CLUSTER>Cluster name to build from the flake.
-y, --yesSkip the confirmation prompt. Required when stdin is not a terminal, unless --dry-run is set.
--dry-runRun kix plan with the same planning flags and stop. Nothing changes. The exit status and -o handling are kix plan’s, and --yes, --timeout, --on-error, --log-format, and --tui have no effect.
--plan <FILE>Deploy only if the plan made now still matches a reviewed one. See Deploy a reviewed plan. With --dry-run, check the plan and exit without deploying. Not with --current-activation or --operator.
--pruneDelete orphans: resources the previous activation had and this build does not, including orphans earlier deploys kept. Without it, orphans are listed and kept, and the new Activation records where they came from so a later kix deploy --prune or kix gc deletes them.
--prune-mode <MODE>default prunes according to --prune. no never prunes in this run, even with --prune; use kix gc later.
--timeout <DURATION>How long to wait for each resource to become ready: 5m, 300s, or a bare number of seconds. Default 180s.
--on-error <MODE>stop (default) stops before applying anything when the plan shows a refused write, and halts the run at the first failure and cancels what has not started. continue applies the plan even when it shows refused writes, cancels only the dependents of a failed resource, and carries on with the rest. Either way, a run in which any resource failed exits 1.
--reconcileRe-apply every resource, including those whose identity hash matches the running activation. Use it to put back fields that were changed on the cluster.
--force-conflictsTake ownership of fields another field manager owns. Without it, a conflict is a refused write in the plan, naming the fields and their current owner, and under --on-error stop the deploy stops before applying anything.
--accept-migrationsProceed when the plan removes and adds stateful resources in the same slot. Without it such a deploy stops, even with --yes. Kix does not copy data.
--current-activationTake the previous activation from the local cache instead of the cluster. The plan is computed offline, so it cannot see deploys that stopped part way or orphans that earlier deploys kept; the new Activation lists the builds those orphans came from again, so a later plain deploy can prune them. With --dry-run nothing contacts the cluster. Without --dry-run, the deploy refuses before applying anything when the cached build is not the Active build of the cluster in the current context: the cache is keyed by cluster name only, so another context’s cluster of the same name can have written it.
--operatorHand the build to the Kix operator instead of applying it from this machine. See Operator mode.
--log-format <FORMAT>pretty (default): interactive progress, coloured when stdout is a terminal; falls back to line when stdin is not a terminal. line: one plain line per resource outcome. json: JSON deploy events, one per line, ending with a summary object. The plan is still plain text on the same stream.
--tuiDraw a live dependency tree while deploying. Ignored when stdin is not a terminal.
--group-by-nsWith --tui, group the tree by namespace instead of package. Execution order does not change.
--fold-height <ROWS>With --tui, cap the live view at this many rows. Default: half the terminal height, between 10 and 40.
--flake, --context, --override-inputSee Global flags. -o has an effect only with --dry-run, and --no-cache has none; use --log-format to choose the deploy’s output.
  1. Builds the cluster.

  2. Reads the running activation from the cluster (or the local cache with --current-activation). If that build is no longer in the local Nix store, Kix rebuilds the previous state from the live resources whose kix.run/activations annotation names the running Activation record, a failed one of the same cluster, or a build listed under status.orphansKeptFrom. Resources of other Kix clusters on the same Kubernetes cluster never enter the previous state. Neither do this cluster’s resources that name only older builds; the plan says how many, and kix gc --hard finds them.

  3. Refuses to start if another deploy of the same cluster is in progress. Resources left by an earlier deploy that stopped part way are folded into the plan: they are re-applied or reported as orphans.

  4. Sends every write and prune delete of the plan as a server-side dry run, the way kix plan does, and prints the result: the changed resources (with field changes under -v), the writes the cluster would refuse, prune fates, drift, and the objects the dry run could not check. The dry run is skipped when there is nothing to apply or prune. It also prints warnings for node labels that DoNotSchedule spread or affinity rules need but no schedulable node carries, and whether the working tree has uncommitted changes. Under --on-error stop, a plan with a refused write or a refused prune delete stops the deploy here, before anything is applied. With --current-activation the plan is not sent to the cluster.

  5. Asks for confirmation, then creates the Activation record and applies. Unchanged resources are skipped. A deploy with nothing to change exits without applying. Before the record is created, the run stops if the cluster serves a resource’s kind as namespaced and its manifest has no metadata.namespace, naming each such resource. Cross-resource check 18 refuses most of these at evaluation; this covers custom kinds whose CustomResourceDefinition is not in the build.

  6. Prunes orphans when asked. Namespaces and CustomResourceDefinitions are reported as kept and never deleted, with or without --prune. In --log-format json, the prune_start event lists them under kept, apart from the resources that prune may delete.

    An orphan that another Kix cluster’s current build still contains is not pruned. The plan lists it as kept, then as kept <resource>, still deployed by Kix cluster <name>, read from the Activation records the live resource names in kix.run/activations. kix rollback --prune and soft kix gc follow the same rule. With --current-activation the plan has not read the cluster yet, so it says the check comes once the deploy connects. The prune checks each orphan again right before deleting it, and leaves one that another cluster started deploying, or that changed, since the plan (prune_kept in --log-format json).

    Orphans a deploy keeps stay pruneable. The Activation it finalizes lists the builds they came from under status.orphansKeptFrom, and the next deploy’s plan includes their orphans again, so kix deploy --prune or soft kix gc deletes them. A deploy that deletes them all removes those builds from the list. If this machine does not have one of those builds, the plan says so and keeps it on the list for a machine that does; kix gc --hard also removes its orphans. While a build is listed, the machine that recorded it keeps a Nix GC root for it in ~/.cache/kix/activations/, so nix-collect-garbage does not remove it. When two deploys are planned against the same Active record, the one that finishes second writes a list computed without the first one’s builds, and only kix gc --hard then finds the orphans the first one kept.

    To avoid relying on this list, pass --prune to every deploy and rollback. A run that deletes every orphan adds nothing to the list, so it stays empty; a failed delete still adds the build it came from. No configuration setting makes --prune the default, so put the flag in the command your scripts or CI run. In operator mode the operator deletes orphans on every deploy.

  7. Marks the Activation Active, or Degraded or Failed when resources failed.

Ctrl-C cancels the run and marks the record Failed. A run that makes no progress for 600 seconds is cancelled; set KIX_STALL_TIMEOUT to a number of seconds to change that, or 0 to disable it. Retries and request timeouts are covered in API server retries and request limits.

Set KIX_NO_INTERACTIVE to any value to treat the terminal as non-interactive.

Save the plan a reviewer approves, then deploy with it:

Terminal window
kix plan prod --output json > plan.json # reviewed and approved
kix deploy prod --plan plan.json --yes

The deploy builds and plans again with the planning flags recorded in the plan, and applies nothing if the new plan differs from the reviewed one on any of these:

ComparedWhat differs
ClusterThe plan is for another Kix cluster.
New buildThe build’s identity hash. A store path that moved, with the same manifests, still matches.
Deployed buildThe identity hash of the build the cluster runs: another deploy landed after the review.
Planning flags--prune, --prune-mode, --reconcile, --force-conflicts, or --accept-migrations. A flag given on the command line that the plan was made without is an error before the build.
RefusalsA conflict, by object, or an admission refusal, by object and reason, that the reviewed plan did not have.
Reverted driftA drifted field, by object and path, that the deploy would put back and the reviewed plan did not list.
Prune deletesAn orphan the deploy would delete that the reviewed plan did not list.
Objects not checkedAn object the dry run checked for the reviewed plan and cannot check now, for example because a webhook it calls lost dry-run support. Its refusals and drift would not show.

What the reviewed plan had and the new one does not, such as a conflict someone resolved, does not refuse the deploy. Messages, live values, resourceVersions, and order are not compared.

The planning flags come from the plan: a flag it has and the command line omits still applies. A plan whose sections do not match how it says it was made, for example a kix plan document with prune marked not checked, was edited after it was written and is refused. The deploy prints the file’s SHA-256 so an approval system can check it used the file it approved.

A refusal lists what differs and exits 4. kix deploy --plan plan.json --dry-run makes the same comparison without deploying. It exits 4 when the plan no longer matches, 3 when it matches but the deploy would stop on a refused write or a migration not accepted, and 0 otherwise. With --output json it prints { plan_sha256, matches, mismatches, skipped, kix_differs }.

A plan saved from kix diff against the cluster can be used too, when the deployed build was available to it, so its drift section is checked. A diff checks no refusals, prune deletes, or objects a dry run cannot reach, so those are not compared and the output says so; the deploy still makes its own dry run first. A deploy from a diff plan refuses --prune, --force-conflicts, --reconcile, and --accept-migrations, since the diff shows no reviewer what they would do. Refused outright: a plan that compared two builds without reading the cluster (made_by: builds), and a kix diff of a snapshot or export, or one made without the deployed build, since neither shows drift.

The checks above catch an edited plan only when its sections contradict how it says it was made. Adding items to a list, or marking a drift section not checked, makes the comparison weaker without making the file inconsistent, so an approval system should check the SHA-256 the deploy prints against the file it approved.

When the plan was made by a Kix binary at another Nix store path, or of another version, the deploy prints a note and does not refuse. Two binaries built outside the store with the same version print no note.

❱ kix deploy demo --dry-run
❱ kix deploy demo --yes --prune --timeout 10m
❱ kix apply demo --yes --log-format json

--log-format json does not currently produce a standalone NDJSON document: the plan remains plain text. A consumer must separate JSON event lines from the surrounding text. When the run reaches the deploy engine, the last JSON line is the summary object, with succeeded (which matches the exit status), dry_run, and the created, configured, unchanged, failed, cancelled, and pruned counts. A run that stops before the engine starts, such as a refused plan or a failed connection, writes no summary line; its error goes to stderr and the exit status is non-zero.

CodeMeaning
0Deploy complete, nothing to apply, or the prompt was declined
1A resource failed, under either --on-error mode, or the run was cancelled; the plan showed a refused write under --on-error stop; a stateful migration was detected without --accept-migrations; confirmation was needed on a non-interactive terminal; or a build, evaluation or cluster error

With --dry-run the exit status is kix plan’s: 0 when nothing would change, 2 when something would, and 3 when the deploy would stop.