Skip to content
kix /docs
Install the CLI

Tutorial 05

Basic graph and deploy loop

Check, preview, deploy, and inspect a small cluster before adding dependencies.

The last tutorial created a cluster from scratch. This tutorial uses that same cluster to look more closely at the Kix loop:

  1. kix check validates the evaluated cluster
  2. kix build shows the rendered resources
  3. kix diff previews against live state
  4. kix deploy applies the checked result
  5. kix graph inspects what Kix knows

The goal is not to learn every graph detail yet. The goal is to see that Kix can evaluate a cluster before it touches Kubernetes.

  • Finish the first cluster from scratch tutorial.
  • Keep the 04-from-scratch cluster entry in flake.nix.
  • Make sure the new cluster file is known to Git. New files in a Git flake are invisible to Nix until they are added to the index:
kix-examples/
❱ git ls-files --error-unmatch tutorial-04/cluster.nix

If that command fails, add the file:

kix-examples/
❱ git add tutorial-04/cluster.nix

Run the Kix checks first:

kix-examples/
❱ kix check 04-from-scratch
 TOOL       RESULT  DETAILS                      
 eval       pass    11 manifests evaluated       
 scorecard  pass    0 errors, 1 warnings, 0 info

This evaluates the cluster and runs validation before anything is applied to Kubernetes. For this tiny cluster, the output should be short. The important part is the habit: check the evaluated cluster before deploying it.

If Kix fails here, fix the cluster file before moving on. A failed check means the deploy step should not run yet.

Now render the cluster:

kix-examples/ offline capture
❱ kix build 04-from-scratch
Show outputHide output · 311 lines
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  annotations:
    kix.run/identity-hash: gvrlspcfgh9a2qnsx5kqxw9f4l3x52jj
    kix.run/package: _cluster
    kix.run/package-namespace: _cluster
  labels:
    app.kubernetes.io/managed-by: kix
  name: activations.kix.run
spec:
  group: kix.run
  names:
    kind: Activation
    listKind: ActivationList
    plural: activations
    singular: activation
  scope: Cluster
  versions:
  - name: v1alpha1
    schema:
      openAPIV3Schema:
        properties:
          apiVersion:
            type: string
          kind:
            type: string
          metadata:
            type: object
          spec:
            type: object
            x-kubernetes-preserve-unknown-fields: true
          status:
            type: object
            x-kubernetes-preserve-unknown-fields: true
        type: object
    served: true
    storage: true
    subresources:
      status: {}
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  annotations:
    kix.run/identity-hash: pk4hih6jm4yj336rlaqk2qycz2k46xvs
    kix.run/package: _cluster
    kix.run/package-namespace: _cluster
  labels:
    app.kubernetes.io/managed-by: kix
  name: packageinstances.kix.run
spec:
  group: kix.run
  names:
    kind: PackageInstance
    listKind: PackageInstanceList
    plural: packageinstances
    singular: packageinstance
  scope: Namespaced
  versions:
  - name: v1alpha1
    schema:
      openAPIV3Schema:
        properties:
          apiVersion:
            type: string
          kind:
            type: string
          metadata:
            type: object
          spec:
            type: object
            x-kubernetes-preserve-unknown-fields: true
          status:
            type: object
            x-kubernetes-preserve-unknown-fields: true
        type: object
    served: true
    storage: true
    subresources:
      status: {}
---
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    kix.run/depends-on: h50qnfszgri8ml59sx8qy0sx3zbp79s9,qrh908pfdb2030zrxsp8018aiinicd2w
    kix.run/identity-hash: jsfzxfyhb8r36fnk07p227k7nhy4w3pg
    kix.run/package: hello-world
    kix.run/package-namespace: tutorial-04
  labels:
    app.kubernetes.io/instance: hello-world
    app.kubernetes.io/managed-by: kix
    app.kubernetes.io/name: hello-world
  name: hello-world
  namespace: tutorial-04
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/instance: hello-world
      app.kubernetes.io/name: hello-world
  template:
    metadata:
      labels:
        app.kubernetes.io/instance: hello-world
        app.kubernetes.io/name: hello-world
    spec:
      containers:
      - image: docker.io/nginxinc/nginx-unprivileged:1.30.5-alpine@sha256:ed04ec1ff34502c339ee5c3ae3f855442398edc1d05591e2b98981dcbbd20b1e
        imagePullPolicy: IfNotPresent
        livenessProbe:
          failureThreshold: 3
          httpGet:
            path: /healthz
            port: 8080
          periodSeconds: 10
        name: nginx
        ports:
        - containerPort: 8080
          name: http
          protocol: TCP
        readinessProbe:
          failureThreshold: 1
          httpGet:
            path: /healthz
            port: 8080
          periodSeconds: 5
        resources:
          limits:
            cpu: '100m'
            memory: '64Mi'
          requests:
            cpu: '50m'
            memory: '32Mi'
        securityContext:
          readOnlyRootFilesystem: true
          runAsNonRoot: true
        volumeMounts:
        - mountPath: /etc/nginx/conf.d
          name: config
          readOnly: true
        - mountPath: /tmp
          name: tmp
      terminationGracePeriodSeconds: 30
      volumes:
      - configMap:
          name: hello-world
        name: config
      - emptyDir: {}
        name: tmp
