Self-hosted service
saschagrunert/kubernix avatar
saschagrunert/kubernix

KuberNix: single-dependency Kubernetes clusters built from Nix packages

Single dependency Kubernetes clusters for local testing, experimenting and development.

829 stars28 forksRustApache-2.0

At a glance

What is it?
KuberNix is a Rust program that bootstraps a local Kubernetes cluster from a pinned Nix package set, so the only thing a developer has to install is Nix itself. It runs rootless by default, starting components in four ordered phases and dropping you into a shell that owns the cluster.
Who is it for?
Adopt KuberNix if you develop against the Kubernetes API and want a cluster that starts the same way on every machine, with the whole toolchain pinned by Nix and no root required. Do not adopt it if your work depends on NodePort services being reachable from the host, on loading kernel modules from the user namespace, or on container isolation between nodes in rootless multi-node mode.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One dependency instead of a toolchain checklist

KuberNix exists to remove the familiar tax of local Kubernetes development. A working cluster normally asks for etcd, a certificate authority, a container runtime, CNI plugins, networking tools and the Kubernetes binaries themselves, each at a compatible version, each installed by a different mechanism on every operating system you might use. The project's goal is a single dependency cluster for local testing, experimenting and development, and the README is explicit that the single dependency is Nix with flakes enabled.

The reasoning is that Nix pins every third-party dependency to a dedicated version, which the README presents as the guarantee of stable, reproducible installations. The program itself is what the README calls a Rusty helper: it bootstraps the cluster, passes the right configuration parameters around, and keeps track of the running processes. The user is a developer who wants to run against a real API server without assembling a control plane by hand, and who would rather not keep a second copy of Kubernetes tooling on the machine.

The scale is deliberately modest. This is a development cluster, not a production platform, and the README describes its purpose as local testing, experimenting and development. What it optimises for is the first run and the reproducibility of that run.

Four boot phases and the order they enforce

KuberNix starts components in four sequential phases, with the components inside a phase starting in parallel. If anything in a phase fails, the later phases are skipped rather than started against a half-built cluster, which is a sensible contract for a tool whose output is a working control plane.

The README draws the sequence as a graph, and the structure is easy to read:

mermaid
graph TD
    A["Phase 1: Infrastructure"] --> B["Phase 2: ControlPlane"]
    B --> C["Phase 3: Controller"]
    C --> D["Phase 4: NodeAgent"]

    A --- etcd
    B --- apiserver
    C --- scheduler & controller-manager & cri["cri-o or containerd (x N)"] & proxy
    D --- kubelet["kubelet (x N)"]

Infrastructure comes first with etcd, then the control plane with the API server, then the controller phase with the scheduler, the controller manager, the container runtime, either cri-o or containerd, and the proxy, and finally the node agent phase with one kubelet per node. The runtime and kubelet counts marked x N are what make multi-node clusters possible, and the choice between cri-o and containerd is exposed as a flag on the command line.

Phase ordering is not decoration. etcd has to be accepting writes before the API server tries to talk to it, and the API server has to be reachable before the scheduler, the controller manager and the kubelets start registering against it. Encoding that as four ordered phases means a failure is reported at the point where the dependency chain broke, instead of surfacing as a kubelet that never appears in kubectl get nodes.

Running rootless and what the network namespace costs you

When you run KuberNix as a non-root user it uses rootlesskit to provide a user namespace where all components run as fake root. That requires unprivileged user namespaces, controlled by kernel.unprivileged_userns_clone, and cgroup v2 with user delegation. On Ubuntu 23.10 and later you may also need to set kernel.apparmor_restrict_unprivileged_userns to 0. These are host-level requirements, and they are the first thing to check when the bootstrap fails on a new machine.

The network arrangement has consequences that matter for application development. The cluster runs in its own network namespace, which slirp4netns connects to the outside world. Only the API server port, 6443, is published to 127.0.0.1 on the host. NodePort services and host ports are therefore not reachable from the host, and the cluster cannot reach services listening on the host's loopback interface. If you are writing an application that talks to something on your laptop, that will not work without a different arrangement.

