nixidy part 1: Introduction to nixidy | codedbearder
nixidy part 1: Introduction to nixidy
Posted on Jun 8, 2026
#nixidy
#nix
#kubernetes
#gitops
#argocd
#tutorial
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<br>you're deploying and what actually lands in the cluster is exactly what nixidy<br>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<br>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<br>manifests automatically, and deploys to your cluster through GitOps.
What you'll build
We're going to build a nixidy environment called dev<br>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<br>installed with flakes enabled (download)
A Kubernetes cluster<br>with Argo CD<br>installed
A Git repository<br>for your Kubernetes manifests (GitHub, GitLab, etc.)
Basic familiarity<br>with Kubernetes Deployments, Services, and Namespaces
Basic familiarity<br>with Argo CD Application<br>resources
info
nixidy implements the Rendered Manifests Pattern<br>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<br>is an integer, image<br>is a string, and a typo in selector<br>is a build error, not a runtime surprise.
Let's start by creating the project:
mkdir my-cluster && cd my-cluster<br>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 = {<br>nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";<br>flake-utils.url = "github:numtide/flake-utils";<br>nixidy.url = "github:arnarg/nixidy";<br>};
outputs = {<br>nixpkgs,<br>flake-utils,<br>nixidy,<br>...<br>}:<br>flake-utils.lib.eachDefaultSystem (system: let<br>pkgs = import nixpkgs {inherit system;};<br>in {<br>nixidyEnvs = nixidy.lib.mkEnvs {<br>inherit pkgs;<br>envs.dev.modules = [ ./env/dev.nix ];<br>};<br>});
A couple things worth noting:
nixidy.lib.mkEnvs<br>takes a set of named environments and returns Nix derivations that build YAML manifests. The key dev<br>becomes the attribute you reference with .#dev.
Each environment takes a list of NixOS-style modules which are plain .nix<br>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:
mkdir -p env
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";<br>nixidy.target.branch = "main";<br>nixidy.target.rootPath = "./manifests/dev";
applications.nginx = {<br>namespace = "nginx";<br>createNamespace = true;
resources = {<br>deployments.nginx.spec = {<br>replicas = 2;<br>selector.matchLabels.app = "nginx";<br>template = {<br>metadata.labels.app = "nginx";<br>spec.containers.nginx = {<br>image = "nginx:1.25.1";<br>ports.http.containerPort = 80;<br>};<br>};<br>};
services.nginx.spec = {<br>selector.app = "nginx";<br>ports.http.port = 80;<br>};<br>};<br>};
Let me walk through what this declares:
nixidy.target.* : Where the generated YAML ends up in your Git repo. Argo CD Application<br>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<br>manifest.
namespace = "nginx" : All resources in this application are deployed to the nginx<br>namespace.
createNamespace = true : Nixidy generates a Namespace<br>manifest automatically. Without this, you'd need to create the namespace out-of-band.
resources.deployments.nginx : A typed...