---
apiVersion: kix.run/v1alpha1
kind: Activation
metadata:
  annotations:
    kix.run/built-via: /nix/store/z72qlbhkzpn9mqrks7ab21g1paihnj0p-k8s-activation-04-from-scratch
    kix.run/depends-on: fxz6dabpxh1hwbjlkamf23jpqaa7jsca,jh345cnnffy1bnj4cz5x0hwq6ibxvkkr,r5sgwjk8w3m41l0xaj0s16xxsacd0nk6,requires:gvrlspcfgh9a2qnsx5kqxw9f4l3x52jj
    kix.run/identity-hash: g941gkm4kxlhpsygx6ndv9cc5vndsdx9
    kix.run/package: _cluster
    kix.run/package-namespace: _cluster
  labels:
    app.kubernetes.io/managed-by: kix
    kix.run/cluster: '04-from-scratch'
  name: '04-from-scratch-g941gkm4kxlh'
spec:
  instances:
    kube-system:
      platform-dns: platform-dns
      platform-storage: platform-storage
    tutorial-04:
      hello-world: hello-world
---
apiVersion: kix.run/v1alpha1
kind: PackageInstance
metadata:
  annotations:
    kix.run/depends-on: sj0fv5bs2hnh9zcqdh62ph34l2wm0b1m,requires:pk4hih6jm4yj336rlaqk2qycz2k46xvs
    kix.run/identity-hash: jh345cnnffy1bnj4cz5x0hwq6ibxvkkr
    kix.run/import-apiversion: apps/v1
    kix.run/import-fqdn: kube-dns.kube-system.svc.cluster.local
    kix.run/import-kind: Deployment
    kix.run/import-name: coredns
    kix.run/mode: import
    kix.run/package: _cluster
    kix.run/package-namespace: _cluster
  labels:
    app.kubernetes.io/instance: platform-dns
    app.kubernetes.io/managed-by: kix
  name: platform-dns
  namespace: kube-system
spec:
  instanceName: platform-dns
  mode: import
  namespaceName: kube-system
---
apiVersion: kix.run/v1alpha1
kind: PackageInstance
metadata:
  annotations:
    kix.run/depends-on: sj0fv5bs2hnh9zcqdh62ph34l2wm0b1m,requires:pk4hih6jm4yj336rlaqk2qycz2k46xvs
    kix.run/identity-hash: r5sgwjk8w3m41l0xaj0s16xxsacd0nk6
    kix.run/import-apiversion: storage.k8s.io/v1
    kix.run/import-kind: StorageClass
    kix.run/import-name: standard
    kix.run/mode: import
    kix.run/package: _cluster
    kix.run/package-namespace: _cluster
  labels:
    app.kubernetes.io/instance: platform-storage
    app.kubernetes.io/managed-by: kix
  name: platform-storage
  namespace: kube-system
spec:
  instanceName: platform-storage
  mode: import
  namespaceName: kube-system
---
apiVersion: kix.run/v1alpha1
kind: PackageInstance
metadata:
  annotations:
    kix.run/depends-on: l5if8rap4fvbpqsnrb970yzmmpzjl84r,qrh908pfdb2030zrxsp8018aiinicd2w,requires:pk4hih6jm4yj336rlaqk2qycz2k46xvs
    kix.run/identity-hash: fxz6dabpxh1hwbjlkamf23jpqaa7jsca
    kix.run/package: _cluster
    kix.run/package-namespace: _cluster
    kix.run/root: hello-world
  labels:
    app.kubernetes.io/instance: hello-world
    app.kubernetes.io/managed-by: kix
  name: hello-world
  namespace: tutorial-04
spec:
  instanceName: hello-world
  namespaceName: tutorial-04
  version: '1.30.5'
---
apiVersion: v1
data:
  default.conf: |
    server {
        listen 8080;
        location / {
            default_type text/plain;
            return 200 'Hello from my first Kix cluster!\n';
        }
        location /healthz {
            default_type text/plain;
            return 200 'ok\n';
        }
    }
kind: ConfigMap
metadata:
  annotations:
    kix.run/depends-on: qrh908pfdb2030zrxsp8018aiinicd2w
    kix.run/identity-hash: h50qnfszgri8ml59sx8qy0sx3zbp79s9
    kix.run/package: hello-world
    kix.run/package-namespace: tutorial-04
  labels:
    app.kubernetes.io/instance: hello-world
    app.kubernetes.io/managed-by: kix
  name: hello-world
  namespace: tutorial-04
