Skip to content
Install the CLI

Tutorial 02

Local setup and first run

In this tutorial you will deploy one tiny Kix cluster to kind, port-forward to it, and make one config edit. The point is to feel the loop:

  1. Edit the cluster or package.
  2. Check what Kix can prove before touching the cluster.
  3. Deploy the checked activation.
  4. Verify with kix pf, curl, or kubectl.

The runnable examples live in the kix-examples repo:

❱ git clone https://github.com/kix-run/kix-examples.git
❱ cd kix-examples

If you already have the repo, use that checkout instead. The commands below assume you are at the repo root.

Open the files for the first example:

flake.nix
tutorials/02-hello-world/cluster.nix
tutorials/02-hello-world/README.md
kind-config.yaml

You do not need to understand all of them yet. For now:

  • flake.nix exposes the example clusters by name;
  • tutorials/02-hello-world/cluster.nix defines the first cluster;
  • kind-config.yaml creates a local kind cluster named kix-demo.

If your machine already has kix, kind, and kubectl on PATH, you can use your normal shell. If you have not installed kix yet, follow Install and run Kix to download and unpack the release archive into ~/.local.

Keep Docker, Colima, or OrbStack running separately. kind needs a Docker-compatible daemon to create the local cluster.

Check that Kix can see the example clusters:

kix-examples/
❱ kix list clusters
 NAME                                
 02-hello-world                      
 06-service-dep                      
 08-namespace-deps                   
 09-typed-options                    
 10-reuse-packages                   
 19-scorecards                       
 homelab-kind                        
 homelab-kind-db-resize              
 homelab-kind-env-stamped            
 homelab-kind-env-stamped-dev-change 
 homelab-kind-gateway                
 homelab-kind-gateway-migrate        
 homelab-kind-ns-rename              
 homelab-kind-pg17                   
 homelab-kind-pvc-resize             
 homelab-kind-storage-migrate        
 homelab-kind-tls                    
 homelab-kind-tls-migrate            
 homelab-kind-upgrade-broad          
 homelab-kind-upgrade-small          
 how-to-adoption                     
 how-to-adoption-takeover            
 how-to-application                  
 how-to-auto-instantiation           
 how-to-composition                  
 how-to-fragments                    
 how-to-helm-bridge                  
 how-to-multi-env                    
 how-to-package-cilium               
 how-to-package-monitoring           
 how-to-package-monitoring-loki      
 how-to-package-stackgres            
 how-to-package-storage              
 how-to-package-valkey               
 how-to-package-velero               
 how-to-package-victoria-metrics     
 how-to-platform-cloudflare-tunnel   
 how-to-platform-gateway             
 how-to-platform-hostpath            
 how-to-platform-ingress             
 how-to-platform-monitoring          
 how-to-platform-network-policy      
 how-to-platform-secrets             
 how-to-platform-sops                
 how-to-stateful-migration

You should see 02-hello-world in the output. If you are not in the example repo, point Kix at it explicitly:

❱ kix --flake /path/to/kix-examples list clusters

The --flake flag tells Kix which flake to evaluate. Without it, Kix uses the current directory.

Start Docker, then create the kind cluster:

kix-examples/
❱ kind create cluster --config kind-config.yaml

This creates a single-node cluster named kix-demo and sets your current kubectl context to kind-kix-demo.

Check that Kubernetes is reachable:

kix-examples/
❱ kubectl cluster-info
Kubernetes control plane is running at https://127.0.0.1:40711
CoreDNS is running at https://127.0.0.1:40711/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
kix-examples/
❱ kubectl get nodes
NAME                     STATUS     ROLES           AGE   VERSION
kix-docs-control-plane   NotReady   control-plane   4s    v1.35.0

If you already have this kind cluster, kind will say it exists. That is fine; continue with the deploy step.

Deploy the first Kix example:

kix-examples/ live capture
❱ kix deploy 02-hello-world -y
Show outputHide output · 39 lines
Building cluster '02-hello-world'...
Cluster 02-hello-world: 10 manifests
Connecting to cluster...
No previous activation on cluster. First deploy.

  _cluster
    ~ cluster-level resources (4 added)
  kube-system
    + platform-dns (0 resources)
  tutorial-02
    + hello-world 1.27 (3 resources)

  Plan: cluster-level changes, 2 added
  Resources: 4 real content, 0 dep-affected
