Skip to content

05. Basic graph and deploy loop

This content is for the v0.1 version. Switch to the latest version for up-to-date documentation.

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

  1. check the cluster before deploying;
  2. inspect what Kix will build;
  3. preview changes against the live cluster;
  4. deploy the checked result;
  5. inspect the package and resource graph.

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:
Run in kix-examples/
❱ git ls-files --error-unmatch tutorial-04/cluster.nix

If that command fails, add the file:

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

Run the Kix checks first:

Run in kix-examples/
❱ kix check 04-from-scratch
 TOOL         RESULT  DETAILS                                                       
 eval         pass    10 manifests evaluated                                        
 kubeconform  pass    skipped (this validation tool is not yet integrated with Kix) 
 pluto        pass    skipped (this validation tool is not yet integrated with Kix) 
 kyverno      pass    skipped (this validation tool is not yet integrated with Kix) 
 scorecard    pass    0 errors, 2 warnings, 1 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:

Run in kix-examples/
❱ kix build 04-from-scratch Show output
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: g9737y2gwhk29xrfv4n17g1k0ga65ra7
    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: nginxinc/nginx-unprivileged:1.27-alpine
        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:
          runAsNonRoot: true
        volumeMounts:
        - mountPath: /etc/nginx/conf.d
          name: config
          readOnly: true
      volumes:
      - configMap:
          name: hello-world
        name: config
---
apiVersion: kix.run/v1alpha1
kind: Activation
metadata:
  annotations:
    kix.run/built-via: /nix/store/s07zv01j1pqyp7xnlgc2q86zs4cvcddp-k8s-activation-04-from-scratch
    kix.run/depends-on: jh345cnnffy1bnj4cz5x0hwq6ibxvkkr,qdhaxrymikrk9yw204k79giz69jc1n0q,requires:gvrlspcfgh9a2qnsx5kqxw9f4l3x52jj
    kix.run/identity-hash: m924biqa0b9f2z8x05xh3fpiskngx59g
    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-m924biqa0b9f'
spec:
  instances:
    kube-system:
      platform-dns: platform-dns
    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: hkdviynvj8w2207m1ppzymhrcrfh0gsq,qrh908pfdb2030zrxsp8018aiinicd2w,requires:pk4hih6jm4yj336rlaqk2qycz2k46xvs
    kix.run/identity-hash: qdhaxrymikrk9yw204k79giz69jc1n0q
    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.27'
---
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: g9737y2gwhk29xrfv4n17g1k0ga65ra7,qrh908pfdb2030zrxsp8018aiinicd2w
    kix.run/identity-hash: hkdviynvj8w2207m1ppzymhrcrfh0gsq
    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:

Run in kix-examples/
❱ kix diff 04-from-scratch Show output
⠁ Fetching live cluster state...                                                Discovering API resources...
Fetching managed resources (60 resource types)...
Fetched 17 resources across 60 resource types
3 packages: 2 unchanged, 0 changed, 0 added, 1 removed

  _cluster
    ~ cluster-level resources (1 removed)
      - Namespace/tutorial-02
  tutorial-02
    - hello-world 1.27 (3 resources)
(exit code: 2)

If your live cluster already matches the checked result, the diff should be empty or say there is nothing to change.

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:

Run in 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:

Run in kix-examples/
❱ kix diff 04-from-scratch Show output
⠁ Fetching live cluster state...                                                Discovering API resources...
Fetching managed resources (60 resource types)...
Fetched 17 resources across 60 resource types
3 packages: 1 unchanged, 1 changed, 0 added, 1 removed

  _cluster
    ~ cluster-level resources (1 removed)
      - Namespace/tutorial-02
  tutorial-02
    - hello-world 1.27 (3 resources)
  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 80;
                   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-m7cpzxzyff67 -> 04-from-scratch-qibygg9r95ww