---
apiVersion: v1
kind: Namespace
metadata:
  annotations:
    kix.run/identity-hash: sj0fv5bs2hnh9zcqdh62ph34l2wm0b1m
    kix.run/package: _cluster
    kix.run/package-namespace: _cluster
  labels:
    app.kubernetes.io/managed-by: kix
    kubernetes.io/metadata.name: kube-system
  name: kube-system
---
apiVersion: v1
kind: Namespace
metadata:
  annotations:
    kix.run/identity-hash: qrh908pfdb2030zrxsp8018aiinicd2w
    kix.run/package: _cluster
    kix.run/package-namespace: _cluster
  labels:
    app.kubernetes.io/managed-by: kix
    kubernetes.io/metadata.name: tutorial-04
  name: tutorial-04
---
apiVersion: v1
kind: Service
metadata:
  annotations:
    kix.run/depends-on: jsfzxfyhb8r36fnk07p227k7nhy4w3pg,qrh908pfdb2030zrxsp8018aiinicd2w
    kix.run/identity-hash: l5if8rap4fvbpqsnrb970yzmmpzjl84r
    kix.run/package: hello-world
    kix.run/package-namespace: tutorial-04
  labels:
    app.kubernetes.io/instance: hello-world
    app.kubernetes.io/managed-by: kix
  name: hello-world
  namespace: tutorial-04
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 8080
  selector:
    app.kubernetes.io/instance: hello-world
    app.kubernetes.io/name: hello-world
  type: ClusterIP

This prints the Kubernetes resources Kix built from your cluster definition. You should see familiar objects such as a Namespace, Deployment, and Service.

For now, use kix build as a window into what Kix evaluated. In normal use, you usually let kix deploy render and apply the checked result for you.

Ask Kix what is different from the live cluster:

kix-examples/ live capture
❱ kix diff 04-from-scratch
Show outputHide output · 6 lines
⠁ Fetching live cluster state...                                                Discovering API resources...
Fetching managed resources (60 resource types)...
Fetched 10 resources across 60 resource types
Skipped 7 objects that belong to Kix cluster 02-hello-world.
2 packages: 2 unchanged, 0 changed, 0 added, 0 removed
No differences found.

If your live cluster already matches the checked result, the diff should be empty or say there is nothing to change. If the hello-world cluster from the local setup tutorial is still deployed in the same kind cluster, Kix reports that it skipped its objects: kix diff compares only the Kix cluster you name.

Now make a visible edit in tutorial-04/cluster.nix:

tutorial-04/cluster.nix
config = {
message = "Preview this before deploy.";
};

Check that Git sees the edit:

kix-examples/
❱ git status --short

Because tutorial-04/cluster.nix is already tracked, Nix can see this dirty edit. You do not need to commit it.

Run the diff again:

