Nix for DevOps — Reproducible Development Environments Complete Guide (2026)
Complete guide to using Nix and Nix flakes for reproducible DevOps environments. Covers installation, dev shells, CI/CD integration, Docker image building with Nix, and team adoption strategies.
"Works on my machine" is a joke. "Works in my Nix shell" is a guarantee.
Every DevOps team has this problem: developer A has Terraform 1.7, developer B has 1.8. The CI pipeline uses 1.6. Nobody's kubectl version matches the cluster. Python dependencies conflict. One engineer's Node.js version breaks the build.
Nix solves this completely. It's a package manager and build system that guarantees reproducible environments — the same tools, same versions, same configuration, everywhere. Every time.
What Is Nix?
Nix is three things:
- A package manager — like apt or brew, but reproducible. Every package is stored with its exact dependency tree, so two versions of the same tool never conflict.
- A build system — can build any software reproducibly from source
- A configuration language — the Nix language describes packages, environments, and systems
The key insight: Nix stores everything in /nix/store with content-addressed paths:
/nix/store/abc123-terraform-1.7.5/
/nix/store/def456-terraform-1.8.2/
Both versions coexist. No conflicts. No PATH hacks. No version managers.
Installing Nix
# Linux/macOS (multi-user installation, recommended)
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
# Verify
nix --versionEnable flakes (modern Nix interface):
# Already enabled by Determinate installer
# If not, add to ~/.config/nix/nix.conf:
echo "experimental-features = nix-command flakes" >> ~/.config/nix/nix.confUse Case 1: Dev Shell — One Command, All Tools
Create a flake.nix in your project root:
{
description = "DevOps project environment";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
in
{
devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [
# Infrastructure
terraform
opentofu
ansible
packer
# Kubernetes
kubectl
kubernetes-helm
kustomize
argocd
k9s
# Cloud CLIs
awscli2
google-cloud-sdk
# Languages
go_1_22
nodejs_20
python312
python312Packages.pip
# Tools
jq
yq
grpcurl
dive # Docker image analyzer
trivy # Security scanner
pre-commit
];
shellHook = ''
echo "DevOps environment loaded"
echo "Terraform: $(terraform version -json | jq -r '.terraform_version')"
echo "kubectl: $(kubectl version --client -o json | jq -r '.clientVersion.gitVersion')"
echo "Helm: $(helm version --short)"
'';
};
});
}Now any developer runs:
cd my-project
nix developAnd gets the exact same tools at the exact same versions. No "please install terraform 1.7.5." No "make sure you have the right kubectl." It just works.
Use Case 2: Pin Exact Versions
Need specific versions? Pin them:
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
# Pin to a specific commit for exact versions
nixpkgs-terraform.url = "github:NixOS/nixpkgs/abc123def";
};
outputs = { self, nixpkgs, nixpkgs-terraform, ... }:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
pkgs-tf = nixpkgs-terraform.legacyPackages.${system};
in
{
devShells.${system}.default = pkgs.mkShell {
buildInputs = [
pkgs-tf.terraform # Exact version from pinned commit
pkgs.kubectl
pkgs.kubernetes-helm
];
};
};
}The flake.lock file (auto-generated) records the exact commit hash for every input. Commit this to Git and every developer gets identical versions forever.
Use Case 3: direnv Integration — Automatic Shell Activation
Nobody wants to remember to run nix develop. Use direnv to auto-activate:
# Install direnv
nix profile install nixpkgs#direnv
# Add to your shell rc
echo 'eval "$(direnv hook bash)"' >> ~/.bashrcCreate .envrc in your project:
use flakeNow when you cd into the project, the Nix shell activates automatically. When you leave, it deactivates. Zero friction.
$ cd ~/projects/infra
direnv: loading .envrc
direnv: using flake
DevOps environment loaded
Terraform: 1.7.5
kubectl: v1.29.3
Helm: v3.14.2
$ cd ~
direnv: unloadingUse Case 4: Build Docker Images with Nix
Nix can build minimal, reproducible Docker images without a Dockerfile:
# flake.nix (add to outputs)
packages.${system}.docker-image = pkgs.dockerTools.buildLayeredImage {
name = "my-app";
tag = "latest";
contents = [
pkgs.bash
pkgs.coreutils
pkgs.curl
self.packages.${system}.my-app # Your built application
];
config = {
Cmd = [ "/bin/my-app" ];
ExposedPorts = { "8080/tcp" = {}; };
Env = [ "PORT=8080" ];
};
};nix build .#docker-image
docker load < result
docker run -p 8080:8080 my-app:latestBenefits over Dockerfile:
- Reproducible — same image every time, bit-for-bit
- Minimal — only includes exactly what you specify, no base image bloat
- No layer caching issues — Nix handles caching at the package level
- Auditable — you can trace every byte in the image back to its source
Use Case 5: CI/CD with Nix
GitHub Actions with Nix:
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: DeterminateSystems/nix-installer-action@main
- uses: DeterminateSystems/magic-nix-cache-action@main
- name: Build
run: nix build
- name: Test
run: nix develop --command npm test
- name: Lint
run: nix develop --command npm run lint
- name: Build Docker image
run: nix build .#docker-imageThe magic-nix-cache action caches Nix store paths, so subsequent builds are fast.
The killer feature: your CI uses the exact same tool versions as your local dev environment. No more "tests pass locally but fail in CI" because of version differences.
Use Case 6: Multi-Environment Shells
Different projects need different tools. Define multiple dev shells:
devShells = {
default = pkgs.mkShell {
buildInputs = [ pkgs.terraform pkgs.kubectl ];
};
aws = pkgs.mkShell {
buildInputs = [
pkgs.terraform
pkgs.awscli2
pkgs.aws-vault
pkgs.eksctl
];
};
gcp = pkgs.mkShell {
buildInputs = [
pkgs.terraform
pkgs.google-cloud-sdk
pkgs.kubectl
];
};
python = pkgs.mkShell {
buildInputs = [
pkgs.python312
pkgs.python312Packages.boto3
pkgs.python312Packages.requests
pkgs.python312Packages.pytest
];
};
};nix develop .#aws # AWS-specific tools
nix develop .#gcp # GCP-specific tools
nix develop .#python # Python developmentNix vs Alternatives
| Feature | Nix | Docker Dev | asdf | Homebrew |
|---|---|---|---|---|
| Reproducible | Exact, bit-for-bit | Nearly (layer cache) | Version pinning only | No |
| Cross-platform | Linux, macOS | Linux containers | Linux, macOS | macOS (mainly) |
| No containers needed | Yes | No | Yes | Yes |
| Offline capable | Yes (with cache) | Yes (with images) | Partial | No |
| Dependency isolation | Full | Full (container) | Partial | No |
| Build system | Yes | Yes | No | No |
| Learning curve | Steep | Moderate | Low | Low |
Nix's biggest weakness is the learning curve. The Nix language is functional and unfamiliar. But once you grok it, the payoff is enormous.
Getting Your Team to Adopt Nix
The biggest adoption barrier is team buy-in. Here's a practical strategy:
Week 1: Provide the flake
- Add
flake.nixand.envrcto your project - Don't require anyone to use it yet
- Just say "if you want, run
nix developand everything's set up"
Week 2: Show the pain it removes
- When someone hits a version mismatch bug, fix it with Nix
- When CI breaks due to tool differences, show how Nix prevents it
Week 3: Make it the default
- Update the README: "Recommended: Use Nix for development setup"
- Add
nix develop --commandto CI pipelines
Week 4: Make it the standard
- Deprecate manual setup instructions
- New team members use
nix developfrom day one - Document only the Nix way
Common Gotchas
1. First download is slow Nix downloads and builds packages the first time. Use binary caches:
# These are configured by default with Determinate installer
nixConfig.substituters = [ "https://cache.nixos.org" ];2. Disk space
Nix keeps all versions in /nix/store. Run garbage collection:
nix-collect-garbage -d # Remove old generations3. macOS quirks Some tools need platform-specific handling:
buildInputs = with pkgs; [
kubectl
] ++ pkgs.lib.optionals pkgs.stdenv.isDarwin [
pkgs.darwin.apple_sdk.frameworks.Security
];Getting Started
For the foundational DevOps skills that Nix environments support, KodeKloud has hands-on courses covering Terraform, Kubernetes, and the full DevOps toolkit.
If you need a cloud server to experiment with Nix and NixOS, DigitalOcean droplets are an affordable option — $6/month gets you a Linux VM to try NixOS as a full server OS.
"Works on my machine" stops being a problem when everyone's machine is defined in a single flake.nix.
Stay ahead of the curve
Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.
Related Articles
AI Coding Assistants Will Change DevOps — But Not in the Way You Think
GitHub Copilot, Cursor, and Claude are already writing infrastructure code. But the real disruption isn't replacing DevOps engineers — it's reshaping what the job actually is.
Ansible vs Terraform: Which One Should You Use? (2026)
Ansible and Terraform are both called 'IaC tools' but they solve completely different problems. Here's when to use each — and when to use both.
GitLab CI Pipeline Keeps Failing? Here's How to Debug and Fix It
GitLab CI pipelines fail for dozens of reasons. This guide walks through the most common errors — from Docker-in-Docker issues to missing variables — and shows you exactly how to fix them.