Skip to content
kix /docs
Install the CLI

How-to guide Adopt existing resources

Handle stateful migration prompts safely

Review a detected stateful replacement, migrate its data separately, and acknowledge the deployment gate.

Kix stops a deployment when one stateful resource replaces another in the same package slot. Treat this as a data-migration gate, not as an ordinary confirmation prompt.

The migration gate applies only when one stateful resource replaces another. If a stateful resource is removed without a replacement, Kix treats it as an ordinary orphan. Running with --prune deletes it without a migration prompt. See Deploy with dry-run, timeouts, pruning, and log/TUI output modes to see how the plan reports orphans before deletion.

The example changes the data/volume package from PersistentVolumeClaim/media-v1 at 5Gi to PersistentVolumeClaim/media-v2 at 10Gi.

Review the pair before contacting the cluster

Section titled “Review the pair before contacting the cluster”

This example exports the current definition with its Kix provenance so it can stand in for the old side of an offline diff:

kix-examples/
❱ kix export how-to-stateful-migration --for audit --out stateful-before
Building cluster 'how-to-stateful-migration'...
  Evaluating cluster 'how-to-stateful-migration'...
  Building 'how-to-stateful-migration'...
  copying path '/nix/store/kppfbp4x7mhfz1q5zswavxxxq71v2f7c-gnu-config-2024-01-01' from 'https://cache.nixos.org'...
  copying path '/nix/store/wrxyd3k2f4bmh52pr5rpdjxxsm5r2qxm-gcc-15.2.0-libgcc' from 'https://cache.nixos.org'...
  copying path '/nix/store/xx0z77494lfxr8qjwpck246fry05n3nm-xgcc-15.2.0-libgcc' from 'https://cache.nixos.org'...
  copying path '/nix/store/i4gg1f526vl5psg5nqniflj4v77vc1kd-libunistring-1.4.2' from 'https://cache.nixos.org'...
  copying path '/nix/store/9vv51km72lpngs6aixxplrr3c88q4c3c-update-autotools-gnu-config-scripts-hook' from 'https://cache.nixos.org'...
  copying path '/nix/store/sgswwrxkhdlfskklqp4gsbi2cskfg07c-libidn2-2.3.8' from 'https://cache.nixos.org'...
  copying path '/nix/store/fjkx1l5cnskzrqacf08z7i8z17256w0j-glibc-2.42-61' from 'https://cache.nixos.org'...
  copying path '/nix/store/2amncb4zvr32gm5d2i8m6gz29c02cn61-bzip2-1.0.8' from 'https://cache.nixos.org'...
  copying path '/nix/store/sl325r3s6d4gl9lmr2hwg2wgp2m0qs7c-attr-2.5.2' from 'https://cache.nixos.org'...
  copying path '/nix/store/i27rhb3nr65rkrwz36bchkwmav6ggsmn-bash-5.3p9' from 'https://cache.nixos.org'...
  copying path '/nix/store/jw7ki3s7krg2ddq00hb8563bd1i4ddrg-ed-1.22.5' from 'https://cache.nixos.org'...
  copying path '/nix/store/lakv43kv98sl6h0ba6wnyg513mcq61vl-gawk-5.4.0' from 'https://cache.nixos.org'...
  copying path '/nix/store/si4q3zks5mn5jhzzyri9hhd3cv789vlm-gcc-15.2.0-lib' from 'https://cache.nixos.org'...
  copying path '/nix/store/yvrwcs1a45rj8142n0l2w9q9s6akamjr-gnumake-4.4.1' from 'https://cache.nixos.org'...
  copying path '/nix/store/af4a8i43kc2ss4rnmf0swkk2mprsw6xq-gnused-4.9' from 'https://cache.nixos.org'...
  copying path '/nix/store/khhzkpj9169ydnyg7jjrjx7s5ygdkcas-pcre2-10.46' from 'https://cache.nixos.org'...
  copying path '/nix/store/hmslvsxvs2ijb7iw5krdckai2im6vp2y-xz-5.8.3' from 'https://cache.nixos.org'...
  copying path '/nix/store/ixhlv41i2wpl84xgjcks061dz4yssbg3-zlib-1.3.2' from 'https://cache.nixos.org'...
  copying path '/nix/store/zj6r42syyswkhrr174bzppj3n7xhq936-bzip2-1.0.8-bin' from 'https://cache.nixos.org'...
  copying path '/nix/store/bifwnk7bvspj2mqc1w7dcydz8lkvfy1q-acl-2.3.2' from 'https://cache.nixos.org'...
  copying path '/nix/store/zj7mxwji29zvj9vl70iip7gw4h6ljfam-patch-2.8' from 'https://cache.nixos.org'...
  copying path '/nix/store/iscmg3ivhx7z67dz14lrg7p77gnsa4dw-file-5.45' from 'https://cache.nixos.org'...
  copying path '/nix/store/rnvb7bvp53v2dw7pcwh9xb89x5z4rjib-gnutar-1.35' from 'https://cache.nixos.org'...
  copying path '/nix/store/9lhr1c3l9qzv8pzp3idmii1nwvxxjys3-gzip-1.14' from 'https://cache.nixos.org'...
  copying path '/nix/store/2nm5c858fh52s6mhcffm07s3biaxys44-xz-5.8.3-bin' from 'https://cache.nixos.org'...
  copying path '/nix/store/wf7lr2hf43546jc5kwqh3dbxnpcnw1mn-gnugrep-3.12' from 'https://cache.nixos.org'...
  copying path '/nix/store/5yypk94lvsbvskjz5xyav09j5ydxm8za-gmp-with-cxx-6.3.0' from 'https://cache.nixos.org'...
  copying path '/nix/store/66lksljlljdd5ppgvfk8g89y8xgqcxd7-patchelf-0.15.2' from 'https://cache.nixos.org'...
  copying path '/nix/store/jjxngswsb214vb58qx485jhmilf0kxxy-coreutils-9.10' from 'https://cache.nixos.org'...
  copying path '/nix/store/hx084k7pgz4n0vgkvil9gbcnl8y6p1xf-diffutils-3.12' from 'https://cache.nixos.org'...
  copying path '/nix/store/vhsirn9m1ifmnw5g1qczzhvqkx6lw1if-findutils-4.10.0' from 'https://cache.nixos.org'...
  copying path '/nix/store/74ma22y9kdxjvvrrgz3q1dqnm8r4d99d-stdenv-linux' from 'https://cache.nixos.org'...
  building '/nix/store/27xfzq32nh8d28syf9z37cyd6ci4k933-k8s-activation-how-to-stateful-migration.drv'...
  Reading package index...
  Loading store graph...
  Reading 3 packages...
  Computing cross-package dependencies...

  Exported 9 resources to stateful-before (audit (kix provenance preserved))

