Introduction to nixidy - Kubernetes GitOps with nix

Lobsters Hottest Tools

Summary

nixidy is a Nix-based tool for managing Kubernetes GitOps deployments that replaces Helm value files and Kustomize overlays with typed, reproducible Nix expressions. This tutorial walks through setting up a nixidy project with Argo CD, generating plain YAML for review.

<p><a href="https://lobste.rs/s/nwrobx/introduction_nixidy_kubernetes_gitops">Comments</a></p>
Original Article
View Cached Full Text

Cached at: 06/08/26, 05:20 PM

# nixidy part 1: Introduction to nixidy | codedbearder Source: [https://codedbearder.com/posts/nixidy-part-1-introduction/](https://codedbearder.com/posts/nixidy-part-1-introduction/) ## nixidy part 1: Introduction to nixidy Posted on Jun 8, 2026 I have managed many GitOps repositories for Kubernetes with ArgoCD and I'm sure I'm not alone in having opened a Helm values override file that was 600 lines of YAML and still not being sure which values actually made it into the rendered manifests\. I've run`helm template`, piped it through`grep`, given up, committed it anyway and hoped the staging diff would catch anything my eyes missed\. That gap between what you*think*you're deploying and what actually lands in the cluster is exactly what[nixidy](https://github.com/arnarg/nixidy)is meant to close\. I wrote it to replace Helm value files, Kustomize overlays, and raw YAML with a single Nix expression per environment\. Every field is typed, every build is reproducible, and the output is plain YAML you can`git diff`before it ever touches a cluster\. By the end of this part we'll have a working nixidy project that defines an nginx Deployment and Service, generates Argo CD`Application`manifests automatically, and deploys to your cluster through GitOps\. ## What you'll build We're going to build a nixidy environment called`dev`containing one application deployed to your cluster via Argo CD\. The project structure will be the skeleton you'd extend to manage an entire production cluster\. ## Prerequisites - **Nix**installed with flakes enabled \([download](https://nixos.org/download.html)\) - **A Kubernetes cluster**with**Argo CD**installed - **A Git repository**for your Kubernetes manifests \(GitHub, GitLab, etc\.\) - **Basic familiarity**with Kubernetes Deployments, Services, and Namespaces - **Basic familiarity**with Argo CD`Application`resources info nixidy implements the[Rendered Manifests Pattern](https://akuity.io/blog/the-rendered-manifests-pattern/)where your CI generates plain YAML, you review it in PRs, and Argo CD deploys it\. If you've used Argo CD with raw YAML or Kustomize before, the deployment side is identical\. The difference is entirely in*how the YAML is produced*\. ## A Nix expression that builds a Kubernetes manifest The core idea behind nixidy is that every Kubernetes resource is a typed Nix option\. A Deployment isn't a blob of YAML, it's a structured attribute set where`replicas`is an integer,`image`is a string, and a typo in`selector`is a build error, not a runtime surprise\. Let's start by creating the project: ``` mkdir my-cluster && cd my-cluster git init ``` Now create`flake\.nix`, this is the entry point that wires nixidy into your Nix flake: ``` { description = "My Kubernetes cluster managed with nixidy"; inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; flake-utils.url = "github:numtide/flake-utils"; nixidy.url = "github:arnarg/nixidy"; }; outputs = { nixpkgs, flake-utils, nixidy, ... }: flake-utils.lib.eachDefaultSystem (system: let pkgs = import nixpkgs {inherit system;}; in { nixidyEnvs = nixidy.lib.mkEnvs { inherit pkgs; envs.dev.modules = [ ./env/dev.nix ]; }; }); } ``` A couple things worth noting: - `nixidy\.lib\.mkEnvs`takes a set of named environments and returns Nix derivations that build YAML manifests\. The key`dev`becomes the attribute you reference with`\.\#dev`\. - Each environment takes a list of NixOS\-style modules which are plain`\.nix`files that set options\. This is the same module system that powers NixOS, which means you get`imports`,`lib\.mkDefault`,`lib\.mkForce`, and all the composition primitives you'd expect\. info If you've configured a NixOS system before, the shape is identical: a list of modules that set options, merged by the module system\. The difference is that the options describe Kubernetes resources instead of system services\. ## An environment module with one application Now let's create the environment directory and the dev module: Write`env/dev\.nix`\. Make sure to replace the repository URL with your own \(this is where nixidy will tell Argo CD to look for rendered manifests\): ``` { nixidy.target.repository = "https://github.com/YOUR_USERNAME/my-cluster.git"; nixidy.target.branch = "main"; nixidy.target.rootPath = "./manifests/dev"; applications.nginx = { namespace = "nginx"; createNamespace = true; resources = { deployments.nginx.spec = { replicas = 2; selector.matchLabels.app = "nginx"; template = { metadata.labels.app = "nginx"; spec.containers.nginx = { image = "nginx:1.25.1"; ports.http.containerPort = 80; }; }; }; services.nginx.spec = { selector.app = "nginx"; ports.http.port = 80; }; }; }; } ``` Let me walk through what this declares: - **`nixidy\.target\.\*`**: Where the generated YAML ends up in your Git repo\. Argo CD`Application`manifests will reference this repo, branch, and path\. - **`applications\.nginx`**: One logical application\. An application gets its own directory in the output and its own Argo CD`Application`manifest\. - **`namespace = "nginx"`**: All resources in this application are deployed to the`nginx`namespace\. - **`createNamespace = true`**: Nixidy generates a`Namespace`manifest automatically\. Without this, you'd need to create the namespace out\-of\-band\. - **`resources\.deployments\.nginx`**: A typed Deployment\. The`spec`attribute follows the[Kubernetes Deployment spec](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/deployment-v1/), but enforced at Nix evaluation time\. - **`resources\.services\.nginx`**: A typed Service, same idea\. Why not just write the YAML? Two reasons\. 1. Type errors become build errors\. Set`replicas = "two"`in the module above and`nixidy build`fails immediately, not 15 minutes into a deployment rollout\. 2. Composition\. When you add a`prod\.nix`that imports this same module and sets`replicas = lib\.mkForce 10`, you're expressing "same app, different scale" in two lines instead of copying an entire YAML file and changing one number\. The NixOS module system \(`imports`,`lib\.mkDefault`,`lib\.mkForce`\) gives you this for free, and it's the same mechanism that handles multi\-environment NixOS configs\. ## Build the manifests Run the build: ``` nix run github:arnarg/nixidy -- build .#dev ``` info The first run downloads nixidy and its dependencies into the Nix store\. Subsequent runs are instant if nothing changed\. Inspect the output: You should see: ``` result/ ├── apps/ │ └── Application-nginx.yaml └── nginx/ ├── Deployment-nginx.yaml ├── Namespace-nginx.yaml └── Service-nginx.yaml ``` Look at the generated Deployment: ``` cat result/nginx/Deployment-nginx.yaml ``` ``` apiVersion: apps/v1 kind: Deployment metadata: name: nginx namespace: nginx spec: replicas: 2 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - image: nginx:1.25.1 name: nginx ports: - containerPort: 80 name: http ``` And the Argo CD`Application`that nixidy generated for you: ``` cat result/apps/Application-nginx.yaml ``` ``` apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: nginx namespace: argocd spec: destination: namespace: nginx server: https://kubernetes.default.svc project: default source: path: ./manifests/dev/nginx repoURL: https://github.com/YOUR_USERNAME/my-cluster.git targetRevision: main ``` Every`applications\.\*`block produces exactly one Argo CD`Application`pointing at the directory containing its rendered manifests\. This is the rendered manifests pattern: Argo CD syncs plain YAML, not templates, not Helm releases, just static files it can diff against the cluster state\. ## Commit the rendered manifests The`nixidy switch`command copies the built manifests into your repository at the`rootPath`you configured: ``` nix run github:arnarg/nixidy -- switch .#dev ``` This creates`\./manifests/dev/`with the same directory tree as`result/`\. Commit and push: ``` git add . git commit -m "Add nginx application via nixidy" git push ``` The rendered YAML is now in your repository\. Argo CD can see it\. ## Deploy to your cluster ### Bootstrap with Argo CD If Argo CD is already running in your cluster, one command creates an "app of apps" \(a parent`Application`that manages all your nixidy applications\): ``` nix run github:arnarg/nixidy -- bootstrap .#dev | kubectl apply -f - ``` This outputs an Argo CD`Application`manifest that points at`manifests/dev/apps/`in your repo\. Argo CD reads that directory, discovers`Application\-nginx\.yaml`, creates the nginx`Application`, which then syncs the Deployment, Service, and Namespace into your cluster\. ### Or: apply directly \(for testing\) If you want to skip Argo CD temporarily, a local`kind`cluster for instance: ``` nix run github:arnarg/nixidy -- apply .#dev ``` This runs`kubectl apply \-\-prune`with the correct label selectors, so resources removed from your nixidy config are also removed from the cluster on the next apply \(if resources have been removed\)\. ## What's next We now have one application in one environment\. Real clusters have a dozen applications across dev, staging, and production and I don't want to copy\-paste the same Deployment into three files\. In[Part 2](https://codedbearder.com/posts/nixidy-part-2-multi-env-and-helm-charts/)we'll refactor the nginx application into a shared module, override`replicas`per environment with`lib\.mkDefault`and`lib\.mkForce`, and integrate a Helm chart without giving up type safety\. \* \* \* [![GitHub Logo](https://codedbearder.com/images/github.svg)](https://github.com/arnarg)[![Linkedin Logo](https://codedbearder.com/images/linkedin.svg)](https://www.linkedin.com/in/arnari)[![Mastodon Logo](https://codedbearder.com/images/mastodon.svg)](https://floss.social/@arnar) © Arnar Gauti Ingason This website is generated with[nixtml](https://github.com/arnarg/nixtml)

Similar Articles

How I like to install NixOS (declaratively)

Michael Stapelberg

A guide on declaratively installing NixOS over the network using tools like nixos-anywhere, with an emphasis on managing configuration files under version control.

NixOS and Secrets

Lobsters Hottest

A tutorial explaining secrets management options for NixOS, comparing tools like sops-nix, agenix, and ragenix, with practical examples of using sops-nix for encrypted secrets management.

Development shells with Nix: four quick examples

Michael Stapelberg

A tutorial demonstrating four ways to set up development shells using Nix, including interactive one-offs, config files, and hermetic Nix Flakes, using GoCV and OpenCV as an example.

Migrating my NAS from CoreOS/Flatcar Linux to NixOS

Michael Stapelberg

Michael Stapelberg details his migration of a NAS from CoreOS/Flatcar Linux to NixOS, covering the step-by-step transition from Docker containers to native NixOS modules with practical examples.

NNN Stack: NixOS, Niri, Noctalia

Lobsters Hottest

The NNN Stack combines NixOS, Niri compositor, and Noctalia shell to create a declarative, scrollable, and reproducible desktop environment, inviting users to contribute their dotfiles.