(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:

Run in kix-examples/
❱ kix deploy 04-from-scratch -y Show output
Building cluster '04-from-scratch'...
Cluster 04-from-scratch: 10 manifests
Connecting to cluster...
Active activation: 04-from-scratch-m7cpzxzyff67 (m7cpzxzy...)
Reading live cluster state...

  _cluster
    ~ cluster-level resources (1 removed)
  tutorial-02
    - hello-world 1.27 (3 resources)
  tutorial-04
    ~ hello-world 1.27 (1 changed, 3 dep-affected)

  Plan: cluster-level changes, 1 updated, 1 removed, 1 unchanged
  Resources: 2 real content, 3 dep-affected
  5 orphaned (kept; pass --prune to delete)
    - Service/hello-world@tutorial-02
    - ConfigMap/hello-world@tutorial-02
    - Deployment/hello-world@tutorial-02
    - Namespace/tutorial-02
    - PackageInstance/hello-world@tutorial-02
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-qibygg9r95ww created
  ✔ Activation/04-from-scratch-qibygg9r95ww ready

prune: 5 orphaned resources kept (warn-only; pass --prune to delete)
  - PackageInstance/hello-world@tutorial-02
  - Service/hello-world@tutorial-02
  - Deployment/hello-world@tutorial-02
  - ConfigMap/hello-world@tutorial-02
  - Namespace/tutorial-02
  • activation '04-from-scratch-m7cpzxzyff67' → Superseded
  • activation '04-from-scratch-qibygg9r95ww' → Active

Deploy complete: 1 created, 4 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:

Run in kix-examples/
❱ kix status 04-from-scratch
 NAME                          NAMESPACE    KIND                      READY  STATUS  AGE 
 02-hello-world-ai8hpfnhamfs   _cluster     Activation                True   Active  8s  
 02-hello-world-rh3pdaspsdxk   _cluster     Activation                True   Active  13s 
 04-from-scratch-m7cpzxzyff67  _cluster     Activation                True   Active  5s  
 04-from-scratch-qibygg9r95ww  _cluster     Activation                True   Active  1s  
 activations.kix.run           _cluster     CustomResourceDefinition  True   Active  35s 
 packageinstances.kix.run      _cluster     CustomResourceDefinition  True   Active  35s 
 kube-system                   _cluster     Namespace                 True   Active  41s 
 tutorial-02                   _cluster     Namespace                 True   Active  35s 
 tutorial-04                   _cluster     Namespace                 True   Active  7s  
 platform-dns                  kube-system  PackageInstance           True   Active  33s 
 hello-world                   tutorial-02  ConfigMap                 True   Active  35s 
 hello-world                   tutorial-02  Deployment                True   1/1     35s 
 hello-world                   tutorial-02  PackageInstance           True   Active  13s 
 hello-world                   tutorial-02  Service                   True   Active  13s 
 hello-world                   tutorial-04  ConfigMap                 True   Active  7s  
 hello-world                   tutorial-04  Deployment                True   1/1     7s  
 hello-world                   tutorial-04  PackageInstance           True   Active  5s  
 hello-world                   tutorial-04  Service                   True   Active  5s

Then port-forward and verify the response:

Run in 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:

Run in kix-examples/
❱ kix graph 04-from-scratch --format tree Show output
CustomResourceDefinition/activations.kix.run
└── Activation/04-from-scratch-m924biqa0b9f
CustomResourceDefinition/packageinstances.kix.run
├── PackageInstance/hello-world@tutorial-04
│   └── Activation/04-from-scratch-m924biqa0b9f
└── PackageInstance/platform-dns@kube-system (import)
    └── Activation/04-from-scratch-m924biqa0b9f
Namespace/kube-system
└── PackageInstance/platform-dns@kube-system (import)
    └── Activation/04-from-scratch-m924biqa0b9f
Namespace/tutorial-04
├── Service/hello-world@tutorial-04
│   └── PackageInstance/hello-world@tutorial-04
│       └── Activation/04-from-scratch-m924biqa0b9f
├── ConfigMap/hello-world@tutorial-04
│   └── Deployment/hello-world@tutorial-04
│       └── Service/hello-world@tutorial-04
│           └── PackageInstance/hello-world@tutorial-04
│               └── Activation/04-from-scratch-m924biqa0b9f
├── PackageInstance/hello-world@tutorial-04
│   └── Activation/04-from-scratch-m924biqa0b9f
└── Deployment/hello-world@tutorial-04
    └── Service/hello-world@tutorial-04
        └── PackageInstance/hello-world@tutorial-04
            └── Activation/04-from-scratch-m924biqa0b9f
10 resources, 13 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.