plan: 10 nodes
  + Namespace/tutorial-02 created
  ✔ Namespace/tutorial-02 ready
  + ConfigMap/hello-world@tutorial-02 created
  ✔ ConfigMap/hello-world@tutorial-02 ready
  + CustomResourceDefinition/activations.kix.run created
  + Deployment/hello-world@tutorial-02 created
  ✔ CustomResourceDefinition/activations.kix.run ready
  ~ Namespace/kube-system configured
  ✔ Namespace/kube-system ready
  + CustomResourceDefinition/packageinstances.kix.run created
   1.140286895s  WARN kix::cluster::client: apiserver request failed, retrying method=GET path="/apis/kix.run/v1alpha1/activations" attempt=1 max_attempts=7 delay_ms=1000 reason="429 Too Many Requests"
  ✔ CustomResourceDefinition/packageinstances.kix.run ready
  + PackageInstance/platform-dns@kube-system created
  ✔ PackageInstance/platform-dns@kube-system ready
  ✔ Deployment/hello-world@tutorial-02 ready
  + Service/hello-world@tutorial-02 created
  ✔ Service/hello-world@tutorial-02 ready
  + PackageInstance/hello-world@tutorial-02 created
  ✔ PackageInstance/hello-world@tutorial-02 ready
  ~ Activation/02-hello-world-sgnxg5lby2jc configured
  ✔ Activation/02-hello-world-sgnxg5lby2jc ready
  • activation '02-hello-world-sgnxg5lby2jc' → Active

Deploy complete: 8 created, 2 configured, 0 unchanged, 0 failed

The -y flag skips the confirmation prompt. On the first deploy, Kix builds the cluster, sees there is no previous activation, and applies the resources wave-by-wave. The order you see comes from the deploy graph.

List the packages installed in this Kix cluster:

kix-examples/
❱ kix list packages --cluster 02-hello-world
 NAME              VERSION  OWNER     STATUS                  
 hello-world       1.30.5   app-team  installed [tutorial-02] 
 platform-dns      -        -         import [kube-system]    
 platform-storage  -        -         import [kube-system]

You should see an installed hello-world instance. The import rows are platform pieces (DNS, storage) Kix pulls in automatically; later tutorials explain them. You can also ask Kubernetes directly:

❱ kubectl get pods -n tutorial-02
NAME                          READY   STATUS    RESTARTS   AGE
hello-world-5dfdf5bdd-f98hz   1/1     Running   0          24s
❱ kubectl get svc -n tutorial-02
NAME          TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
hello-world   ClusterIP   10.96.166.102   <none>        80/TCP    1s

The first example installs an echo-server package instance named hello-world. Use kix pf to port-forward to its primary workload:

kix-examples/
❱ kix pf 02-hello-world hello-world 8080:8080

Leave that command running. In another terminal, call the service:

❱ curl http://localhost:8080/
Hello from Kix!

Stop the port-forward with Ctrl-C.

kix pf resolves the package instance to the workload Kix built, then delegates to kubectl port-forward. The package name can also be written as namespace/name when a cluster has duplicate instance names, but this first cluster only has one.

Open tutorials/02-hello-world/cluster.nix and find this instance:

tutorials/02-hello-world/cluster.nix
hello-world = {
# `packages.echo-server` ships in the public kixpkgs
# repository, along with many others.
# It serves a simple fixed message over HTTP.
package = packages.echo-server;
# `config` is whatever the package's options accept.
# echo-server's `message` becomes the body of every response.
config = {
message = "Hello from Kix!";
};
};

View source on GitHub ↗

Change the message:

kix-examples/tutorials/02-hello-world/cluster.nix
config = {
message = "Hello from my laptop!";
};

Deploy again:

kix-examples/ live capture
❱ kix deploy 02-hello-world -y
Show outputHide output · 25 lines
Building cluster '02-hello-world'...
Cluster 02-hello-world: 10 manifests
Connecting to cluster...
Active activation: 02-hello-world-sgnxg5lby2jc (sgnxg5lb...)

  tutorial-02
    ~ 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-02 configured
  ✔ ConfigMap/hello-world@tutorial-02 ready
  ~ Deployment/hello-world@tutorial-02 configured
  ✔ Deployment/hello-world@tutorial-02 ready
  ~ Service/hello-world@tutorial-02 configured
  ✔ Service/hello-world@tutorial-02 ready
  ~ PackageInstance/hello-world@tutorial-02 configured
  ✔ PackageInstance/hello-world@tutorial-02 ready
  ~ Activation/02-hello-world-5iyml5dk18c8 configured
  ✔ Activation/02-hello-world-5iyml5dk18c8 ready
  • activation '02-hello-world-sgnxg5lby2jc' → Superseded
  • activation '02-hello-world-5iyml5dk18c8' → Active

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

Port-forward and curl again:

kix-examples/
❱ kix pf 02-hello-world hello-world 8080:8080

In another terminal:

❱ curl http://localhost:8080/
Hello from my laptop!

The response should show your new message.

That edit is small, but it proves the core loop: Kix evaluated the flake, rebuilt the manifests, applied the changed workload, and left you with a normal Kubernetes Service and Pod.

You have now used:

  • kix list clusters to discover cluster names;
  • kix deploy <cluster> -y to apply one cluster;
  • kix list packages --cluster <cluster> to inspect installed packages;
  • kix pf <cluster> <package> <local>:<remote> to reach a workload.

The next tutorial steps back from the running cluster and looks at the repo shape that made those commands work.