# kubenix turns a Nix module system into Kubernetes manifests

> kubenix is a Nix project that lets you describe a Kubernetes cluster with Nix modules and evaluation, then emits the JSON manifests to a result directory. The output is a normal Kubernetes manifest set that any deployment system can apply, and a built in CLI adds a diff, a confirmation prompt and secret injection through vals. It is MIT licensed and the readme warns to expect breaking changes.

**hall/kubenix** — Kubernetes management with Nix

- Repository: https://github.com/hall/kubenix
- Website: https://kubenix.org/
- Stars: 528 · Forks: 49
- Language: Nix
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/hall-kubenix

## Nix evaluation in place of manifest editing

kubenix is Kubernetes management with Nix, and the approach is to use the Nix module system to produce the manifests rather than to hand-write them.

The minimal example is a flake.nix built with nix build:

```nix
{
  inputs.kubenix.url = "github:hall/kubenix";
  outputs = {self, kubenix, ... }@inputs: let
    system = "x86_64-linux";
  in {
    packages.${system}.default = (kubenix.evalModules.${system} {
      module = { kubenix, ... }: {
        imports = [ kubenix.modules.k8s ];
        kubernetes.resources.pods.example.spec.containers.nginx.image = "nginx";
      };
    }).config.kubernetes.result;
  };
}
```

The structure of that is worth reading carefully. You evaluate the kubenix module system with your own module, and the module imports the k8s module from the kubenix flake input, then sets a path into the resource tree, kubernetes.resources.pods.example.spec.containers.nginx.image, and the result comes out of config.kubernetes.result.

That is a Nix module tree where the option paths mirror the shape of a Kubernetes object. The payoff is the ordinary Nix one, namely that options can be defined once and reused, and that a cluster description becomes a value you can pass between evaluations rather than a file you edit.

The readme is upfront about the maturity: this is a work in progress, and you should expect breaking changes.

## The same thing without flakes, and where the output lands

If you are not using flakes, there is a default.nix built with nix-build:

```nix
{ kubenix ? import (builtins.fetchGit {
  url = "https://github.com/hall/kubenix.git";
  ref = "main";
}) }:
(kubenix.evalModules.x86_64-linux {
  module = { kubenix, ... }: {
    imports = [ kubenix.modules.k8s ];
    kubernetes.resources.pods.example.spec.containers.nginx.image = "nginx";
  };
}).config.kubernetes.result
```

The difference is only how the kubenix source is obtained, a flake input versus a fetchGit in the default.nix. The module structure and the result are the same.

Either way, and this is the important line, the JSON manifests will be written to ./result. So kubenix is a producer of files, not an actor in your cluster. The result directory is what you then hand to whatever applies your manifests.

That separation is what makes the tool compatible with just about any deployment system, as the CLI section puts it. If you want to see more, the examples page on the project site covers pods and more, and the repository holds a docs directory alongside the flake, lib, modules, pkgs and tests directories.

## The CLI, and what a diff before an apply buys you

While kubenix is compatible with just about any deployment system, there is a simple builtin CLI which can show a diff, prompt for confirmation and then apply, prune removed resources, and pipe manifests through vals for the ability to inject secrets without writing them to the nix store.

The diff is the centrepiece, and the documentation shows a real one. The output is a unified diff of the live resource against the merged one, with two ignore patterns for the kubenix hash and generation lines, followed by the confirmation prompt:

```diff
diff -N -u -I ' kubenix/hash: ' -I ' generation: ' /tmp/LIVE-2503962153/apps.v1.Deployment.default.home-assistant /tmp/MERGED-231044561/apps.v1.Deployment.default.home-assistant
--- /tmp/LIVE-2503962153/apps.v1.Deployment.default.home-assistant      2023-07-06 23:33:29.841771295 -0400
+++ /tmp/MERGED-231044561/apps.v1.Deployment.default.home-assistant     2023-07-06 23:33:29.842771296 -0400
@@ -43,7 +43,7 @@
     spec:
       automountServiceAccountToken: true
       containers:
-      - image: homeassistant/home-assistant:2023.5
+      - image: homeassistant/home-assistant:2023.6
         imagePullPolicy: IfNotPresent
         livenessProbe:
           failureThreshold: 3
apply? [y/N]:
```

So the CLI reads the live state, reads the evaluated state, shows the difference and waits. The diff example is an image tag bump, which is exactly the kind of change you want to see before it lands.

To configure the CLI you override the default package and pass the arguments of evalModules, which is where specialArgs goes:

