Skip to content
Install the CLI

How-to guide Start and inspect a Kix project

Use mkFlake in a consuming repo

Use kixpkgs.lib.mkFlake in the repository that owns your cluster definitions. It exposes the cluster outputs that Kix discovers.

Create a flake.nix and declare nixpkgs and kixpkgs:

flake.nix
{
description = "Application infrastructure";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
kixpkgs.url = "github:kix-run/kix";
kixpkgs.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = inputs: inputs.kixpkgs.lib.mkFlake {
inherit inputs;
clusters.production = ./clusters/production.nix;
};
}

Following the same nixpkgs input keeps package evaluation on one nixpkgs revision.

The examples repository registers each cluster by name:

flake.nix
outputs =
inputs:
inputs.kixpkgs.lib.mkFlake {
inherit inputs;
clusters = {
"02-hello-world" = ./tutorials/02-hello-world/cluster.nix;
"06-service-dep" = ./tutorials/06-service-dep/cluster.nix;
"08-namespace-deps" = ./tutorials/08-namespace-deps/cluster.nix;
"09-typed-options" = ./tutorials/09-typed-options/cluster.nix;
"10-reuse-packages" = ./tutorials/10-reuse-packages/cluster.nix;
"19-scorecards" = ./tutorials/19-scorecards/cluster.nix;
"how-to-application" = ./how-to/application/cluster.nix;
"how-to-adoption" = ./how-to/adoption/cluster.nix;
"how-to-stateful-migration" = ./how-to/adoption/stateful-before-cluster.nix;
"how-to-adoption-takeover" = ./how-to/adoption/takeover-cluster.nix;
"how-to-helm-bridge" = ./how-to/adoption/helm-bridge-cluster.nix;
"how-to-auto-instantiation" = ./how-to/auto-instantiation/cluster.nix;
"how-to-composition" = ./how-to/composition/cluster.nix;
"how-to-fragments" = ./how-to/composition/fragments-cluster.nix;
"how-to-multi-env" = ./how-to/composition/multi-env-cluster.nix;
"how-to-package-storage" = ./how-to/package-stacks/storage-cluster.nix;
"how-to-package-stackgres" = ./how-to/package-stacks/stackgres-cluster.nix;
"how-to-package-monitoring" = ./how-to/package-stacks/monitoring-cluster.nix;
"how-to-package-monitoring-loki" = ./how-to/package-stacks/monitoring-loki-cluster.nix;
"how-to-package-cilium" = ./how-to/package-stacks/cilium-cluster.nix;
"how-to-package-valkey" = ./how-to/package-stacks/valkey-cluster.nix;
"how-to-package-velero" = ./how-to/package-stacks/velero-cluster.nix;
"how-to-package-victoria-metrics" = ./how-to/package-stacks/victoria-metrics-cluster.nix;
"how-to-platform-gateway" = ./how-to/platform/gateway-cluster.nix;
"how-to-platform-hostpath" = ./how-to/platform/hostpath-cluster.nix;
"how-to-platform-ingress" = ./how-to/platform/ingress-cluster.nix;
"how-to-platform-monitoring" = ./how-to/platform/monitoring-cluster.nix;
"how-to-platform-network-policy" = ./how-to/platform/network-policy-cluster.nix;
"how-to-platform-secrets" = ./how-to/platform/secrets-cluster.nix;
"how-to-platform-sops" = ./how-to/platform/sops-secrets-cluster.nix;
"how-to-platform-cloudflare-tunnel" = ./how-to/platform/cloudflare-tunnel-cluster.nix;
# The homelab scenario: one service set and its migration variants.
"homelab-kind" = ./how-to/homelab/cluster-kind.nix;
"homelab-kind-gateway" = ./how-to/homelab/cluster-kind-gateway.nix;
"homelab-kind-gateway-migrate" = ./how-to/homelab/cluster-kind-gateway-migrate.nix;
"homelab-kind-tls" = ./how-to/homelab/cluster-kind-tls.nix;
"homelab-kind-tls-migrate" = ./how-to/homelab/cluster-kind-tls-migrate.nix;
"homelab-kind-upgrade-small" = ./how-to/homelab/cluster-kind-upgrade-small.nix;
"homelab-kind-upgrade-broad" = ./how-to/homelab/cluster-kind-upgrade-broad.nix;
"homelab-kind-pvc-resize" = ./how-to/homelab/cluster-kind-pvc-resize.nix;
"homelab-kind-storage-migrate" = ./how-to/homelab/cluster-kind-storage-migrate.nix;
"homelab-kind-db-resize" = ./how-to/homelab/cluster-kind-db-resize.nix;
"homelab-kind-ns-rename" = ./how-to/homelab/cluster-kind-ns-rename.nix;
"homelab-kind-pg17" = ./how-to/homelab/cluster-kind-pg17.nix;
"homelab-kind-env-stamped" = ./how-to/homelab/cluster-kind-env-stamped.nix;
"homelab-kind-env-stamped-dev-change" = ./how-to/homelab/cluster-kind-env-stamped-dev-change.nix;
};
};

