← all posts

Reproducible everything: my NixOS + devenv stack

12 September 2025 · 6 min read

  • NixOS
  • devenv
  • Devops
  • Flakes

If you’ve heard me talk you know the punchline already, so here’s the architecture instead.

One repo, three surfaces

flake.nix        ← single entrypoint, pinned nixpkgs 26.05
devenv.nix       ← the dev shell (node, postgres, s3, git-hooks)
nixos/           ← production machine configuration

Dev (devenv up), CI (nix flake check + action runners inside the shell), and production (nixos-rebuild switch on a target host) all read the same arm of the flake. The “works on my machine” bug dies here.

devenv does the boring work

devenv gives me:

  • languages.javascript with pinned Node + pnpm, auto pnpm install
  • services (postgres, garage) that start when you enter the shell
  • git-hooks (prettier, typos, eslint) so review isn’t a style discussion

Secrets the boring way

  • Local dev: gitignored .env, loaded by devenv’s dotenv integration
  • Production: sops-nix decrypts age-encrypted secrets into /run/secrets/ at activation — the running service just reads its env var

No plaintext credentials in git history, not even “temporary” ones.

The gotchas you’ll actually hit

  1. Erase your darlings in flake.lock — pin it, commit it, or your CI and prod will diverge on a Monday.
  2. Prisma on NixOS — point the engine binaries at nixpkgs via PRISMA_*_BINARY env vars, don’t let the npm client download its own.
  3. Systemd hardening is free — DynamicUser, StateDirectory, PrivateTmp, and IPAddressDeny=any with explicit allows. Half-day of work, years of fewer 3am pages.