Skip to content
kix /docs
Install the CLI

How-to guide Adopt existing resources

Take ownership of kubectl-managed resources

Move an existing kubectl-managed workload under Kix without changing immutable selectors.

Use a one-time server-side apply takeover when an existing resource was applied with kubectl and should now be managed by Kix.

This guide uses a Deployment as the example. Back up the live resource first, and perform the takeover during a normal change window.

Save the resource before changing its ownership:

❱ kubectl get deployment legacy-web -n takeover -o yaml --show-managed-fields > legacy-web.before.yaml

Keep any fields your workload relies on, especially immutable selectors, Service selectors, volume claims, and identity-bearing names. The managedFields section also shows which manager owns each part of the object.

Configure the package instance with the live Deployment’s selector and the state you want Kix to manage:

how-to/adoption/takeover-cluster.nix
instances.takeover.legacy-web = {
package = webPackage;
# Keep the live Deployment's immutable selector.
selectorLabels = {
app = "legacy-web";
tier = "frontend";
};
config = {
replicas = 2;
environment = "production";
message = "This workload is managed by Kix";
};
};

View source on GitHub ↗

The example retains the existing app and tier selector. Its desired replica count is two, while the kubectl-managed fixture has one replica.

Check the cluster before contacting Kubernetes:

kix-examples/
❱ kix check how-to-adoption-takeover
 TOOL       RESULT  DETAILS                      
 eval       pass    11 manifests evaluated       
 scorecard  pass    0 errors, 6 warnings, 1 info

Inspect the rendered Deployment:

kix-examples/ output excerpt
❱ kix build how-to-adoption-takeover --output json
{
  "apiVersion": "apps/v1",
  "kind": "Deployment",
  "metadata": {
    "name": "legacy-web",
    "namespace": "takeover"
  },
  "spec": {
    "replicas": 2,
    "selector": {
      "matchLabels": {
        "app": "legacy-web",
        "tier": "frontend"
      }
    },
    "template": {
      "metadata": {
        "labels": {
          "app": "legacy-web",
          "app.kubernetes.io/name": "legacy-web",
          "tier": "frontend"
        }
      },
      "spec": {
        "containers": [
          {
            "name": "nginx",
            "image": "docker.io/library/nginx:1.27-alpine"
          }
        ]
      }
    }
  }
}

Compare it with legacy-web.before.yaml. Do not continue if the rendered resource changes an immutable selector or omits a field the workload still needs. Correct the package configuration first.

Run an API-server dry run without taking ownership:

kix-examples/ output excerpt live capture
❱ kix deploy how-to-adoption-takeover --dry-run -y
Show outputHide output · 4 lines
⚠ Deployment/legacy-web@takeover cannot update Deployment/legacy-web@takeover: another tool owns fields that Kix is trying to change. Review the fields listed below. If Kix should manage them, rerun with --force-conflicts to take ownership; if the other tool is meant to own them, declare them on the resource with `ownership.cede`. Kubernetes reported: ApiError: Apply failed with 1 conflict: conflict with "kubectl": .spec.replicas: Conflict (ErrorResponse { status: "Failure", message: "Apply failed with 1 conflict: conflict with \"kubectl\": .spec.replicas", reason: "Conflict", code: 409 })
✗ halt-on-first-failure: cannot update Deployment/legacy-web@takeover: another tool owns fields that Kix is trying to change. Review the fields listed below. If Kix should manage them, rerun with --force-conflicts to take ownership; if the other tool is meant to own them, declare them on the resource with `ownership.cede`. Kubernetes reported: ApiError: Apply failed with 1 conflict: conflict with "kubectl": .spec.replicas: Conflict (ErrorResponse { status: "Failure", message: "Apply failed with 1 conflict: conflict with \"kubectl\": .spec.replicas", reason: "Conflict", code: 409 })
Dry run complete: 3 created, 2 configured, 0 unchanged, 1 failed, 4 cancelled
(exit code: 1)

Kix discovers live state through its management labels, so kix diff does not compare a resource that Kix has not managed before. The deployment dry run sends the intended manifests to the API server without persisting them.

The API server rejects a change when another field manager owns a field Kix is trying to change. The error identifies the resource and points to --force-conflicts.

The dry run carries on past a conflict, so the plan lists every conflicting resource with the manager that owns each field. Review every reported field against the saved resource before using --force-conflicts.

When the diff and conflicting fields are expected, deploy with --force-conflicts:

kix-examples/ output excerpt
❱ kix deploy how-to-adoption-takeover --force-conflicts -y
~ Deployment/legacy-web@takeover configured
✔ Deployment/legacy-web@takeover ready
• activation 'how-to-adoption-takeover-3gdjlxrhh7h4' → Active
Deploy complete: 6 created, 4 configured, 0 unchanged, 0 failed

The flag tells server-side apply to transfer conflicting fields in the Kix manifest to the kix field manager. Fields that Kix does not specify remain with their existing managers.

Check the result and save its field ownership:

kix-examples/
❱ kix status how-to-adoption-takeover
❱ kubectl get deployment legacy-web -n takeover -o yaml --show-managed-fields > legacy-web.after.yaml

Use ordinary kix deploy how-to-adoption-takeover commands after the takeover. Do not leave --force-conflicts enabled for routine deployments, because a later conflict may represent another controller making an intentional change.