```nix
{
  kubenix = inputs.kubenix.packages.${pkgs.system}.default.override {
    module = import ./cluster;
    # optional; pass custom values to the kubenix module
    specialArgs = { flake = self; };
  };
}
```

And the manifests can be written to ./result/manifests.json with nix build on the kubenix package, if you would rather apply them yourself.

## Secrets stay out of the Nix store

The vals integration deserves its own attention, because it solves a problem that is easy to get wrong with Nix.

The Nix store is content addressed and world readable, so anything you evaluate into it is effectively published to anyone who can read your store. A secret that goes into a manifest that goes into the store is a secret that leaks. The CLI can pipe the manifests through vals, which resolves values at apply time rather than at build time.

That is the difference between a secret being in a derivation and a secret being injected into a request. It is also why the CLI lists it as a capability alongside diffing and pruning, since all three are about not doing something irreversible by accident.

Pruning is the third of those, and it is the one to think about before enabling. Pruning removes resources that are no longer in your configuration, which is correct for a declarative system and destructive for a cluster where something was deployed by hand. The two capabilities fit together the way you would expect: you see a diff, you confirm, and then removed resources go away because they are gone from the configuration.

For a cluster managed entirely by kubenix, that is the right model. For a cluster where other people also apply manifests, it is worth knowing that the prune is a cluster-wide statement about what should exist.

## A fork of a fork, and an early version number

The attribution section is a good sign about how the project handles its history. It was forked from GTrunSec, which was forked from xtruder, with commits incorporated from blaggacao. The logo is described as a mishmash of the Kubernetes wheel and the NixOS snowflake.

So the useful part of this project is a sequence of three rewrites of the same idea, and the current maintainer has been explicit about where the code came from. That is a much better position than a repository with an unclear origin, particularly for a project whose output goes straight into a cluster.

The versioning tells a similar story. The releases are 0.3.0 from 2024-05-06, 0.2.0 from 2023-07-07 and 0.1.0 from 2023-07-07, with two of the three published on the same day in July 2023. The last push was on 2026-10-18, so the branch is being worked on, but the published version has not moved since May 2024 and the project is still below 1.0.

Combined with the warning in the readme about breaking changes, the picture is of a young project with an active maintainer and an unstable interface. The module paths in the readme are the contract, and they can change under you, which is why the changelog is linked from the warning itself.

## Conclusion

Adopt kubenix if you already use Nix and want cluster configuration to be modules with reusable options rather than a pile of manifests, and you are prepared to track a project that warns about breaking changes. Do not adopt it if you want a Kubernetes operator or a controller, since kubenix only produces manifests and hands them to something else to apply. Before you start, check three things: which module set you import, since the minimal example imports the k8s module and the rest is your own options, whether your CI can run nix build and read the result directory, and how you will handle secrets, since the documented route pipes the manifests through vals so that they never enter the nix store.

## FAQ

### What does kubenix do?

It is Kubernetes management with Nix. You evaluate a Nix module system, import the k8s module, set options that mirror the shape of Kubernetes objects, and the evaluation produces the JSON manifests, which are written to the ./result directory.

### How do I use kubenix without flakes?

Use a default.nix built with nix-build, importing the kubenix source with builtins.fetchGit and calling evalModules the same way. The module structure and the result are the same either way, and the manifests still land in ./result.

### What does the kubenix CLI do before it applies anything?

It shows a unified diff between the live resources and the merged ones, ignoring the kubenix hash and generation lines, and then prompts for confirmation with an apply? [y/N] question. It can also prune removed resources and pipe the manifests through vals.

### How does kubenix handle secrets?

The CLI can pipe the manifests through vals, which injects secrets at apply time so they are not written to the nix store. That matters because the nix store is content addressed and readable, so a secret evaluated into a manifest would otherwise be effectively published.

### Is kubenix stable enough to depend on?

The readme warns that it is a work in progress and that you should expect breaking changes, with a link to the changelog, and the newest release is 0.3.0 from 2024-05-06 even though the last push was on 2026-10-18. The licence is MIT and the project is not archived.

## Sources

- [hall/kubenix on GitHub](https://github.com/hall/kubenix)
- [License: MIT](https://github.com/hall/kubenix/blob/main/LICENSE)
- [Project website](https://kubenix.org/)
- [README](https://github.com/hall/kubenix/blob/main/README.md)
- [Releases](https://github.com/hall/kubenix/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/hall-kubenix
