nixosnixlinuxdotfilesproductivity
Why NixOS and Nix, is it just hype?
← All postsTired of 'it works on my machine'? Discover how Nix and NixOS solve dependency hell with declarative configuration and reproducible builds. Docker meets Infrastructure as Code - but is it revolutionary or just hype?
16 min read
Introduction
"It works on my machine."
How many times have you heard this phrase? Or worse, how many times have you said it?
Picture this: You're two months into a project. Everything runs perfectly on your laptop. You push to production and disaster strikes. Wrong library version. Missing environment variable. Or your colleague just installed packages differently than you did.
The usual fix? Docker containers, virtual environments, documentation nobody reads, and hours of debugging. Been there way too many times.
But there's a system that solves this at its core: NixOS and Nix.
The question is: Is this another Linux trend, or is there something genuinely revolutionary here?
Let's find out.
What is Nix and NixOS?
Two things exist here: Nix (the package manager) and NixOS (the operating system). You can use one without the other.
Nix: The Package Manager
Nix builds packages in complete isolation from each other. If a package works on one machine, it will work on another. No exceptions.
Instead of dumping packages into /usr/bin like every other package manager, Nix stores everything in /nix/store with a unique cryptographic hash:
/nix/store/nawl092prjblbhvv16kxxbk6j9gkgcqm-git-2.47.1
/nix/store/x8dhcf2kpl5h4lm9ci3k2y9vn8qlx9cd-nodejs-24.1.0
That random string? It's a hash of everything used to build the package — source files, dependencies, compiler flags, all of it. Change anything, get a different hash, get a different package. I know, sounds nerdy but stick with me.
The Nix Formula
Think of it as a deterministic build process where specific inputs always generate the exact same, immutable output:
SOURCE CODE + DEPENDENCIES + BUILD SCRIPT
↓
NIX HASH FUNCTION
↓
UNIQUE STORE PATH: /nix/store/x8dhkfewprojgihtrmvcf2...-nodejs-24.1.0
- Input A:
git-source+openssl-3.0→ Result:..5yn92-git - Input B:
git-source+openssl-3.1→ Result:..fx23f-git
Even a tiny security patch in a dependency (like moving from OpenSSL 3.0 to 3.1) creates a completely new, isolated path. Your old version stays untouched, and the new one doesn't overwrite it.
So yeah — Node 20 and Node 24 can coexist. Go 1.24 and 1.25 on the same system. Python 3.9 and 3.11 simultaneously. No conflicts. Ever. Each project gets exactly what it needs.
NixOS: The Operating System
NixOS extends this concept to your entire system. Everything — kernel, services, packages, configs — lives in declarative files.
Traditional approach (imperative):
sudo apt install nginx
sudo systemctl enable nginx
# manually edit configs
# hope you documented this
# pray nothing breaks
NixOS approach (declarative):
{
services.nginx.enable = true;
services.nginx.virtualHosts."example.com" = {
root = "/var/www";
};
}
Run sudo nixos-rebuild switch and your system becomes that description. That's it.
Immutability: The Core Principle
The /nix/store is read-only and immutable. Nothing gets deleted or modified — Nix creates new versions and symlinks to them.
This is why rollbacks are instant. Every system state you've ever had is still there. Something breaks? Reboot, select the previous generation, back to working in 30 seconds. I've saved my own setup more times than I'd like to admit this way.
It's Git for your entire operating system. Every change tracked. Every state recoverable. Time travel for your OS.
Garbage Collection: The Smart Cleanup
You're probably thinking: "Won't my disk fill up?"
Traditional Distros: When you run apt remove, packages are gone forever. Want it back? Reinstall. Made a mistake? Too bad. Orphaned dependencies? Hunt them down manually with apt autoremove.
Nix's Intelligence: Nothing gets deleted until you explicitly run garbage collection. And even then, it only removes what's truly unreachable — nothing any generation or project depends on.
# Preview what would be deleted
nix-collect-garbage --dry-run
# Delete unreachable packages
nix-collect-garbage
# Delete old generations
nix-collect-garbage -d
# Delete generations older than 30 days
nix-collect-garbage --delete-older-than 30d
Automate it in NixOS:
{
nix.gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 30d";
};
}
Your last month of generations stays safe while everything older gets cleaned automatically.
The Real Problems Nix Solves
Managing development environments is painful. Let's talk about the actual problems you face daily.
Version Juggling Nightmare
You're juggling three Node.js projects. One needs Node 20, another needs Node 22, the new microservice needs Node 24. You've got nvm but you keep forgetting to switch versions and waste time debugging. (Ask me how I know.)
The usual mess:
- Open terminal → forget
nvm use→ build fails - Install global package for Project A → breaks Project B
- Spend Friday afternoon debugging "but it worked yesterday"
With Nix: Just cd into a project directory. That's it. Nix sees the config and loads the exact environment needed. Automatically.
# Project A - Node 20
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
buildInputs = with pkgs; [
nodejs_20
python311 # for native modules
];
}
Switch projects? Different Node version loads automatically. No thinking required.
"Works on My Machine" Syndrome
Your colleague pushes a TypeScript project. You clone it, run npm install, compilation fails. Different TypeScript version. Missing build tool. Now you're debugging setup instead of coding.
The old way: Write documentation nobody reads. Debug the same issues with every new team member.
The Nix way: Share a flake.nix file. Run one command. Get the exact same setup.
{
description = "TypeScript project";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
outputs = { nixpkgs, ... }: {
devShells.x86_64-linux.default =
let pkgs = nixpkgs.legacyPackages.x86_64-linux;
in pkgs.mkShell {
buildInputs = with pkgs; [
nodejs_24
nodePackages.typescript
nodePackages.eslint
];
};
};
}
Instead of writing instructions for how to build an environment, you're sharing the actual environment itself. Like
package.json+package-lock.jsonbut for your entire development setup, not just JavaScript packages.
CI/CD Inconsistency Hell
Your Go project works locally. Push to CI? Fails. Different environment variables. Slightly different Go version. Manual configuration from months ago nobody remembers.
You end up with three different behaviors: your machine, your coworker's machine, and CI.
Nix fixes this: The environment is code. Same file, same environment everywhere.
{
description = "Go project";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
outputs = { nixpkgs, ... }: {
devShells.x86_64-linux.default =
let pkgs = nixpkgs.legacyPackages.x86_64-linux;
in pkgs.mkShell {
buildInputs = with pkgs; [
go_1_24
golangci-lint
delve
];
shellHook = ''
export GOPATH=$PWD/.go
export PATH=$GOPATH/bin:$PATH
'';
};
};
}
This file goes in your repo. Everyone who uses it — local devs, CI, staging, production — gets the exact same setup. The inconsistency just disappears.
How Does Nix Work? The Magic Behind
Pure Functions: Mathematical Reproducibility
Nix treats every package as a pure function — a mathematical function where the same inputs always produce the exact same output.
inputs (source + dependencies + tools) → cryptographic hash → output package
Nix is designed around reproducibility. While not every package is guaranteed to be bit-for-bit reproducible today, the system enforces strict input purity — making non-reproducible builds the exception, not the rule.
Change anything in the inputs, get a different hash, get a different package.
You literally can't accidentally break reproducibility. The system enforces it mathematically. It's not a convention or a best practice — it's the only way the system works.
The /nix/store: Isolation at Scale
Every package lives in /nix/store with a unique hash:
$ ls /nix/store/ | grep nodejs
3j8d...-nodejs-24.1.0
7k2m...-nodejs-22.11.0
9xp4...-nodejs-20.18.1
Those prefixes are content-addressable hashes representing everything used to build that package. This is why multiple versions coexist without conflicts.
Declarative vs Imperative
Traditional systems are imperative — you tell them what to do step by step:
# Steps that modify state
sudo apt update
sudo apt install nginx
sudo systemctl enable nginx
sudo vim /etc/nginx/nginx.conf
# Hope you documented this
# Pray it works on another machine
Nix is declarative — you describe what you want:
{
services.nginx = {
enable = true;
virtualHosts."myapp.com" = {
root = "/var/www/myapp";
};
};
}
Run nixos-rebuild switch and your system transforms to match that description.
Imperative is "do this, then do that." Declarative is "the system should look like this." One creates state. The other describes state.
Atomic Rollbacks
When you update your system:
- Nix builds new versions
- Creates a new generation
- Symlinks to the new generation
- Keeps all old generations intact
Boot menu shows your history:
NixOS - Generation 47 (current)
NixOS - Generation 46
NixOS - Generation 45
...
Something broke? Boot into the previous generation or run:
sudo nixos-rebuild switch --rollback
30 seconds. Done. No reinstalling packages, no configuration restoration, no database migrations. Just instant time travel.
Nix vs The World: Honest Comparisons
Nix vs Docker: Complementary, Not Competitors
They solve different problems and work great together.
Docker's Strength — Runtime Isolation
Packages your app with everything it needs. Ship the container, runs identically everywhere. Massive ecosystem.
Weaknesses:
- Build process itself isn't reproducible
RUN apt-get installfetches latest versions (not deterministic)- Can only inherit from single base image
- Build cache breaks between machines
Nix's Strength — Build Reproducibility
Every package built from exact pinned dependencies. Cryptographic guarantees.
Weaknesses:
- Steeper learning curve (not gonna sugarcoat it)
- Smaller ecosystem
- Less familiar to teams
Here's the cool part — you can use Nix to build reproducible Docker images.
{ pkgs ? import <nixpkgs> {} }:
pkgs.dockerTools.buildLayeredImage {
name = "my-app";
tag = "latest";
contents = with pkgs; [ nodejs_24 bash ];
config.Cmd = [ "${pkgs.nodejs_24}/bin/node" "app.js" ];
}
Nix's reproducibility during build + Docker's portability for deployment. Companies like Replit and Shopify do this.
Nix vs apt/yum/brew: Declarative vs Imperative
| Feature | apt/yum/brew | Nix |
|---|---|---|
| Approach | Imperative (do this, then that) | Declarative (system should be this) |
| Reproducibility | None — state drifts over time | Perfect — same config = same system |
| Multiple Versions | Conflicts | Coexist peacefully |
| Rollbacks | None | Instant (30 seconds) |
| Version Control | Manual documentation | Git-based configs |
Traditional package managers modify global state. Nix builds deterministic state.
Nix vs Traditional Linux Distros
Ubuntu/Debian
- Best for: General use, "just works" experience
- Strengths: Massive community, everything documented, stable LTS releases
- Weaknesses: State drifts, updates break things, no atomic rollbacks
Arch Linux
- Best for: Cutting-edge packages, DIY approach
- Strengths: Latest software, extensive wiki, complete control
- Weaknesses: Manual everything, breaks occasionally, no safety net
Fedora
- Best for: Modern tech, balanced approach
- Strengths: Latest features, Red Hat backing, SELinux integration
- Weaknesses: 6-month release cycle, traditional package management
NixOS
- Best for: Reproducibility, declarative infrastructure
- Strengths: Atomic rollbacks, perfect reproducibility, version control everything
- Weaknesses: Steep learning curve, smaller community, weird syntax (seriously, functional programming in a config file?)
Nix vs Language-Specific Tools
Every language has version managers: nvm, pyenv, rbenv, gvm.
Problems:
- Different tool for each language
- Don't manage system dependencies
- Inconsistent across projects
Nix replaces all of them:
{
description = "Multi-language project";
outputs = { nixpkgs, ... }: {
devShells.x86_64-linux.default =
let pkgs = nixpkgs.legacyPackages.x86_64-linux;
in pkgs.mkShell {
buildInputs = with pkgs; [
nodejs_24 # JavaScript
python311 # Python
go_1_24 # Go
rustc # Rust
postgresql_16 # Database
redis # Cache
];
};
};
}
One tool. All languages. System dependencies included. Completely reproducible.
Key Killer Features
1. True Reproducibility (Not Just Claims)
Other systems claim reproducibility. Nix enforces it mathematically.
Same
flake.lock= identical environment everywhere. Not "similar." Not "should work." Identical. Cryptographically guaranteed.
2. Time Travel for Your System
Every change creates a generation. Every generation is recoverable.
# List all generations
nix-env --list-generations
# Rollback to previous
sudo nixos-rebuild switch --rollback
# Switch to specific generation
sudo nixos-rebuild switch --rollback 42
Broke something? You're 30 seconds away from a working state. Always.
3. Configuration as Code (For Real)
Not "infrastructure as code" aspirationally. Actually as code.
# This IS your system. Not a description. The actual state.
{
services.nginx.enable = true;
services.postgresql.enable = true;
services.redis.enable = true;
networking.firewall.allowedTCPPorts = [ 80 443 ];
users.users.deploy = {
isNormalUser = true;
extraGroups = [ "wheel" ];
};
}
Check this into Git. Every machine built from this file is identical.
4. Development Environments That Actually Work
Share a flake.nix. Everyone gets the exact environment. Not documentation. Not instructions. The actual environment.
New teammate?
git clone→nix develop→ coding in 2 minutes. No setup docs. No "it works on my machine." Just instant productivity.
5. Binary Caching
Build once. Share everywhere. Teams using Cachix build packages once and everyone else downloads pre-built binaries.
First developer builds? 20 minutes. Everyone else? 30 seconds.
Real-World Use Cases
Development Environments
Problem: New developer spends two days installing tools, debugging versions, configuring environment.
Solution: One file, one command.
{
description = "Backend API dev environment";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
outputs = { nixpkgs, ... }: {
devShells.x86_64-linux.default =
let pkgs = nixpkgs.legacyPackages.x86_64-linux;
in pkgs.mkShell {
buildInputs = with pkgs; [
nodejs_24 python311 go_1_24
postgresql_16 redis
docker-compose kubectl
];
shellHook = ''
export DATABASE_URL="postgresql://localhost/myapp"
echo "Dev environment loaded!"
'';
};
};
}
Real companies: Replit, Shopify, Tweag use this for instant reproducible dev environments.
Production Servers
Problem: 50 servers. One configured differently. Behaves differently. Nobody knows why.
Solution: Entire server config in declarative file checked into Git.
nixos-rebuild switch --target-host api-prod-01
All 50 servers identical. Changes version-controlled. Rollback any change instantly.
Personal Dotfiles with Home Manager
Problem: Three machines with different configs. Syncing manually is tedious.
Solution: Declare your entire user environment.
{ pkgs, ... }: {
home.packages = with pkgs; [
neovim tmux git ripgrep fzf bat
];
programs.git = {
enable = true;
userName = "Your Name";
userEmail = "you@example.com";
};
programs.zsh = {
enable = true;
shellAliases = {
ll = "ls -la";
gs = "git status";
};
};
}
New laptop? Run a few commands. Five minutes later, entire setup restored perfectly.
Advanced Tools
Flakes: package.json for Your System
Flakes are exactly like package.json + package-lock.json combined — but for your entire development environment.
Traditional Nix: import <nixpkgs> imports whatever version is on your system. Your coworker might have different version. CI might have yet another.
Flakes fix this: flake.lock pins exact versions.
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1704067270,
"narHash": "sha256-...",
"rev": "63678e9f3d3afecfeafa0acead6239cdb54055e4"
}
}
}
}
Result: Same flake.lock = identical environment everywhere. No surprises. Ever.
Flakes are still marked "experimental" but widely used in production. The Nix community considers them de facto stable after years with minimal breaking changes.
Home Manager: Version Control Your Workspace
Manage your entire user environment declaratively. Not just packages — everything. Editor configs, shell aliases, Git settings, SSH keys.
Changed your config? home-manager switch applies it instantly. Don't like it? home-manager switch --rollback reverts it.
It's literally version control for your entire workspace.
The Dark Side: Honest Challenges
Let's be real about the problems.
The Learning Curve is Brutal
Reality check: Nix has one of the steepest learning curves in Linux.
- First week: "What is this syntax?"
- First month: "I think I'm getting it... maybe"
- First six months: "Now I actually understand"
You'll hit walls. Get frustrated. Question if it's worth it. I definitely did. Spent an entire weekend debugging why my shell wouldn't source properly. Turned out I forgot a semicolon. This is normal.
Documentation is Fragmented
No single source of truth. Information scattered. Examples don't always work with current versions. You'll spend hours debugging something better docs could've solved in minutes.
The Ecosystem is Smaller
Ubuntu has millions of users. NixOS? Significantly smaller community.
In practice, that looks like:
- Fewer Stack Overflow answers
- Less corporate support
- Gaming is harder (though improving)
- Hardware compatibility can be tricky
When You Shouldn't Use NixOS
Don't use NixOS if:
- You're new to Linux — Start with Ubuntu or Fedora. Learn basics first.
- You need "just works" gaming — Pop!_OS or Arch with Steam will be smoother.
- You're on tight deadlines — Don't learn NixOS while building production systems.
- Your team doesn't buy in — If you're the only one using Nix, you're creating silos.
Build Times Can Be Painful
First build might take forever. Updating your system could take 30 minutes if lots changed. Binary caches help, but that first experience is brutal. Pro tip: don't start a system rebuild right before a meeting.
Getting Started
Step 1: Install Nix (Not NixOS)
Start with just the Nix package manager on your existing system. Don't jump into full NixOS immediately.
Recommended installer (enables Flakes by default):
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
Verify:
nix --version
Step 2: Try It Out
# Try cowsay temporarily
nix shell nixpkgs#cowsay
cowsay "Hello Nix!"
# Install permanently
nix profile install nixpkgs#firefox
Step 3: Create Your First Dev Environment
{
description = "My first Nix environment";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
outputs = { nixpkgs, ... }: {
devShells.x86_64-linux.default =
let pkgs = nixpkgs.legacyPackages.x86_64-linux;
in pkgs.mkShell {
buildInputs = with pkgs; [ nodejs_24 python311 ];
};
};
}
Use it:
git init
git add flake.nix
nix develop
Perfect development environment. That's it.
Step 4: Learn More
- zero-to-nix.com — Best beginner guide. Start here.
- nix.dev — Official tutorials and docs.
- NixOS Wiki — Community knowledge base.
- My complete NixOS setup — Real config with Flakes and Home Manager.
Step 5: Consider NixOS Later
After you're comfortable with Nix for a few weeks, try NixOS in a VM first. Don't rush this step. NixOS is a commitment.
Conclusion: Is It Worth The Hype?
Honestly? It depends.
When The Hype is Real
If you've ever:
- Spent hours debugging "it works on my machine"
- Lost weekends to broken updates
- Wished you could time travel to a working state
- Struggled to replicate environments across teams
Then yes, Nix is revolutionary. The reproducibility actually works. The atomic rollbacks actually work. The declarative configuration actually works.
When The Hype Doesn't Matter
But if you just need a stable desktop that works and don't care about reproducibility, then the hype doesn't apply to you. And that's okay. Ubuntu and Fedora serve most people perfectly.
The Real Value
Nix isn't about being trendy. It's about reliability through mathematics. Your system becomes a pure function: same inputs → same outputs, guaranteed.
For teams building production systems or requiring strict reproducibility, Nix provides value that's hard to quantify. The initial investment pays dividends daily.
Don't adopt Nix because it's trendy. Adopt it because it solves problems you actually have.
Try it. Break it. Learn from it. Then decide for yourself. That's what I did anyway.
The best part? With Nix, you can always roll back.