There is also a CIDR constraint: the value passed with --cidr must not overlap the slirp4netns network 10.0.2.0/24. Kernel modules cannot be loaded explicitly from inside the user namespace. The kernel loads the netfilter modules for the pod network and kube-proxy on demand, and otherwise they have to be loaded on the host beforehand, with iptable_nat and nf_conntrack named as examples.

Installing Nix, building KuberNix and starting the cluster

The first step is installing Nix with flakes enabled, which the README reduces to a single command:

shell
$ curl https://nixos.org/nix/install | sh

The script prints instructions of its own, and the README asks you to follow them and to make sure flakes are enabled in your Nix configuration.

Next you need the KuberNix binary, either from the release binaries or built from source. The build route goes through the Makefile:

shell
$ make build-release

That produces target/release/kubernix in the project directory. Installing the published crate instead is the alternative the README gives, with cargo install kubernix.

With the binary in place, starting the cluster is a bare invocation:

shell
$ kubernix

KuberNix then sets up the Nix environment, downloads the required binaries and starts the cluster. By default it creates a directory called kubernix-run in the current path containing everything the cluster needs. On success it drops you into an interactive shell and tells you so, with a warning that matters more than it looks:

code
[INFO ] Everything is up and running
[INFO ] Spawning interactive shell
[INFO ] Please be aware that the cluster stops if you exit the shell

That last line is the operational contract. The shell owns the cluster's lifetime, so closing it tears the cluster down. From that prompt, kubectl works against the local API server, and a first listing shows the system pods already running.

What ends up in the run directory

All configuration files are written into the target directory, which becomes the current directory for the shell session. The layout maps closely onto the components, which makes finding a log or a certificate a directory walk rather than a hunt:

code
> ls -1
apiserver/
controllermanager/
coredns/
crio/          # or containerd/ when using --cri-runtime=containerd
encryptionconfig/
etcd/
kubeconfig/
kubelet/
kubernix.env
kubernix.toml
nix/
pki/
policy.json
proxy/
scheduler/

Two files in that list are the ones you interact with. The kubeconfig directory holds the credentials a client needs to reach the cluster, and the pki directory holds the generated certificate material, with cfssl among the pinned packages for that job. kubernix.toml and kubernix.env are the tool's own configuration, and the encryptionconfig and policy.json entries relate to data encryption at rest and the pod security policy the components enforce.

Logs follow the same shape, one file per component, with the CRI runtime and kubelet entries stored per node:

