π§ Nix Flakes β Development Environment Deep Dive
A progressive guide from Newbie β Expert for setting up reproducible, automated development environments with Nix Flakes.
Table of Contents
Open Table of Contents
- 1. Why Nix Flakes for Dev Environments?
- 2. Prerequisites & Installation
- 3. Core Concepts
- 4. Progressive Implementation Levels
- 5. Project Recipes
- 6. Tool Integration
- 7. Auto-Setup: direnv + nix-direnv
- 8. Developer Experience Enhancers
- 9. Cheatsheet
- 10. Troubleshooting
- 11. Open Questions & Brainstorm
- References
1. Why Nix Flakes for Dev Environments?
| Problem | Nix Flakes Solution |
|---|---|
| βWorks on my machineβ | Deterministic, hash-locked dependencies via flake.lock |
| Onboarding takes hours/days | cd project && direnv allow β done |
| Conflicting global tool versions | Each project gets its own isolated toolchain |
| Broken system after updates | Atomic rollbacks, garbage collection |
| CI/CD environment drift | Same flake.lock = same tools everywhere |
Mental Model
flake.nix β What you WANT (declarative)
flake.lock β What you GOT (pinned, reproducible)
nix develop β Activate the environment (imperative entry)
direnv + .envrc β Activate AUTOMATICALLY on cd (magic β¨)
2. Prerequisites & Installation
Install Nix (multi-user, with flakes)
# Determinate Nix Installer (recommended β flakes enabled by default)
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
# Or official installer + enable flakes manually
sh <(curl -L https://nixos.org/nix/install) --daemon
If using the official installer, enable flakes:
# ~/.config/nix/nix.conf
experimental-features = nix-command flakes
Install direnv + nix-direnv
# Via Nix itself
nix profile install nixpkgs#direnv nixpkgs#nix-direnv
# Hook into your shell (~/.bashrc or ~/.zshrc)
eval "$(direnv hook bash)" # or zsh
Add to ~/.config/direnv/direnvrc:
source $HOME/.nix-profile/share/nix-direnv/direnvrc
VS Code Extension
Install the direnv extension (mkhl.direnv) so VS Code respects the Nix environment.
Also recommended: Nix IDE (jnoortheen.nix-ide) for syntax highlighting & LSP.
3. Core Concepts
flake.nix Anatomy
{
description = "My project dev environment";
# INPUTS β dependencies (pinned in flake.lock)
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
};
# OUTPUTS β what this flake provides
outputs = { self, nixpkgs, ... }:
let
# Helper to support multiple systems (x86_64-linux, aarch64-darwin, etc.)
eachSystem = f:
nixpkgs.lib.genAttrs
[ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]
(system: f nixpkgs.legacyPackages.${system});
in
{
devShells = eachSystem (pkgs: {
default = pkgs.mkShell {
packages = [ /* tools here */ ];
shellHook = ''
echo "π Dev environment ready!"
'';
};
});
};
}
Key Commands
| Command | Purpose |
|---|---|
nix develop | Enter the default devShell |
nix develop .#backend | Enter a named devShell |
nix flake update | Update all inputs (rewrites flake.lock) |
nix flake lock --update-input nixpkgs | Update only nixpkgs |
nix flake show | Show what this flake provides |
nix flake check | Run checks/tests defined in the flake |
4. Progressive Implementation Levels
Level 0 β Newbie: Hello Nix Shell
Goal: Understand that Nix can give you tools without installing them globally.
# Try a tool without installing it
nix shell nixpkgs#hello nixpkgs#cowsay
hello
cowsay "I'm using Nix!"
exit # tools are gone
# Run a one-off command
nix shell nixpkgs#nodejs --command node --version
Key takeaway: Nix provides isolated, temporary environments. Nothing pollutes your system.
Level 1 β Junior: Project devShell with flake.nix
Goal: Create a
flake.nixthat gives your project all the tools it needs.
# flake.nix β Minimal Node.js project
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
};
outputs = { nixpkgs, ... }:
let
eachSystem = f:
nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed
(system: f nixpkgs.legacyPackages.${system});
in
{
devShells = eachSystem (pkgs: {
default = pkgs.mkShell {
packages = with pkgs; [
nodejs # Node.js runtime
pnpm # Package manager
gitlint # Commit message linter
];
shellHook = ''
echo "π¦ Node $(node --version) | pnpm $(pnpm --version)"
'';
};
});
};
}
# Enter the environment
nix develop
# Or with direnv (auto-activate)
echo "use flake" > .envrc
direnv allow
Key takeaway: One file (flake.nix) declares your entire toolchain. Share it via git.
Level 2 β Senior: Multi-shell, Tools & Automation
Goal: Multiple dev shells, formatter/linter integration, pre-commit hooks, environment variable management.
# flake.nix β Senior level
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
# pre-commit hooks as a flake input
git-hooks = {
url = "github:cachix/git-hooks.nix";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, git-hooks, ... }:
let
supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
eachSystem = f:
nixpkgs.lib.genAttrs supportedSystems
(system: f nixpkgs.legacyPackages.${system});
in
{
# ββ Pre-commit checks ββββββββββββββββββββββββββββββ
checks = eachSystem (pkgs: {
pre-commit-check = git-hooks.lib.${pkgs.system}.run {
src = ./.;
hooks = {
# Nix
nixfmt-rfc-style.enable = true;
# JS/TS
eslint.enable = true;
prettier = {
enable = true;
types_or = [ "javascript" "typescript" "css" "json" "yaml" "markdown" ];
};
# Git
commitizen.enable = true;
# General
check-merge-conflicts.enable = true;
detect-private-keys.enable = true;
};
};
});
# ββ Dev Shells βββββββββββββββββββββββββββββββββββββ
devShells = eachSystem (pkgs: {
# Default β full-stack dev environment
default = pkgs.mkShell {
inherit (self.checks.${pkgs.system}.pre-commit-check) shellHook;
packages = with pkgs; [
# Runtime
nodejs
pnpm
corepack
# Linting / Formatting
dprint # Fast formatter (Rust-based)
nodePackages.eslint
nodePackages.prettier
# Git
pre-commit
gitlint
commitizen
# AWS
awscli2
localstack
# LSP / Editor
nodePackages.typescript-language-server
# Utilities
jq
yq-go
httpie
hyperfine # benchmarking
];
env = {
# AWS LocalStack config
AWS_ACCESS_KEY_ID = "test";
AWS_SECRET_ACCESS_KEY = "test";
AWS_DEFAULT_REGION = "ap-northeast-1";
AWS_ENDPOINT_URL = "http://localhost:4566";
};
};
# Backend only
backend = pkgs.mkShell {
packages = with pkgs; [ nodejs pnpm awscli2 ];
};
# Frontend only
frontend = pkgs.mkShell {
packages = with pkgs; [ nodejs pnpm ];
};
# CI β minimal for pipelines
ci = pkgs.mkShell {
packages = with pkgs; [ nodejs pnpm ];
};
});
# ββ Formatter (nix fmt) ββββββββββββββββββββββββββββ
formatter = eachSystem (pkgs: pkgs.nixfmt-rfc-style);
};
}
.envrc for direnv:
# .envrc
use flake
# Optional: export additional project-specific vars
export BIOME_BINARY=$(which biome 2>/dev/null || true)
# Use the different shells
nix develop # default (full-stack)
nix develop .#backend
nix develop .#frontend
nix develop .#ci
# Format Nix files
nix fmt
Key takeaway: Named shells for different contexts. Pre-commit hooks declared in Nix. Environment variables set declaratively.
Level 3 β Expert: Composable Flake Architecture
Goal: Reusable flake modules, custom overlays, flake-parts, process management, full infrastructure-as-code.
Using flake-parts (Recommended for large projects)
# flake.nix β Expert level with flake-parts
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
flake-parts.url = "github:hercules-ci/flake-parts";
git-hooks = {
url = "github:cachix/git-hooks.nix";
inputs.nixpkgs.follows = "nixpkgs";
};
treefmt-nix = {
url = "github:numtide/treefmt-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
process-compose-flake.url = "github:Platonic-Systems/process-compose-flake";
services-flake.url = "github:juspay/services-flake";
};
outputs = inputs:
inputs.flake-parts.lib.mkFlake { inherit inputs; } {
systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
imports = [
inputs.git-hooks.flakeModule
inputs.treefmt-nix.flakeModule
inputs.process-compose-flake.flakeModule
];
perSystem = { config, pkgs, system, ... }: {
# ββ Formatting (treefmt) βββββββββββββββββββββββ
treefmt = {
projectRootFile = "flake.nix";
programs = {
nixfmt.enable = true; # Nix
prettier.enable = true; # JS/TS/CSS/JSON/YAML/MD
# dprint.enable = true; # Alternative: faster
};
};
# ββ Pre-commit hooks ββββββββββββββββββββββββββ
pre-commit.settings.hooks = {
treefmt.enable = true; # Use treefmt for formatting
eslint.enable = true;
commitizen.enable = true;
check-merge-conflicts.enable = true;
detect-private-keys.enable = true;
};
# ββ Process Compose (services) ββββββββββββββββ
process-compose."services" = {
settings.processes = {
localstack = {
command = "localstack start";
readiness_probe.http_get = {
host = "127.0.0.1";
port = 4566;
path = "/_localstack/health";
};
};
backend = {
command = "cd apps/backend && pnpm dev";
depends_on.localstack.condition = "process_healthy";
};
frontend = {
command = "cd apps/frontend && pnpm dev";
};
};
};
# ββ Dev Shell βββββββββββββββββββββββββββββββββ
devShells.default = pkgs.mkShell {
inputsFrom = [
config.pre-commit.devShell # includes hooks
config.treefmt.build.devShell # includes formatters
];
packages = with pkgs; [
# Runtime
nodejs
pnpm
corepack
# AWS
awscli2
localstack
# Infrastructure
terraform
docker-compose
# Utilities
jq yq-go httpie process-compose
];
env = {
AWS_ACCESS_KEY_ID = "test";
AWS_SECRET_ACCESS_KEY = "test";
AWS_DEFAULT_REGION = "ap-northeast-1";
AWS_ENDPOINT_URL = "http://localhost:4566";
};
shellHook = ''
echo ""
echo "ββββββββββββββββββββββββββββββββββββββββββββββββ"
echo "β π Dev Environment Ready β"
echo "β Node: $(node --version) β"
echo "β pnpm: $(pnpm --version) β"
echo "β β"
echo "β Commands: β"
echo "β nix run .#services β start all services β"
echo "β nix fmt β format all files β"
echo "β nix flake check β run all checks β"
echo "ββββββββββββββββββββββββββββββββββββββββββββββββ"
echo ""
'';
};
};
};
}
Custom Overlay β Pin or Patch a Package
# overlays/default.nix
final: prev: {
# Pin Node.js to a specific major version
nodejs = prev.nodejs_22;
# Override dprint to use a specific version
# dprint = prev.dprint.overrideAttrs (old: rec {
# version = "0.45.0";
# src = prev.fetchFromGitHub { ... };
# });
}
# In flake.nix
outputs = { nixpkgs, ... }: {
overlays.default = import ./overlays;
devShells = eachSystem (pkgs:
let myPkgs = pkgs.extend self.overlays.default;
in { default = myPkgs.mkShell { /* ... */ }; }
);
};
Key takeaway: Composable architecture. Services managed via process-compose. Formatters unified via treefmt. Everything is modular and reusable across projects.
5. Project Recipes
Backend β NestJS
# flake.nix
{
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
outputs = { nixpkgs, ... }:
let
eachSystem = f:
nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed
(system: f nixpkgs.legacyPackages.${system});
in {
devShells = eachSystem (pkgs: {
default = pkgs.mkShell {
packages = with pkgs; [
nodejs
pnpm
# NestJS CLI
nodePackages."@nestjs/cli"
# Database
postgresql
# AWS
awscli2
localstack
# Quality
dprint
pre-commit
gitlint
# LSP
nodePackages.typescript-language-server
];
env = {
DATABASE_URL = "postgresql://postgres:postgres@localhost:5432/myapp";
AWS_ACCESS_KEY_ID = "test";
AWS_SECRET_ACCESS_KEY = "test";
AWS_DEFAULT_REGION = "ap-northeast-1";
AWS_ENDPOINT_URL = "http://localhost:4566";
};
shellHook = ''
echo "ποΈ NestJS Backend β Node $(node --version)"
'';
};
});
};
}
Frontend β React / Next.js
# flake.nix
{
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
outputs = { nixpkgs, ... }:
let
eachSystem = f:
nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed
(system: f nixpkgs.legacyPackages.${system});
in {
devShells = eachSystem (pkgs: {
default = pkgs.mkShell {
packages = with pkgs; [
nodejs
pnpm
# Formatting / Linting
biome
nodePackages.prettier
# Image optimization (for Next.js)
sharp
# LSP
nodePackages.typescript-language-server
# Browsers for testing
# playwright-driver.browsers
];
env = {
NEXT_TELEMETRY_DISABLED = "1";
};
shellHook = ''
echo "βοΈ React/Next.js Frontend β Node $(node --version)"
# Ensure biome from Nix is used instead of node_modules
export BIOME_BINARY="$(command -v biome)"
'';
};
});
};
}
Full-Stack Monorepo
my-monorepo/
βββ flake.nix β Root flake with all devShells
βββ flake.lock
βββ .envrc β "use flake"
βββ apps/
β βββ backend/ β NestJS
β β βββ .envrc β "use flake .#backend" (optional per-app shell)
β βββ frontend/ β Next.js
β βββ .envrc β "use flake .#frontend"
βββ packages/ β Shared libraries
βββ infra/ β Terraform / Pulumi
# Root flake.nix for monorepo
{
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
outputs = { nixpkgs, ... }:
let
eachSystem = f:
nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed
(system: f nixpkgs.legacyPackages.${system});
commonPackages = pkgs: with pkgs; [
nodejs
pnpm
dprint
pre-commit
gitlint
jq
];
in {
devShells = eachSystem (pkgs: {
# Full monorepo dev
default = pkgs.mkShell {
packages = (commonPackages pkgs) ++ (with pkgs; [
awscli2
localstack
docker-compose
terraform
]);
};
# Backend only
backend = pkgs.mkShell {
packages = (commonPackages pkgs) ++ (with pkgs; [
awscli2
localstack
postgresql
]);
env.DATABASE_URL = "postgresql://postgres:postgres@localhost:5432/myapp";
};
# Frontend only
frontend = pkgs.mkShell {
packages = (commonPackages pkgs) ++ (with pkgs; [
biome
]);
env.NEXT_TELEMETRY_DISABLED = "1";
};
# CI pipeline (minimal)
ci = pkgs.mkShell {
packages = with pkgs; [ nodejs pnpm ];
};
});
};
}
6. Tool Integration
Linting & Formatting
Option A: dprint (Fast, Rust-based)
packages = with pkgs; [ dprint ];
// dprint.json
{
"plugins": [
"https://plugins.dprint.dev/typescript-0.93.0.wasm",
"https://plugins.dprint.dev/json-0.19.4.wasm",
"https://plugins.dprint.dev/markdown-0.17.8.wasm"
]
}
β οΈ Gotcha: pnpm may install its own dprint in
node_modules/.bin/. UseshellHookto fix:
shellHook = ''
# Force node_modules/.bin/dprint to point to Nix's dprint
NIX_DPRINT_BIN="$(command -v dprint)"
if [ -n "$NIX_DPRINT_BIN" ]; then
mkdir -p ./node_modules/.bin
rm -f ./node_modules/.bin/dprint
ln -s "$NIX_DPRINT_BIN" ./node_modules/.bin/dprint
echo "β
dprint β $(readlink -f ./node_modules/.bin/dprint)"
fi
'';
Option B: Biome (All-in-one linter + formatter)
packages = with pkgs; [ biome ];
shellHook = ''
export BIOME_BINARY="$(command -v biome)"
'';
Option C: treefmt (Unified multi-language formatting)
With treefmt-nix flake input β see Level 3 Expert example.
Pre-commit Hooks
Option A: Nix-native with git-hooks.nix (cachix/git-hooks.nix)
See Level 2 Senior example. Hooks are declared in Nix, installed automatically on nix develop.
Option B: Traditional pre-commit + .pre-commit-config.yaml
packages = with pkgs; [ pre-commit gitlint ];
shellHook = ''
pre-commit install --install-hooks
'';
# .pre-commit-config.yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-merge-conflict
- id: detect-private-key
- repo: https://github.com/commitizen-tools/commitizen
rev: v3.29.0
hooks:
- id: commitizen
- repo: local
hooks:
- id: dprint
name: dprint format
entry: dprint fmt --diff
language: system
pass_filenames: false
AWS LocalStack
packages = with pkgs; [
awscli2
localstack # LocalStack CLI
# docker-compose # if running LocalStack via Docker
];
env = {
AWS_ACCESS_KEY_ID = "test";
AWS_SECRET_ACCESS_KEY = "test";
AWS_DEFAULT_REGION = "ap-northeast-1";
AWS_ENDPOINT_URL = "http://localhost:4566"; # LocalStack default port
};
# Start LocalStack
localstack start -d
# Verify
awslocal s3 mb s3://my-bucket
awslocal s3 ls
# Or use process-compose for orchestration (Expert level)
nix run .#services
Docker / Podman Compose
packages = with pkgs; [
docker-compose # or podman-compose
docker-client # or podman
];
# docker-compose.yml β LocalStack + PostgreSQL
services:
localstack:
image: localstack/localstack
ports:
- "4566:4566"
environment:
- SERVICES=s3,sqs,lambda,dynamodb
- DEFAULT_REGION=ap-northeast-1
postgres:
image: postgres:16
ports:
- "5432:5432"
environment:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: myapp
7. Auto-Setup: direnv + nix-direnv
The killer feature:
cdinto your project β environment is ready. No manual steps.
How It Works
You: cd ~/projects/my-app
direnv: Loading .envrc β "use flake"
nix: Activating devShell from flake.nix
Result: All tools available. ENV vars set. Hooks installed.
It just works. β¨
Setup
-
Install direnv + nix-direnv (see Prerequisites)
-
Create
.envrcin your project root:
# .envrc β Basic
use flake
# .envrc β With extras
use flake
# Project-specific overrides
export BIOME_BINARY=$(which biome 2>/dev/null || true)
export NODE_ENV=development
# Watch additional files for changes
watch_file flake.nix
watch_file flake.lock
- Allow direnv:
direnv allow
- Add to
.gitignore:
# direnv
.direnv/
Per-directory Shells in Monorepo
# apps/backend/.envrc
source_up # inherit parent .envrc
use flake ..#backend
# apps/frontend/.envrc
source_up
use flake ..#frontend
VS Code Integration
Install mkhl.direnv extension. It will:
- Auto-detect
.envrc - Load the Nix environment into VS Codeβs integrated terminal
- Make language servers find the right tools
- Show a notification when the environment loads
8. Developer Experience Enhancers
starship β Beautiful Shell Prompt
Shows Nix shell indicator, git branch, Node version, etc.
packages = with pkgs; [ starship ];
shellHook = ''
eval "$(starship init bash)" # or zsh
'';
process-compose β Service Orchestration
Run backend, frontend, database, LocalStack all at once:
nix run .#services
# Opens a TUI showing all processes, logs, health status
devenv β Higher-level Nix Dev Environments
An alternative/complementary approach with more batteries included:
# devenv.nix
{ pkgs, ... }: {
languages.javascript = {
enable = true;
package = pkgs.nodejs;
pnpm.enable = true;
};
services.postgres.enable = true;
services.localstack.enable = true;
pre-commit.hooks = {
prettier.enable = true;
eslint.enable = true;
};
}
mise (formerly rtx) β Polyglot Version Manager
Can coexist with Nix for teams partially adopting Nix:
packages = with pkgs; [ mise ];
Cachix β Binary Cache
Avoid rebuilding tools from source:
# Setup
nix profile install nixpkgs#cachix
cachix use my-org
# In CI, push built artifacts
cachix push my-org ./result
9. Cheatsheet
Flake Commands
# ββ Environment βββββββββββββββββββββββββββββββββββ
nix develop # Enter default devShell
nix develop .#backend # Enter named devShell
nix develop --command zsh # Use a specific shell
# ββ Flake Management βββββββββββββββββββββββββββββ
nix flake init # Create a new flake.nix
nix flake update # Update all inputs (rewrite flake.lock)
nix flake lock --update-input nixpkgs # Update single input
nix flake show # Show flake outputs
nix flake check # Run checks (tests, pre-commit, etc.)
nix flake metadata # Show flake metadata & inputs tree
# ββ Formatting ββββββββββββββββββββββββββββββββββββ
nix fmt # Format with declared formatter
# ββ Building & Running βββββββββββββββββββββββββββ
nix build # Build default package
nix build .#myPackage # Build named package
nix run .#services # Run a named app
# ββ Debugging βββββββββββββββββββββββββββββββββββββ
nix repl # Interactive Nix REPL
# In REPL:
# :lf . # Load current flake
# outputs.<TAB> # Explore outputs
# :q # Quit
# ββ Garbage Collection βββββββββββββββββββββββββββ
nix store gc # Remove unused store paths
nix store gc --min 10G # Free at least 10 GB
# ββ Search ββββββββββββββββββββββββββββββββββββββββ
nix search nixpkgs nodejs # Find packages
nix search nixpkgs --json nodejs | jq # JSON output
direnv Commands
direnv allow # Trust current .envrc
direnv deny # Untrust current .envrc
direnv reload # Force reload environment
direnv status # Show direnv state
Common Patterns
# Pin Node.js version
nodejs_22
# Override yarn to use specific Node
(yarn.override { nodejs = nodejs_22; })
# Python with packages
(python3.withPackages (ps: [ ps.flask ps.boto3 ]))
# Use env attribute for clean environment variable declaration
env = {
MY_VAR = "value";
DATABASE_URL = "postgresql://localhost/mydb";
};
# Multiple inputs with follows (dedup nixpkgs)
inputs.some-tool = {
url = "github:owner/tool";
inputs.nixpkgs.follows = "nixpkgs";
};
10. Troubleshooting
| Problem | Solution |
|---|---|
error: experimental feature 'flakes' is disabled | Add experimental-features = nix-command flakes to ~/.config/nix/nix.conf |
pnpm uses wrong binary from node_modules/.bin/ | Use shellHook to symlink Nix binary (see dprint gotcha) |
direnv not loading in VS Code | Install mkhl.direnv extension. Restart VS Code after direnv allow |
nix develop is slow first time | Expected β it downloads/builds packages. Subsequent runs use cache. Use Cachix for team-wide binary cache |
warning: Git tree is dirty | Commit or git add your flake.nix. Nix tracks git-tracked files only |
error: path '/nix/store/...' is not valid | Run nix store repair --all or nix store gc |
Environment not updating after flake.nix change | direnv reload or exit and re-run nix develop |
Collision between Nix tool and node_modules tool | Use shellHook to create symlinks or export TOOL_BINARY=$(which tool) |
11. Open Questions & Brainstorm
Architecture & Strategy
- Mono-flake vs multi-flake? Should a monorepo have one
flake.nixat root, or one per app? What are the trade-offs for lock file management? - devenv vs raw flake vs flake-parts? Which abstraction level is right for the team? How much Nix knowledge should be required?
- How to handle secrets in dev environments?
sops-nix,agenix,.envfiles, or vault integration? - Whatβs the migration path for a team not using Nix? Can Nix coexist with
nvm/volta/asdf/miseduring transition?
Tooling & DX
- dprint vs Biome vs Prettier? dprint is fastest but less ecosystem. Biome replaces ESLint+Prettier. Prettier has most plugins. Which to standardize on?
- How to handle npm postinstall scripts that need native dependencies? (e.g.,
sharp,bcrypt, Prisma engines) β useLD_LIBRARY_PATH?buildFHSUserEnv? - Should pre-commit hooks be Nix-managed (git-hooks.nix) or traditional (.pre-commit-config.yaml)? What about CI parity?
- How to integrate Playwright/Cypress browsers in Nix? Chromium sandboxing issues? Use
playwright-driver.browsersor system browsers?
Infrastructure
- LocalStack vs real AWS dev accounts? What services are well-emulated? Whatβs the boundary?
- process-compose vs docker-compose for local services? Nix-native (process-compose) is lighter but less portable. Docker is more familiar but adds overhead.
- How to share Nix binary caches across the team? Self-hosted Attic? Cachix? S3 bucket? GitHub Actions cache?
- Can we build Docker images with Nix (
dockerTools) instead of Dockerfile? Benefits: smaller images, reproducible builds. Cost: learning curve.
Team & Adoption
- How to enforce that everyone uses Nix environments? CI checks? Git hooks that verify Nix shell?
.tool-versionsfallback? - Whatβs the minimum Nix knowledge for a developer? Can we make it so devs only need
cdanddirenv allow? - How to version/template flake.nix across multiple projects? Flake templates? Internal flake registry? Shared overlays?
- How to handle Nix on CI/CD? GitHub Actions with
DeterminateSystems/nix-installer-action? Self-hosted runners with Nix pre-installed?
Performance & Maintenance
- How to keep
flake.lockup to date? Renovate bot? Schedulednix flake updatein CI? Manual? - Disk usage management? Nix store grows fast. Auto-GC? Per-project
.nix-gc-roots? - How to debug slow
nix developtimes? Use--show-trace? Profile withnix eval? Reduce input count? - What if nixpkgs doesnβt have the version we need? Pin specific commits? Use overlays? Fall back to
npm/npx?
Advanced Exploration
- Nix-based CI pipelines? Hydra? Garnix? Hercules CI? Or just Nix in GitHub Actions?
- NixOS as the dev machine OS? Full reproducibility from OS to project. Worth the investment?
- Remote development with Nix? Codespaces / Devcontainers with Nix?
nix copyto remote machines? - Can Nix replace Terraform for infrastructure? NixOps? Colmena? Terranix?
References
- Nix Flakes Book (ryan4yin) β Best beginner-friendly guide
- nix.dev β Official Nix documentation
- NixOS/nixpkgs β Package repository
- nix-community/nix-direnv β Fast direnv integration
- cachix/git-hooks.nix β Pre-commit hooks as Nix flake
- numtide/treefmt-nix β Unified formatting
- hercules-ci/flake-parts β Composable flake modules
- Platonic-Systems/process-compose-flake β Service orchestration
- juspay/services-flake β Pre-built service definitions
- devenv.sh β Higher-level dev environments on Nix
- Determinate Systems Nix Installer β Recommended installer