View source on GitHub ↗

Each value under clusters is a cluster function. Its attribute name is the cluster name you pass to the CLI, and, unless the file passes name to kix.buildCluster, the name Kix records in the manifests and the Activation record. A cluster listed here needs no name of its own.

Keep the name of a cluster that Kix 0.6 deployed

Section titled “Keep the name of a cluster that Kix 0.6 deployed”

Kix 0.6 named a cluster cluster when its file passed no name to kix.buildCluster. Kix 0.7 names it after its key, so its deploy records no longer match. kix deploy and kix plan refuse such a cluster before they apply anything: its file passes no name, the Kubernetes cluster has no Activation records for the key, and it has records for a Kix cluster named cluster. Fix it in the cluster file in one of two ways:

  • Add name = "cluster"; to the kix.buildCluster call to keep the old name and its deploy history.
  • Add name = "<key>"; to confirm the new name. The cluster then starts a new deploy history. The old cluster records stay on the Kubernetes cluster, and Kix never deletes the objects that only the old build has, so remove them yourself.

Either value turns the check off.

For a single project, a compact registration is enough:

flake.nix
outputs = inputs: inputs.kixpkgs.lib.mkFlake {
inherit inputs;
clusters.application = ./cluster.nix;
};

clusterModules lists modules every cluster in the flake starts from, ahead of the cluster’s own. Use it for a setting the repository wants everywhere, such as making scorecard findings fail the build:

flake.nix
outputs = inputs: inputs.kixpkgs.lib.mkFlake {
inherit inputs;
clusters.production = ./clusters/production.nix;
clusterModules = [ { scorecard.maxSeverity = "error"; } ];
};

A cluster that needs a different value sets the option itself with lib.mkForce.

Evaluating a cluster does not check the option contract. List the packages the repository defines under packages, and the flake gets the check option-contract-<name> for each one:

flake.nix
outputs = inputs: inputs.kixpkgs.lib.mkFlake {
inherit inputs;
clusters.production = ./clusters/production.nix;
packages.billing-api = ./packages/billing-api;
};

nix flake check then fails when a default of billing-api would be partly discarded by an instance that sets part of the option, at any availability level the package can run at.

A repository whose flake also builds other things, such as NixOS machines or container images, can still use mkFlake for its Kix outputs. Call it in a let binding and merge its packages, checks, and legacyPackages into the flake’s own, one system at a time:

flake.nix
outputs =
inputs:
let
systems = [ "x86_64-linux" "aarch64-darwin" ];
forAllSystems = inputs.nixpkgs.lib.genAttrs systems;
kixOutputs = inputs.kixpkgs.lib.mkFlake {
inherit inputs systems;
clusters.production =
{ kix, packages }:
import ./clusters/production.nix {
inherit kix packages;
shared = import ./clusters/shared.nix { inherit kix; };
};
};
in
{
nixosConfigurations.web-1 = ...;
packages = forAllSystems (
system: kixOutputs.packages.${system} // { deploy-tool = ...; }
);
checks = forAllSystems (
system:
kixOutputs.checks.${system}
// {
policy = import ./checks/policy.nix {
inherit (kixOutputs.legacyPackages.${system}.kixLib) kix packages;
};
}
);
legacyPackages = forAllSystems (system: kixOutputs.legacyPackages.${system});
};
  • A cluster that needs more arguments is given as a function. mkFlake passes it kix and packages, and the function passes the rest to the cluster file. Build anything the cluster uses, such as shared above, from the kix the function receives.
  • A check or tool outside the clusters takes the library from legacyPackages.<system>.kixLib. It holds kix and packages, set up for the builder in kix.toml, so it renders Helm charts the same way as the clusters.
  • Keep your own output names apart from Kix’s. // keeps the right-hand value when two names clash, without an error. Kix’s outputs start with cluster-, schema-cluster-, rules-cluster-, pi-wiring-cluster-, option-contract-, and, under legacyPackages, kix.

By default (builder = "kix"), Kix evaluates your project with the Kix runtime and Kix’s local store (~/.cache/kix/store). If your project needs the machine’s Nix installation and its configured binary caches or remote builders, add kix.toml beside flake.nix and stage it in Git:

kix.toml
builder = "nix"
infrastructure/
❱ git add kix.toml

See kix.toml for the full builder reference.

Pin the Kix library (kixpkgs in flake.lock) to match your installed Kix:

infrastructure/
❱ kix lib update

Commit flake.nix and flake.lock together. Other machines and CI will then evaluate the same Kix and nixpkgs revisions.

List the clusters exposed by the flake:

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

If the flake is not in the current directory, pass it explicitly:

❱ kix list clusters --flake ./infrastructure

You can now use the discovered names with check, build, diff, deploy, and the other cluster commands.