code
> ls -1 */*.log */*/*.log
apiserver/kube-apiserver.log
controllermanager/kube-controller-manager.log
crio/<node>/crio.log  # or containerd/<node>/containerd.log
etcd/etcd.log
kubelet/<node>/kubelet.log
proxy/kube-proxy.log
scheduler/kube-scheduler.log

The angle bracket placeholders are literal in the output and stand for the hostname in a single node cluster or node-0, node-1 and so on in a multi-node one.

The pinned package set and how the table is generated

The README lists the technology stack the project currently uses, and the list is long enough to make the value of pinning obvious. It runs Kubernetes 1.37.1 with kubectl 1.37.1, etcd 3.6.15, cfssl 1.6.5 for certificates, containerd 2.4.1 alongside cri-o-wrapper 1.37.2 and cri-tools 1.37.0 as the alternative runtime, crun 1.30.1 and runc 1.5.2 as low-level runtimes, podman 5.8.9 with rootlesskit 2.3.6 for the rootless machinery, slirp4netns 1.3.5 for networking, cni-plugins 1.9.1, iproute2, iptables, kmod, conntrack-tools, conmon, socat, sysctl, util-linux and nss-cacert.

The README notes that some tools are not listed because they are not first-level dependencies. More interesting is how the table stays correct. The repository contains a hack directory with an update-readme-versions.sh script, and the Makefile has an update-readme target whose description says it regenerates the README versions table from the Nix development shell. The nix-update target updates the pinned nixpkgs to nixpkgs-unstable and then calls that script. In other words, the versions in the README are a generated artifact of the pinned package set rather than a hand-maintained list.

Two other repository files support that pinning story: dependencies.yaml, which records the dependency set, and deny.toml, the configuration for cargo-deny, which is the tool used to check licences and advisories across Rust dependencies.

Where KuberNix is the wrong tool

The clearest limitation is the one the README states about the network. Because only the API server port is published to the host, NodePort services and host ports are not reachable from the host, and the cluster cannot reach services bound to the host's loopback interface. Developers whose workflow depends on reaching an application in the cluster from their browser, or on the cluster reaching a service on the laptop, will hit a wall that the tool does not remove.

Multi-node isolation is the second limit. In multi-node rootless mode all nodes run as direct processes with --hostname-override for node identity, with no container isolation between them. Running with sudo gives container-based multi-node isolation instead. So the more isolated setup is the one that needs root, which is precisely what rootless mode is for avoiding.

Third, the lifetime is tied to the shell. The cluster stops when you exit the interactive shell, which is convenient interactively and awkward for scripted use. The README does not document a detached mode, and it does not document how to keep a cluster running across sessions, so anyone trying to use this as the backend of a CI job has to read the source.

The alternative approach is to install the components yourself with a tool such as kubeadm, minikube or kind, each of which bundles its own versions of etcd, the runtime and the networking. The difference in approach is dependency management: those tools carry the versions inside the binary, while KuberNix resolves them from Nix at run time and therefore needs Nix on the machine and accepts the cost of that dependency.

Release cadence, build targets and licence

The project is not archived and the last push was on 2026-10-19, the same day as the v0.4.3 release. Before that came v0.4.2 on 2026-10-08 and v0.4.1 on 2026-09-24, so releases land roughly every one to three weeks. The crate is versioned in Cargo.toml as 0.4.3, uses Rust edition 2024 and declares a minimum Rust version of 1.88.

The Makefile covers more than the local build. Besides build and build-release, there is build-static, which compiles with RUSTFLAGS setting the crt-static target feature, strips the binary and then verifies with ldd that the result is statically linked. There is also build-cross for cross compilation, with a default target of x86_64-unknown-linux-gnu, and a clean target that kills the runtime processes and removes the kubernix-run directory before running cargo clean.

Licensing is Apache-2.0, recorded in Cargo.toml and shipped as a LICENSE file at the repository root. That is a permissive licence with an express patent grant and the standard requirement to preserve notices, which suits both commercial use and modification, and it is the same terms the dependency checking in deny.toml is there to protect. The documentation is published twice over, on docs.rs for the crate API and on a GitHub Pages site for the project documentation, and the CI workflow and codecov configuration at the repository root show that coverage is tracked on every change.

Editorial conclusion

Adopt KuberNix if you develop against the Kubernetes API and want a cluster that starts the same way on every machine, with the whole toolchain pinned by Nix and no root required. Do not adopt it if your work depends on NodePort services being reachable from the host, on loading kernel modules from the user namespace, or on container isolation between nodes in rootless multi-node mode. Verify first that your kernel exposes unprivileged user namespaces and cgroup v2 with delegation, that the cluster CIDR you pass does not overlap the slirp4netns range 10.0.2.0/24, and that the netfilter modules the pod network needs are already loaded on the host.

Frequently asked questions

What do I need to install before running KuberNix?

Only Nix with flakes enabled. The README installs it with curl https://nixos.org/nix/install | sh, and KuberNix resolves the Kubernetes components, etcd, the container runtime and the networking tools from the pinned Nix package set.

How does KuberNix run a cluster without root?

It uses rootlesskit to create a user namespace where components run as fake root, which needs unprivileged user namespaces and cgroup v2 with user delegation. On Ubuntu 23.10 and later you may also need to set kernel.apparmor_restrict_unprivileged_userns to 0.

Can I reach NodePort services in a KuberNix cluster from my host?

No. The cluster runs in its own network namespace connected by slirp4netns, and only the API server port 6443 is published to 127.0.0.1 on the host, so NodePort services and host ports are not reachable from the host.

How do I stop or clean up a KuberNix cluster?

The cluster stops when you exit the interactive shell it spawns. The Makefile also provides a clean target that kills the running processes, removes the kubernix-run directory and runs cargo clean.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. README
  4. Releases
  5. saschagrunert/kubernix on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/saschagrunert-kubernix.svg)](https://hysenlabs.com/projects/saschagrunert-kubernix)

Was this useful?