Templates (Deprecated)¶
Note: This section is primarily for my own learning purposes and is not necessarily useful for most users, as it targets NixOS rather than Linux.
Here you will find deprecated project templates for setting up a Nix environment.
The current template, nixos-pythonml/
, should not be used for Python machine learning project development on Linux, as it will fail. It was originally intended for a full NixOS environment.
Layout¶
.
└── nixos-pythonml
├── .envrc
├── .gitignore
├── .python-version
├── flake.nix
├── pyproject.toml
├── README.md
└── src
Template Description¶
NixOS Python ML¶
Note on Package Managers¶
Packages are managed both through nix
and uv
. As a rule of thumb:
Handle your Python packages with
uv
and everything else throughnix
. For instance, there may be external system packages required for your Python packages to work correctly—usenix
for those cases.Specify your Python version with
nix
for perfect reproducibility. (This is not done in the Linux environment because Nix Python expects some libraries to be in/nix/store
, not in/usr/lib
or other proprietary library locations on Linux. This requires extendingLD_LIBRARY_PATH
—see below—but particularly fails for CUDA drivers.)If a package does not work when installed from
uv
, you may want to specify it withnix
.For example,
pytorch
might not recognize your GPU, and specifying it withnix
might solve the issue. However, its version will be immutable and can only be changed throughnix
. Additionally, building from source can be prohibitively slow.This is why
uv
is generally preferred for handling Python dependencies.
How to Add New Packages¶
If you want to skip the context, jump directly to the Summary Section.
There are four ways to add new packages. While you should be aware of the different options (for debugging), your day-to-day workflow will primarily use the last method (uv
).
This Python ML template is intended to work on the following systems: macOS, Linux, NixOS (and supposedly Windows). Let’s clarify the distinction between nix
-installed programs and others.
In NixOS (and also when using the Nix Package Manager), packages are installed in paths like
/nix/store/[hash]-[package-name]
, and dependencies are managed internally so that packages installed throughnix
only look for their dependencies in that store. This provides complete isolation between packages and enables multiple versions of the same package, contributing to the reliability and reproducibility of the Nix system.The problem is that most programs expect access to certain runtime libraries, such as a C compiler or GPU toolkit. For programs installed from
nix
, dependencies are handled through the/nix/store
. But for foreign programs, they are unaware of/nix/store/
. On Linux, runtime libraries are expected to be listed under theLD_LIBRARY_PATH
with.so
file extensions. On macOS, the equivalent isDYLD_LIBRARY_PATH
and.dylib
file extensions (note thatDYLD_LIBRARY_PATH
is not defined by default, but you can specify your runtime package paths under that variable).What problem does this cause? On pure NixOS systems, the
LD_LIBRARY_PATH
variable is not defined by default. When installing packages from other sources (like Python packages fromuv
), they will still expect their runtime dependencies to be present inLD_LIBRARY_PATH
. So even if you have the right packages installed in/nix/store/
, you have to add them toLD_LIBRARY_PATH
.This is what
nix-ld
is for (but it is only available for NixOS).Alternatively, you can manually extend
LD_LIBRARY_PATH
, which is the approach chosen inflake.nix
for compatibility between systems.let ... # The custom list of nix packages that should be available at runtime LD_pkgs = (with pkgs; [ # ADD NEEDED PACKAGES HERE stdenv.cc.cc # useful for compiling C/C++ code glib # core application building blocks for libraries written in C libGL # useful for graphical rendering, e.g., required for matplotlib ... ]) ++ pkgs.lib.optionals pkgs.stdenv.isLinux (with pkgs; [ # ADD PACKAGES HERE IF ONLY REQUIRED BY LINUX cudatoolkit # required for GPU libraries ... ]) ; ... in # CUSTOM SHELL FOR OUR PROJECT devShells = { default = mkShell { # DEFINE THE CUSTOM LD_LIBRARY_PATH NIX_LD_LIBRARY_PATH = lib.makeLibraryPath LD_pkgs; # Add LD_pkgs to the shell as a rule of thumb packages = ... ++ LD_pkgs; shellHook = '' # Extend the original LD_LIBRARY_PATH with the `NIX_LD_LIBRARY_PATH` (only for this shell) export LD_LIBRARY_PATH=$NIX_LD_LIBRARY_PATH:$LD_LIBRARY_PATH ... ''; }; }; ...
Does this apply to us with our Ubuntu machine + Nix as a package manager?
No, because on Linux or macOS, those runtime packages are usually already present. The
LD_LIBRARY_PATH
for Linux is already set and should include all the utilities you need; the same applies for macOS (thoughDYLD_LIBRARY_PATH
is not set by default, runtime dependencies are already in locations like/usr/bin/
).Maybe, because bugs can still occur. Packages may not work properly (and it’s not necessarily because you are using Nix as a package manager).
If an external runtime package is required (the error might be something like
cannot find [name of the package]
), extendingLD_LIBRARY_PATH
should resolve the issue. The code snippet above ensures this is done in an isolated fashion and won’t corrupt your system. Note: This flake assumes you won’t have issues with your macOS setup. It only modifiesLD_LIBRARY_PATH
, which is ignored on macOS. If you encounter errors, you may want to modifyDYLD_LIBRARY_PATH
instead.
In the code snippet above, you have already seen one way to add packages—those related to LD_LIBRARY_PATH
, which may be specific to Linux or any system. These packages are stored in the LD_pkgs
variable.
The next three ways to add new packages are detailed in the Summary Section, but in short, they are for:
Adding
nix
packages that are not runtime dependencies (i.e., should not be present inLD_LIBRARY_PATH
). This includesuv
, your Python version, or the Python ShellHook (the tool that creates.venv
). It could also be specific packages your project requires, likeduckdb
for fast database queries.Adding Python packages with
nix
. This should not be the default and should only be used if you cannot make it work throughuv
.Adding Python packages with
uv
. This will be your standard workflow.
To Summarise¶
Packages that will be present in
LD_LIBRARY_PATH
, should be on every OS, and can be found on Nix Packages Search. These are stored inLD_pkgs
.LD_pkgs = (with pkgs; [ # ADD NEEDED PACKAGES HERE ... ]) ++ pkgs.lib.optionals pkgs.stdenv.isLinux (with pkgs; [ # ADD PACKAGES THAT SHOULD ONLY BE PRESENT FOR LINUX ... ]);
Packages installed for Python through
nix
, because you cannot make them work withuv
.python_with_pkgs = pkgs.python311.withPackages (pp: with pp [ # ADD PYTHON PKGS HERE THAT YOU NEED FROM NIX REPOS ... ]);
Packages that should be installed through
nix
that are not Python packages and should not be available inLD_LIBRARY_PATH
(i.e., not runtime dependencies):shell_packages = # ADD NIX PACKAGES NOT REQUIRED IN LD_LIBRARY_PATH (with pkgs; [ python311Packages.venvShellHook mpkgs.uv ... ])
The standard way to install packages with
uv
(this should be the default and is the most important; the other options are mainly for debugging):
uv pip install [name-of-the-package]
How to Change Python Version¶
Change the Python distribution in nix
the flake.nix.
You can then update the .python-version
file with:
uv python pin
Another Template: FHS-Compliant¶
FHS stands for Filesystem Hierarchy Standard. It is essentially a map that tells you where to find the tools you need, such as /usr/bin/
. On NixOS, the /usr/bin
folder doesn’t exist and all tools are located in /nix/store/
, as mentioned above.
This enables multiple versions of programs and ensures each program only sees what it needs.
However, some programs or languages (like R
) expect an FHS system, i.e., a /usr/bin
folder. In such cases, you need to add tools from /nix/store/
into a project-specific /usr/bin
. This is what pkgs.BuildFHSEnv
is for.
You may want to read the NixOS Wiki
on the subject.
You could therefore have a flake.nix
like the one from Ank’s Neusis Setup:
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
nixpkgs_master.url = "github:NixOS/nixpkgs/master";
systems.url = "github:nix-systems/default";
flake-utils.url = "github:numtide/flake-utils";
flake-utils.inputs.systems.follows = "systems";
};
outputs =
{
self,
nixpkgs,
flake-utils,
...
}@inputs:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = import nixpkgs {
inherit system;
config.allowUnfree = true;
config.cudaSupport = true;
};
mpkgs = import inputs.nixpkgs_master {
inherit system;
config.allowUnfree = true;
config.cudaSupport = true;
};
### FHS ENVIRONMENT DEFINITION
fhsenv = pkgs.buildFHSEnv {
name = "fhs-shell";
targetPkgs =
pkgs: with pkgs; [
gcc
cudatoolkit
libGL
libz
mpkgs.uv
python311
];
profile = ''
export VENVDIR=.venv
if [[ -d "$VENVDIR" ]]; then
printf "%s\n" "Skipping venv creation, '$VENVDIR' already exists"
source "$VENVDIR/bin/activate"
else
printf "%s\n" "Creating new venv environment in path: '$VENVDIR'"
python -m venv "$VENVDIR"
source "$VENVDIR/bin/activate"
fi
'';
runScript = "zsh";
};
in
{
devShells = {
default = fhsenv.env;
};
}
);
}
# Things one might need for debugging or adding compatibility
# export CUDA_PATH=${pkgs.cudaPackages.cudatoolkit}
# export LD_LIBRARY_PATH=${pkgs.cudaPackages.cuda_nvrtc}/lib
# export EXTRA_LDFLAGS="-L/lib -L${pkgs.linuxPackages.nvidia_x11}/lib"
# export EXTRA_CCFLAGS="-I/usr/include"
# Data syncthing commands
# syncthing cli show system | jq .myID
# syncthing cli config devices add --device-id $DEVICE_ID_B
# syncthing cli config folders $FOLDER_ID devices add --device-id $DEVICE_ID_B
# syncthing cli config devices $DEVICE_ID_A auto-accept-folders set true
# FHS related help
# https://discourse.nixos.org/t/best-way-to-define-common-fhs-environment/25930
# https://ryantm.github.io/nixpkgs/builders/special/fhs-environments/
This is a slightly more complex environment than the one proposed in the previous template. It is not necessarily recommended, as it can be confusing. It is somewhat equivalent to overriding LD_LIBRARY_PATH
for your project, but even stronger. You generally do not want to use this option, as it conflicts with the Nix philosophy of building isolated environments using /nix/store/
. Instead, it symlinks packages from /nix/store/
and adds them to /usr/bin
(and possibly other directories). See the documentation for BuildFHSEnv
.
References¶
Want to read more on
LD_LIBRARY_PATH
? See this LD_LIBRARY_PATH resource or this article onDYLD_LIBRARY_PATH