Compare the replacement definition with that saved state:

kix-examples/ output excerpt offline capture
❱ kix diff how-to-stateful-migration --from stateful-before
Show outputHide output · 8 lines
+ PersistentVolumeClaim/media-v2@data
- PersistentVolumeClaim/media-v1@data
Stateful migrations detected (1):
~ data/volume: PersistentVolumeClaim/media-v1 -> PersistentVolumeClaim/media-v2 (size changed: 5Gi -> 10Gi)
source: PersistentVolumeClaim/data/media-v1
target: PersistentVolumeClaim/data/media-v2
Data migration is not automated. Review the source and target resources above and migrate the data before deploying with --accept-migrations.
(exit code: 2)

Check the logical slot, source, target, and reason. Kix reports this pair because both resources have lifecycle = "stateful", occupy the same data/volume slot, and have different Kubernetes identities.

Choose a procedure supported by the workload and storage provider. Before changing the deployment:

  1. make a recoverable backup or snapshot;
  2. stop or quiesce writers;
  3. copy or restore the data into the target;
  4. verify the target data independently;
  5. record how to return consumers to the source.

Kix detects the replacement but does not perform these steps. The exact copy and verification commands depend on the application, volume provider, and access mode, so they should live in the service’s migration runbook.

An ordinary deployment refuses the pair even when --yes is present:

