Dev tools#
A developer workstation on NixOS: editors, shells, containers, VMs, per-project environments, IDEs, and the “how do I run this random binary from the internet” problem.
This page assumes you already have a real NixOS host — NixOS-WSL, a VirtualBox sandbox, a libvirt VM on Kali, or real hardware. If you are still at the home-manager-on-Debian / home-manager-on-Kali stage (Level 2a), most of what’s below has a home-manager equivalent; see home-manager-modes.md. The virtualisation.*, programs.nix-ld, programs.docker.* options shown here are NixOS-only and require configuration.md to be your starting point.
In this page#
System-wide CLI tools#
environment.systemPackages = with pkgs; [
# editors
neovim
micro
helix
# shell utilities
git
git-lfs
tmux
zellij
fzf
ripgrep
fd
bat
eza
zoxide
jq
yq
httpie
curl
wget
# system
btop
htop
ncdu
dust
duf
lsof
tree
file
unzip
zip
p7zip
# nix-specific
nix-tree # explore store closures
nix-output-monitor # `nom` — prettier `nix build`
nvd # diff generations
nix-index # find which package provides a command
nixpkgs-review
];
Shells#
programs.fish.enable = true;
programs.zsh.enable = true;
users.users.alice.shell = pkgs.fish;
Per-user fish config goes in ~/.config/fish/config.fish — or declaratively via home-manager’s programs.fish.* options.
Starship prompt is system-wide via
programs.starship.enable = true;but most users prefer declaring it per-user through home-manager.
Git / pre-commit hooks#
programs.git = {
enable = true;
lfs.enable = true;
config = {
init.defaultBranch = "main";
pull.rebase = true;
push.autoSetupRemote = true;
};
};
Pre-commit ships as pre-commit in nixpkgs; use it via direnv in per-project shells, not system-wide.
Docker / Podman#
virtualisation.docker = {
enable = true;
rootless = {
enable = true;
setSocketVariable = true;
};
};
# Add your user to the docker group for rootful socket access
users.users.alice.extraGroups = [ "docker" ];
Podman as a drop-in with the same CLI:
virtualisation.podman = {
enable = true;
dockerCompat = true; # symlinks `docker` → `podman`
defaultNetwork.settings.dns_enabled = true;
};
Pick one daemon per host. If you are coming from Docker Desktop on Windows (WSL2 backend) or Docker Desktop on macOS, the native Linux daemon enabled above replaces that shim entirely — no Desktop client, no VM-in-a-VM. From inside NixOS-WSL the daemon runs inside the WSL2 distro directly, so interop with Windows containers is lost; that is nearly always fine for Linux-container development.
Virtualization (virt-manager / libvirt / QEMU)#
virtualisation.libvirtd = {
enable = true;
qemu = {
package = pkgs.qemu_kvm;
swtpm.enable = true;
ovmf = {
enable = true;
packages = [ pkgs.OVMFFull.fd ];
};
};
};
programs.virt-manager.enable = true;
users.users.alice.extraGroups = [ "libvirtd" "kvm" ];
Now virt-manager can create Windows/Linux VMs with full UEFI + TPM support.
Per-project environments#
The golden pattern on NixOS: one shell.nix (or flake.nix) per repo, auto-activated by direnv.
Enable direnv system-wide#
programs.direnv = {
enable = true;
nix-direnv.enable = true; # fast, caching-aware Nix integration
loadInNixShell = true;
};
Then in any repo:
echo "use flake" > .envrc # for flakes
# or
echo "use nix" > .envrc # for classic shell.nix
direnv allow
Minimal shell.nix#
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
packages = with pkgs; [ python312 uv ruff pre-commit ];
shellHook = ''
export PYTHONDONTWRITEBYTECODE=1
'';
}
Minimal flake.nix devShell#
{
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
outputs = { nixpkgs, ... }:
let
system = "x86_64-linux";
pkgs = import nixpkgs { inherit system; };
in {
devShells.${system}.default = pkgs.mkShell {
packages = with pkgs; [ python312 uv ruff pre-commit ];
};
};
}
devenv / flox / pixi on top of Nix#
All three run on NixOS the same as they do on Debian. See python/setup for the devenv + direnv workflow already documented in this repo. On NixOS the install step simplifies — devenv is just a package:
environment.systemPackages = with pkgs; [ devenv ];
IDEs#
environment.systemPackages = with pkgs; [
# editors / IDEs
vscode # unfree
vscode-fhs # vscode in an FHS env — fixes native extensions that `patchelf`-reject
jetbrains.idea-community
jetbrains.pycharm-community
jetbrains.clion # unfree
zed-editor
];
nixpkgs.config.allowUnfree = true;
Cursor is packaged as
code-cursorin unstable, or install as an AppImage — see packages.md.
Language toolchains and the FHS caveat#
Pure nixpkgs packages work perfectly. Where you hit friction is when a language’s own package manager tries to install prebuilt binaries (pip wheels with native libs, npm/pnpm native modules, rustup-installed toolchains, etc.). The dynamic linker on NixOS is not at /lib64/ld-linux-x86-64.so.2, so those binaries fail with confusing errors.
Three fixes, from cleanest to easiest:
Use nixpkgs’s own version:
environment.systemPackages = with pkgs; [ python312 nodejs_22 rustc cargo go jdk21 ];
Use
programs.nix-ldto fake an FHS dynamic linker for foreign binaries. The canonical library list lives in packages.md; declare it once in a shared module and import from every host. A minimal working set:programs.nix-ld = { enable = true; libraries = with pkgs; [ stdenv.cc.cc.lib zlib zstd openssl curl glib libxkbcommon icu fuse3 xorg.libX11 xorg.libXcursor xorg.libXi xorg.libXrandr xorg.libXxf86vm libGL ]; };
Now prebuilt binaries (VSCode extensions, pip wheels, pnpm/npm native modules, rustup-installed toolchains, etc.) mostly Just Work. When one still fails, the error names the missing
lib<foo>.so.N— add the corresponding nixpkgs package tolibrariesand rebuild.Run inside an FHS shell for the stubborn cases:
(pkgs.buildFHSEnv { name = "fhs-dev"; targetPkgs = p: with p; [ gcc zlib openssl ]; runScript = "bash"; })
Launch with the command name; inside, paths behave like Ubuntu/Debian.
See packages.md for the same pattern applied to games and random binaries.
Secrets in dev (quick note)#
Don’t put secrets in configuration.nix — it lands in the world-readable /nix/store. Use direnv-local .envrc (gitignored) during dev. For production-style secrets-in-config, see the agenix/sops-nix pointer in snippets.md.
Next#
packages.md — adding packages, unfree, Steam, AppImages, Flatpak,
nix-ldcanonical library list.home-manager-modes.md — putting
programs.fish/programs.git/programs.direnvinhome.nixinstead.multi-host-flake.md — sharing one
dev.nixmodule across every host.snippets.md — docker rootless, GPU, fonts, etc.