This document outlines the challenges and solutions encountered while setting up a Python development environment in NixOS. It’s intended as a reference for a future self.
The Pure Nix Approach
The purest way to manage Python packages in NixOS is by using the nixpkgs repository. You can build a Python binary with specific, pre-packaged libraries.
pkgs.mkShell {
packages = [
(python.withPackages (pypkgs: [
pypkgs.numpy
pypkgs.requests
]))
];
}
Nixpkgs provides a vast collection of packages, and this approach ensures perfect reproducibility because all dependencies are managed and built by Nix. For packages not in nixpkgs (e.g., your own GitHub projects), you’d need to manually wrap them using the buildPythonPackage function. This process, while a bit overwhelming for newcomers, is a great way to understand the Nix build system.
-
Pros: Easy when packages are already in
nixpkgs; provides full reproducibility. -
Cons: Requires manual effort to package external Python libraries into the Nix ecosystem.
The uv and nix-ld Approach
Sometimes, you might want to use a familiar tool like uv to manage your Python dependencies, prioritizing convenience over Nix’s strict reproducibility. However, this poses a significant challenge on NixOS.
The uv Challenge
When you simply add uv to your shell.nix, you’ll likely run into issues with Python packages that have external, non-Python dependencies (like numpy). The reason is that these packages, when built outside of the Nix environment, expect to find their shared libraries in standard Linux directories like /usr/lib or /lib64, which don’t exist in the NixOS FHS (Filesystem Hierarchy Standard). This is the expected behavior for any user dealing with non-Nix binaries.
The nix-ld Solution
The nix-ld module provides a solution by creating an environment that mimics the standard Linux FHS. It allows non-Nix binaries to find their required shared libraries.
To use it, you first enable the module in your configuration.nix:
# configuration.nix
{
programs.nix-ld.enable = true;
}
Then, you can use it in your flake.nix by setting the NIX_LD and NIX_LD_LIBRARY_PATH variables.
# flake.nix
pkgs.mkShell {
NIX_LD_LIBRARY_PATH = lib.makeLibraryPath [
stdenv.cc.cc
openssl
];
NIX_LD = "${stdenv.cc}/nix-support/dynamic-linker";
}
Often, nix-ld has a good set of default libraries, so you may not need to manually specify the NIX_LD_LIBRARY_PATH. By enabling and configuring nix-ld, tools like uv start working out-of-the-box, bridging the gap between standard Python workflows and the unique architecture of NixOS. This approach lets you get the best of both worlds: the power of Nix for your system, and the convenience of familiar tools for your dev workflow.