kix-examples/ live capture
❱ kix deploy how-to-stateful-migration -y
Show outputHide output · 13 lines
Building cluster 'how-to-stateful-migration'...
Cluster how-to-stateful-migration: 8 manifests
Connecting to cluster...
Active activation: how-to-stateful-migration-w2a1afvarsca (w2a1afva...)

Stateful migrations detected (1):
  ~ data/volume: PersistentVolumeClaim/media-v1 -> PersistentVolumeClaim/media-v2 (size changed: 5Gi -> 10Gi)
      source: PersistentVolumeClaim/data/media-v1
      target: PersistentVolumeClaim/data/media-v2

Deployment stopped: these changes move workloads to different storage. Migrate the data before continuing.
Data migration is not automated. Review the source and target resources above, migrate the data yourself, then rerun with --accept-migrations.
(exit code: 1)

--yes skips the general deploy confirmation. It does not acknowledge data loss risk.

After the data has been migrated and verified, repeat the API-server dry run with the separate acknowledgement:

kix-examples/ live capture
❱ kix deploy how-to-stateful-migration --accept-migrations --dry-run -y
Show outputHide output · 33 lines
Building cluster 'how-to-stateful-migration'...
Cluster how-to-stateful-migration: 8 manifests
Connecting to cluster...
Active activation: how-to-stateful-migration-w2a1afvarsca (w2a1afva...)

Stateful migrations detected (1):
  ~ data/volume: PersistentVolumeClaim/media-v1 -> PersistentVolumeClaim/media-v2 (size changed: 5Gi -> 10Gi)
      source: PersistentVolumeClaim/data/media-v1
      target: PersistentVolumeClaim/data/media-v2

--accept-migrations set: continuing deployment. Kix will not copy data. Workloads using the new storage will see only the data already present there.

  data
    ~ volume 1.0.0 (1 changed, 1 added, 1 removed)

  Plan: 1 updated, 1 unchanged
  Resources: 3 real content, 0 dep-affected
  1 orphaned (kept; `kix deploy --prune` or `kix gc` deletes them)
    - PersistentVolumeClaim/media-v1@data

Dry run. No changes will be applied.
plan: 8 nodes
  + PersistentVolumeClaim/media-v2@data created
  ✔ PersistentVolumeClaim/media-v2@data ready
  ~ PackageInstance/volume@data configured
  ✔ PackageInstance/volume@data ready
  + Activation/how-to-stateful-migration-a620ys0axp58 created
  ✔ Activation/how-to-stateful-migration-a620ys0axp58 ready

prune: 1 orphaned resources kept (warn-only; `kix deploy --prune` or `kix gc` deletes them)
  - PersistentVolumeClaim/media-v1@data

Dry run complete: 2 created, 1 configured, 5 unchanged, 0 failed

--accept-migrations only allows the deployment to continue. It does not copy data or verify that the target contains it. The warning about an empty target is literal when the out-of-band migration has not happened.

Review the rest of the deployment plan. Do not add --prune while the source PVC is still part of the rollback plan. Pruning has no separate safeguard for stateful resources. Once the source PVC becomes an orphan, Kix deletes it like any other orphan.

Run the acknowledged deployment without --dry-run:

kix-examples/
❱ kix deploy how-to-stateful-migration --accept-migrations

Confirm the application is using the target and validate its data before retiring the source. Remove the old PVC only after the recovery window and according to the storage provider’s reclaim policy.

See Preview changes with kix plan and kix diff for the ordinary diff workflow.