Skip to content

How to use uv on NixOS

uv ships as a single static binary that drops onto NixOS without a build step. The hard part is what happens next: uv’s managed Python interpreters are precompiled for a generic Linux that NixOS doesn’t pretend to be, so uv sync and uv run fail with Could not start dynamically linked executable until you bridge the gap.

This guide walks through the two patterns NixOS users reach for: enabling nix-ld system-wide so uv-downloaded Python binaries find their dynamic linker, and pinning uv inside a flake.nix dev shell that sets LD_LIBRARY_PATH per project. Pick one based on whether you want uv to work everywhere on your system or only inside specific repositories.

Why uv-managed Python fails on NixOS

NixOS does not follow the Filesystem Hierarchy Standard. Shared libraries live under /nix/store/<hash>-<name>/lib/, not /usr/lib/, and the system has no ld-linux-x86-64.so.2 at the path that prebuilt binaries hardcode. Native Nix packages get patched at build time so their interpreter and library paths point inside /nix/store. Anything you download from the open internet does not.

uv’s managed Python builds are downloaded at runtime from python-build-standalone, so they hit this wall the first time uv tries to use one:

$ uv sync
error: Querying Python at `~/.local/share/uv/python/cpython-3.13.../bin/python3.13` failed
  Caused by: Cannot start a dynamically linked executable

The fix is to either teach NixOS how to run unpatched binaries (Pattern 1) or wrap the project in a Nix shell that exposes the libraries the binary needs (Pattern 2).

Pattern 1: Install uv system-wide and enable nix-ld

If you administer the NixOS machine and want uv to work in every shell, this is the shortest path. nix-ld installs a shim at /lib64/ld-linux-x86-64.so.2 that reads NIX_LD and NIX_LD_LIBRARY_PATH to redirect any unpatched binary to the real linker and the right libraries.

Add the following to /etc/nixos/configuration.nix:

/etc/nixos/configuration.nix
{ pkgs, ... }: {
  environment.systemPackages = with pkgs; [
    uv
  ];

  # Tell uv to install user-level binaries onto $PATH.
  environment.localBinInPath = true;

  # Let unpatched binaries (uv-managed Python, pip wheels with C deps)
  # find a dynamic linker and the shared libraries they expect.
  programs.nix-ld = {
    enable = true;
    libraries = with pkgs; [
      stdenv.cc.cc.lib   # libstdc++.so.6 for most wheels
      zlib               # CPython, many compiled extensions
      openssl
      libffi
      glibc
    ];
  };
}

Apply the change:

sudo nixos-rebuild switch

Open a new shell and verify uv can fetch and run a managed interpreter:

uv python install 3.13
uv run --python 3.13 python -c "import ssl, zlib; print(ssl.OPENSSL_VERSION)"

If the second command prints an OpenSSL version instead of a linker error, nix-ld is doing its job. Add packages to programs.nix-ld.libraries whenever a new wheel imports a C library that’s missing; the NixOS Wiki page on nix-ld lists the ones most projects need.

Note

nix-ld is a NixOS module. On non-NixOS systems where Nix is installed alongside a regular distribution, use Pattern 2 instead. The host’s standard dynamic linker handles the rest.

Pattern 2: Pin uv in a flake.nix dev shell

When the repository has to work for teammates who don’t run NixOS, or when you don’t want to enable nix-ld globally, declare uv and its dependencies inside a flake.nix dev shell. Anyone with Nix and flakes enabled gets the same uv version and the same library paths, on NixOS or any other distribution.

Create flake.nix at the project root:

flake.nix
{
  description = "uv-managed Python project";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = nixpkgs.legacyPackages.${system};
        libs = with pkgs; [ stdenv.cc.cc.lib zlib openssl libffi glibc ];
      in {
        devShells.default = pkgs.mkShell {
          packages = [ pkgs.uv ];
          env.LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath libs;
        };
      });
}

Enter the shell and use uv normally:

nix develop
uv init demo
cd demo
uv add requests
uv run python -c "import requests; print(requests.__version__)"

nix develop drops you into a shell with uv on PATH and the same LD_LIBRARY_PATH nix-ld would have provided. uv downloads its managed Python and installs requests into .venv; the import then succeeds.

Pair the flake with direnv so the dev shell loads automatically when you cd into the project:

.envrc
use flake

Run direnv allow once. After that, every cd into the directory enters the dev shell; every cd out leaves it.

Tip

Add libraries to the libs list when a new dependency fails to import. Scientific stacks usually need pkgs.gcc-unwrapped.lib; GPU wheels also need pkgs.cudaPackages.cudatoolkit.

When to reach for uv2nix instead

The two patterns above let uv install and run Python packages from PyPI. Those packages stay impure: they come from the network into a venv, outside /nix/store. For Nix users who want every dependency built from source through the Nix store, uv2nix parses uv.lock and emits Nix expressions that reproduce the resolved environment as Nix derivations.

Reach for uv2nix when you need bit-identical builds across machines or when you’re packaging a Python application for Nix consumers. Skip it for day-to-day development; the patterns above let uv’s lockfile stay the source of truth.

Fix common errors

Could not start dynamically linked executable after uv python install succeeded: the binary is on disk but the dynamic linker isn’t in place. On NixOS, enable programs.nix-ld.enable = true; (Pattern 1). Inside a flake dev shell, confirm LD_LIBRARY_PATH is set: echo $LD_LIBRARY_PATH | tr ':' '\n' should list /nix/store paths.

ImportError: libstdc++.so.6: cannot open shared object file: a wheel needs the C++ runtime. Add pkgs.stdenv.cc.cc.lib to programs.nix-ld.libraries (Pattern 1) or to the libs list in flake.nix (Pattern 2), then rebuild or re-enter the shell.

uv tries to download Python on every uv sync: set UV_PYTHON_DOWNLOADS=never and install Python with uv python install <version> once. uv will reuse the cached interpreter at ~/.local/share/uv/python/.

uv binaries land somewhere your shell can’t find them: if uv tool install succeeds but the tool is command not found, set environment.localBinInPath = true; in /etc/nixos/configuration.nix so ~/.local/bin is on PATH. Outside NixOS, add it to the shell profile manually.

Learn More

Last updated on