kix-examples/ live capture
❱ kix diff 04-from-scratch
Show outputHide output · 27 lines
⠁ Fetching live cluster state...                                                Discovering API resources...
Fetching managed resources (60 resource types)...
Fetched 10 resources across 60 resource types
Skipped 7 objects that belong to Kix cluster 02-hello-world.
2 packages: 1 unchanged, 1 changed, 0 added, 0 removed

  tutorial-04
    ~ hello-world 1.27 (1 changed, 3 dep-affected)
      ~ ConfigMap/hello-world@tutorial-04
          ~ $.data.default.conf:
              --- old
              +++ new
              @@ -2,7 +2,7 @@
                   listen 8080;
                   location / {
                       default_type text/plain;
              -        return 200 'Hello from my first Kix cluster!\n';
              +        return 200 'Preview this before deploy.\n';
                   }
                   location /healthz {
                       default_type text/plain;
      ~ Deployment/hello-world@tutorial-04 (via dependency)
      ~ PackageInstance/hello-world@tutorial-04 (via dependency)
      ~ Service/hello-world@tutorial-04 (via dependency)

  Activation: 04-from-scratch-ll6g0k5f2kqm -> 04-from-scratch-6w9czkc51502
(exit code: 2)

This time Kix should show the change before it applies anything. That is the basic preview loop: edit locally, evaluate locally, inspect the change, then deploy.

Deploy the change:

kix-examples/ live capture
❱ kix deploy 04-from-scratch -y
Show outputHide output · 25 lines
Building cluster '04-from-scratch'...
Cluster 04-from-scratch: 10 manifests
Connecting to cluster...
Active activation: 04-from-scratch-ll6g0k5f2kqm (ll6g0k5f...)

  tutorial-04
    ~ hello-world 1.27 (1 changed, 3 dep-affected)

  Plan: 1 updated, 1 unchanged
  Resources: 1 real content, 3 dep-affected
plan: 10 nodes
  ~ ConfigMap/hello-world@tutorial-04 configured
  ✔ ConfigMap/hello-world@tutorial-04 ready
  ~ Deployment/hello-world@tutorial-04 configured
  ✔ Deployment/hello-world@tutorial-04 ready
  ~ Service/hello-world@tutorial-04 configured
  ✔ Service/hello-world@tutorial-04 ready
  ~ PackageInstance/hello-world@tutorial-04 configured
  ✔ PackageInstance/hello-world@tutorial-04 ready
  ~ Activation/04-from-scratch-6w9czkc51502 configured
  ✔ Activation/04-from-scratch-6w9czkc51502 ready
  • activation '04-from-scratch-ll6g0k5f2kqm' → Superseded
  • activation '04-from-scratch-6w9czkc51502' → Active

Deploy complete: 0 created, 5 configured, 5 unchanged, 0 failed

Kix evaluates the cluster, computes what needs to change, and applies the checked result to Kubernetes.

Check the resource status:

kix-examples/
❱ kix status 04-from-scratch
Skipped 7 objects that belong to Kix cluster 02-hello-world.
 NAME                          NAMESPACE    KIND                      READY  STATUS  AGE 
 04-from-scratch-6w9czkc51502  _cluster     Activation                True   Active  3s  
 04-from-scratch-ll6g0k5f2kqm  _cluster     Activation                True   Active  11s 
 activations.kix.run           _cluster     CustomResourceDefinition  True   Active  45s 
 packageinstances.kix.run      _cluster     CustomResourceDefinition  True   Active  45s 
 kube-system                   _cluster     Namespace                 True   Active  52s 
 tutorial-04                   _cluster     Namespace                 True   Active  11s 
 platform-dns                  kube-system  PackageInstance           True   Active  43s 
 hello-world                   tutorial-04  ConfigMap                 True   Active  11s 
 hello-world                   tutorial-04  Deployment                True   1/1     11s 
 hello-world                   tutorial-04  PackageInstance           True   Active  7s  
 hello-world                   tutorial-04  Service                   True   Active  7s

Then port-forward and verify the response:

kix-examples/
❱ kix pf 04-from-scratch hello-world 8080:80

In another terminal:

❱ curl http://localhost:8080/
Preview this before deploy.

Stop the port-forward with Ctrl-C.

Run:

kix-examples/ offline capture
❱ kix graph 04-from-scratch --format tree
Show outputHide output · 30 lines
CustomResourceDefinition/activations.kix.run
└── Activation/04-from-scratch-g941gkm4kxlh
CustomResourceDefinition/packageinstances.kix.run
├── PackageInstance/hello-world@tutorial-04
│   └── Activation/04-from-scratch-g941gkm4kxlh
├── PackageInstance/platform-storage@kube-system (import)
│   └── Activation/04-from-scratch-g941gkm4kxlh
└── PackageInstance/platform-dns@kube-system (import)
    └── Activation/04-from-scratch-g941gkm4kxlh
Namespace/kube-system
├── PackageInstance/platform-storage@kube-system (import)
│   └── Activation/04-from-scratch-g941gkm4kxlh
└── PackageInstance/platform-dns@kube-system (import)
    └── Activation/04-from-scratch-g941gkm4kxlh
Namespace/tutorial-04
├── Service/hello-world@tutorial-04
│   └── PackageInstance/hello-world@tutorial-04
│       └── Activation/04-from-scratch-g941gkm4kxlh
├── ConfigMap/hello-world@tutorial-04
│   └── Deployment/hello-world@tutorial-04
│       └── Service/hello-world@tutorial-04
│           └── PackageInstance/hello-world@tutorial-04
│               └── Activation/04-from-scratch-g941gkm4kxlh
├── PackageInstance/hello-world@tutorial-04
│   └── Activation/04-from-scratch-g941gkm4kxlh
└── Deployment/hello-world@tutorial-04
    └── Service/hello-world@tutorial-04
        └── PackageInstance/hello-world@tutorial-04
            └── Activation/04-from-scratch-g941gkm4kxlh
11 resources, 16 dependencies

For this cluster the graph is still small. You may see Kix’s own activation resources, the namespace, platform imports, and the hello-world package resources. The useful habit is to read the graph as “what Kix knows about this cluster,” not as raw YAML.

Do not worry about the deeper graph mechanics yet. Later tutorials add service dependencies, cross-namespace wiring, generated network policy, and scorecard rules. Each one gives the graph more useful information.

You used the same local cluster definition through the main Kix loop:

  • kix check catches problems before deploy
  • kix build shows the rendered Kubernetes resources
  • kix diff previews changes against the live cluster
  • kix deploy applies the evaluated result
  • kix status and kix graph inspect what Kix knows afterward

The next tutorial adds the first real relationship: one local package depends on another service.