The “Works On My Machine” Problem

We’ve all been there:

Developer: “It works fine on my machine!” DevOps: “Well, it’s broken in production.”

The culprits are usually:

  • Different package versions
  • Missing dependencies
  • Environment variables
  • OS differences
  • System libraries

Traditional solutions (Docker, virtual machines) help but don’t solve the root problem: deterministic, reproducible builds.

Enter Nix

Nix is a purely functional package manager. Every package build is:

  • Deterministic: Same inputs always produce the same output
  • Isolated: Packages don’t interfere with each other
  • Reproducible: Build once, run anywhere

How Nix Works

The Nix Store

Everything lives in /nix/store/:

/nix/store/a7s9dk1...-python-3.11.5/
/nix/store/x9sk2f...-nodejs-18.17.0/
/nix/store/m2kd9s...-postgresql-15.3/

Each package has a unique hash based on its inputs: source code, dependencies, build scripts, compiler flags.

Change any input → different hash → different package.

Declarative Environments

Instead of apt install or brew install, you declare your environment:

# shell.nix
{ pkgs ? import <nixpkgs> {} }:

pkgs.mkShell {
  buildInputs = with pkgs; [
    go_1_21
    postgresql_15
    nodejs_18
    bazel_6
  ];

  shellHook = ''
    echo "Development environment loaded!"
    export DATABASE_URL="postgresql://localhost/myapp"
  '';
}

Enter the environment:

nix-shell
# All tools are now available
go version    # go1.21
node --version  # v18.17.0

Real-World Use Case

Before Nix

Our team’s development setup:

# Install Homebrew (macOS) or apt (Linux)
# Install Go
# Install Node.js
# Install PostgreSQL
# Set environment variables in .bashrc/.zshrc
# Hope everyone has compatible versions

Problems:

  • Takes 30-60 minutes to onboard new developers
  • Version mismatches cause mysterious bugs
  • “Did you update your Go version?”
  • macOS and Linux developers have different setups

After Nix

git clone repo
cd repo
nix-shell
# Done. Identical environment for everyone.

Benefits:

  • Onboarding takes 5 minutes
  • Guaranteed version consistency
  • Works identically on macOS and Linux
  • No system pollution - everything in /nix/store

Beyond Development Environments

CI/CD

Same shell.nix works in CI:

# .github/workflows/test.yml
- name: Run tests
  run: |
    nix-shell --run "make test"

No more “works in CI but not locally” (or vice versa).

Production Builds

Build production artifacts with Nix:

{ pkgs ? import <nixpkgs> {} }:

pkgs.buildGoModule {
  name = "myapp";
  src = ./.;
  vendorHash = "sha256-...";
}

The resulting binary is bit-for-bit reproducible. Build it today, build it next year with the same Nix configuration → identical output.

Docker Images

Generate minimal Docker images:

pkgs.dockerTools.buildLayeredImage {
  name = "myapp";
  contents = [ myapp ];
  config = {
    Cmd = [ "/bin/myapp" ];
  };
}

No base image needed. Just your application and its dependencies.

The Learning Curve

I won’t lie - Nix has a steep learning curve:

Week 1: “What is this functional programming syntax?” Week 2: “Why are there so many different tools? (nix-shell, nix-build, flakes…)” Week 3: “I think I’m starting to get it…” Week 4: “This is actually amazing!”

Tips for Learning

  1. Start with nix-shell: Don’t try to learn everything at once
  2. Use nixpkgs search: https://search.nixos.org/packages
  3. Read others’ configs: GitHub is full of examples
  4. Join the community: NixOS Discourse, Reddit r/NixOS
  5. Be patient: The payoff is worth it

Common Pitfalls

1. Impure Builds

Nix builds are sandboxed - they can’t access the network or your home directory (by default).

Problem: Build scripts that download dependencies at build time Solution: Use fixed-output derivations or fetchurl

2. Binary Caches

Building from source is slow. Use Hydra (Nix’s binary cache):

substituters = [ "https://cache.nixos.org" ];

3. macOS Differences

Some packages work differently on macOS (especially with Apple Silicon).

Solution: Check nixpkgs issues, sometimes you need overrides

Our Team’s Nix Setup

# shell.nix
{ pkgs ? import <nixpkgs> { } }:

pkgs.mkShell {
  buildInputs = with pkgs; [
    # Backend
    go_1_21
    golangci-lint

    # Frontend
    nodejs_18
    yarn

    # Infrastructure
    kubectl
    helm
    terraform

    # Database
    postgresql_15
    redis

    # Tools
    jq
    yq-go
    protobuf
    bazel_6
  ];

  shellHook = ''
    export GOPATH=$PWD/.go
    export PATH=$GOPATH/bin:$PATH

    # Set up pre-commit hooks
    if [ ! -f .git/hooks/pre-commit ]; then
      ln -s ../../scripts/pre-commit.sh .git/hooks/pre-commit
    fi

    echo "🚀 Development environment ready!"
  '';
}

Every developer gets:

  • Exact same tool versions
  • Properly configured PATH
  • Environment variables set
  • Pre-commit hooks installed

Nix Flakes

The future of Nix is Flakes - a new system for hermetic, reproducible Nix projects.

# flake.nix
{
  description = "My development environment";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.05";
  };

  outputs = { self, nixpkgs }:
    let
      system = "x86_64-linux";
      pkgs = nixpkgs.legacyPackages.${system};
    in {
      devShells.${system}.default = pkgs.mkShell {
        buildInputs = with pkgs; [
          go
          nodejs
        ];
      };
    };
}

Flakes lock dependencies (like package-lock.json) for even better reproducibility.

Is Nix Right For You?

Use Nix if:

  • You work on teams with different OSs
  • “Works on my machine” bugs plague you
  • You value reproducibility
  • You’re willing to invest learning time
  • You want declarative infrastructure

Skip Nix if:

  • You’re a solo developer with simple needs
  • You’re satisfied with Docker for development
  • You need a solution RIGHT NOW (learning curve is real)
  • Your team isn’t ready for the paradigm shift

The Bottom Line

Nix solved real problems for our team:

  • ✅ Eliminated environment setup issues
  • ✅ Made onboarding trivial
  • ✅ Guaranteed dev/prod parity
  • ✅ Enabled reproducible builds

But it required investment:

  • ⏱️ Learning curve for the team
  • 📚 Documentation isn’t always beginner-friendly
  • 🐛 Occasional edge cases with packages

For us, the trade-off was absolutely worth it. Your mileage may vary.

Resources

If you’re tired of environment issues and want true reproducibility, give Nix a try.

It might just change how you think about software development.


Have questions about Nix or want to share your setup? Let’s talk!