# How to use uv on NixOS


[uv](https://pydevtools.com/handbook/reference/uv.md) 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](https://pydevtools.com/handbook/how-to/how-to-install-python-with-uv.md) 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](https://wiki.nixos.org/wiki/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](https://github.com/astral-sh/python-build-standalone), so they hit this wall the first time uv tries to use one:

```console
$ 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`](https://github.com/nix-community/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`:

```nix {filename="/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:

```bash
sudo nixos-rebuild switch
```

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

```bash
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](https://wiki.nixos.org/wiki/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:

```nix {filename="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:

```bash
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](https://pydevtools.com/handbook/reference/direnv.md) so the dev shell loads automatically when you `cd` into the project:

```bash {filename=".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`](https://pyproject-nix.github.io/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`](https://docs.astral.sh/uv/reference/settings/#python-downloads) 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

- [Python quickstart using uv (NixOS Wiki)](https://wiki.nixos.org/wiki/Python_quickstart_using_uv) lists every NixOS option uv responds to, with the exact configuration keys.
- [`nix-community/nix-ld`](https://github.com/nix-community/nix-ld) explains how the linker shim works and what `NIX_LD_LIBRARY_PATH` overrides at runtime.
- [`pyproject-nix/uv2nix`](https://pyproject-nix.github.io/uv2nix/) shows the templates and flake structure for building uv projects fully through Nix.
- [How to install uv on Linux](https://pydevtools.com/handbook/how-to/how-to-install-uv-on-linux.md) covers the standalone installer for non-Nix Linux distributions.
- [How to customize uv's virtual environment location](https://pydevtools.com/handbook/how-to/how-to-customize-uvs-virtual-environment-location.md) is useful when a Nix dev shell mounts the project from a read-only path.
