--- title: Getting Started with Nix Flakes permalink: /futureproof/nix-flakes/ description: This article describes my journey to integrate Nix Flakes into my NixOS configuration for better version control and reproducibility. Initially hesitant due to the perceived complexity, I eventually embraced Flakes for their ability to simplify git management and streamline my workflow. The article provides a step-by-step guide on implementing Nix Flakes, moving the system configuration to user space, and rebuilding the system for a more manageable and reproducible development environment. meta_description: "Getting started with Nix Flakes: Manage your NixOS configuration.nix with Git by moving it to user space for easier version control. Includes setup guide." meta_keywords: Nix Flakes, NixOS configuration, configuration.nix, manage NixOS config, Git, version control, user space config, reproducible environment, flakes setup, flake.nix, Python Nix Flake, cross-platform flake, CUDA Nix Flake, Nix pip install, Nix uv, dependency pinning, symlink config layout: post sort_order: 1 --- {% raw %} ## A Cross-Platform Python Nix Flake with CUDA Support You're probably here for is this [Python Nix Flake](https://github.com/miklevin/darwinix) that allows you to run Python with pip install working on macOS, Windows (with WSL) and with your NVIDIA card with CUDA support if you've got it. All the cool kids are using `uv` these days, so here's the [Python Nix Flake with uv](https://github.com/miklevin/python_nix_flake). Here's an article about [using pip with nix](/futureproof/nix-pip-install/). ### Solving Common Python Nix Environment Issues What's more, it addresses the ***inflexibility*** and often ***out-of-date pip packages*** of a traditional Python nix flake by creating a Python virtual environment and installing all the PyPI requirements with pip. ```nix # ____ _ _ .--. ___________ # | _ \ __ _ _ ____ _(_)_ __ (_)_ __ ,--./,-. |o_o | | | | # | | | |/ _` | '__\ \ /\ / / | '_ \| \ \/ / / # \ |:_/ | | | | # | |_| | (_| | | \ V V /| | | | | |> < | | // \ \ |_____|_____| # |____/ \__,_|_| \_/\_/ |_|_| |_|_/_/\_\ \ / (| | ) | | | # `._,._,' /'\_ _/`\ | | | # Solving the "Not on my machine" problem well. \___)=(___/ |_____|_____| # Most modern development is done on Linux, but Macs are Unix. If you think Homebrew and Docker # are the solution, you're wrong. Welcome to the world of Nix Flakes! This file defines a complete, # reproducible development environment. It's like a recipe for your perfect workspace, ensuring # everyone on your team has the exact same setup, every time. As a bonus, you can use Nix flakes on # Windows under WSL. Plus, whatever you make will be deployable to the cloud. { # This description helps others understand the purpose of this Flake description = "A flake that reports the OS using separate scripts with optional CUDA support and unfree packages allowed."; # Inputs are the dependencies for our Flake # They're pinned to specific versions to ensure reproducibility inputs = { # nixpkgs is the main repository of Nix packages nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; # flake-utils provides helpful functions for working with Flakes flake-utils.url = "github:numtide/flake-utils"; }; # Outputs define what our Flake produces # In this case, it's a development shell that works across different systems outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system: let # We're creating a custom instance of nixpkgs # This allows us to enable unfree packages like CUDA pkgs = import nixpkgs { inherit system; config = { allowUnfree = true; # This is necessary for CUDA support }; }; # These helpers let us adjust our setup based on the OS isDarwin = pkgs.stdenv.isDarwin; isLinux = pkgs.stdenv.isLinux; # Common packages that we want available in our environment # regardless of the operating system commonPackages = with pkgs; [ python313Full # Python 3.13 interpreter python313Packages.virtualenv # Tool to create isolated Python environments figlet # For creating ASCII art welcome messages tmux # Terminal multiplexer for managing sessions zlib # Compression library for data compression git # Version control system for tracking changes curl # Command-line tool for transferring data with URLs wget # Utility for non-interactive download of files from the web cmake # Cross-platform build system generator htop # Interactive process viewer for Unix systems ] ++ (with pkgs; pkgs.lib.optionals isLinux [ gcc # GNU Compiler Collection for compiling C/C++ code stdenv.cc.cc.lib # Standard C library for Linux systems ]); # This script sets up our Python environment and project runScript = pkgs.writeShellScriptBin "run-script" '' #!/usr/bin/env bash # Activate the virtual environment source .venv/bin/activate # Create a fancy welcome message REPO_NAME=$(basename "$PWD") PROPER_REPO_NAME=$(echo "$REPO_NAME" | awk '{print toupper(substr($0,1,1)) tolower(substr($0,2))}') figlet "$PROPER_REPO_NAME" echo "Welcome to the $PROPER_REPO_NAME development environment on ${system}!" echo # Install Python packages from requirements.txt # This allows flexibility to use the latest PyPI packages # Note: This makes the environment less deterministic echo "- Installing pip packages..." if pip install --upgrade pip --quiet && \ pip install -r requirements.txt --quiet; then package_count=$(pip list --format=freeze | wc -l) echo "- Done. $package_count pip packages installed." else echo "Warning: An error occurred during pip setup." fi # Check if numpy is properly installed if python -c "import numpy" 2>/dev/null; then echo "- numpy is importable (good to go!)" echo echo "To start JupyterLab, type: start" echo "To stop JupyterLab, type: stop" echo else echo "Error: numpy could not be imported. Check your installation." fi # Create convenience scripts for managing JupyterLab # Note: We've disabled token and password for easier access, especially in WSL environments cat << EOF > .venv/bin/start #!/bin/sh echo "A JupyterLab tab will open in your default browser." tmux kill-session -t jupyter 2>/dev/null || echo "No tmux session named 'jupyter' is running." tmux new-session -d -s jupyter 'source .venv/bin/activate && jupyter lab --NotebookApp.token="" --NotebookApp.password="" --NotebookApp.disable_check_xsrf=True' echo "If no tab opens, visit http://localhost:8888" echo "To view JupyterLab server: tmux attach -t jupyter" echo "To stop JupyterLab server: stop" EOF chmod +x .venv/bin/start cat << EOF > .venv/bin/stop #!/bin/sh echo "Stopping tmux session 'jupyter'..." tmux kill-session -t jupyter 2>/dev/null || echo "No tmux session named 'jupyter' is running." echo "The tmux session 'jupyter' has been stopped." EOF chmod +x .venv/bin/stop ''; # Define the development shell for Linux systems (including WSL) linuxDevShell = pkgs.mkShell { # Include common packages and conditionally add CUDA if available buildInputs = commonPackages ++ (with pkgs; pkgs.lib.optionals (builtins.pathExists "/usr/bin/nvidia-smi") cudaPackages); shellHook = '' # Set up the Python virtual environment test -d .venv || ${pkgs.python311}/bin/python -m venv .venv export VIRTUAL_ENV="$(pwd)/.venv" export PATH="$VIRTUAL_ENV/bin:$PATH" # Customize the prompt to show we're in a Nix environment # export PS1='$(printf "\033[01;34m(nix) \033[00m\033[01;32m[%s@%s:%s]$\033[00m " "\u" "\h" "\w")' export LD_LIBRARY_PATH=${pkgs.lib.makeLibraryPath commonPackages}:$LD_LIBRARY_PATH # Set up CUDA if available if command -v nvidia-smi &> /dev/null; then echo "CUDA hardware detected." export CUDA_HOME=${pkgs.cudatoolkit} export PATH=$CUDA_HOME/bin:$PATH export LD_LIBRARY_PATH=$CUDA_HOME/lib64:$LD_LIBRARY_PATH else echo "No CUDA hardware detected." fi # Run our setup script ${runScript}/bin/run-script ''; }; # Define the development shell for macOS systems darwinDevShell = pkgs.mkShell { buildInputs = commonPackages; shellHook = '' # Set up the Python virtual environment test -d .venv || ${pkgs.python311}/bin/python -m venv .venv export VIRTUAL_ENV="$(pwd)/.venv" export PATH="$VIRTUAL_ENV/bin:$PATH" # Customize the prompt to show we're in a Nix environment # export PS1='$(printf "\033[01;34m(nix) \033[00m\033[01;32m[%s@%s:%s]$\033[00m " "\u" "\h" "\w")' export LD_LIBRARY_PATH=${pkgs.lib.makeLibraryPath commonPackages}:$LD_LIBRARY_PATH # Run our setup script ${runScript}/bin/run-script ''; }; in { # Choose the appropriate development shell based on the OS devShell = if isLinux then linuxDevShell else darwinDevShell; # Ensure multi-OS support }); } ``` ### Related Resources All the cool kids are using `uv` today instead of pip, and if you want that version, you can check the article: [Replacing `pip` In Python Nix Flake With `uv`](/python-nix-flake-uv/). And if you want more yabbering about why all this works so well, travel into the future to this article: [Nix Flake Python Solution: Venv, Cuda, macOS/Windows, Etc.](/nix-flake-python/). Oh yeah, and the GitHub repos are here: - [GitHub repo for Python Nix Flake using `pip`](https://github.com/miklevin/darwinix) - [GitHub repo for Python Nix Flake using `uv`](https://github.com/miklevin/python_nix_flake) > And Now back to the regularly scheduled article. If you got what you need, > surf away! Below is the history that led up to this awesome Python nix flake. --- ## Introduction From tinkering with **Levinux** to embracing **NixOS**, I've been on a quest for the holy grail of **reproducible, timeless computing environments**. Now, with **Nix Flakes**, I'm taking another leap into the future of system configuration. Join me as I unravel the complexities, face my initial skepticism, and discover how Flakes might just be the missing piece in our perpetual tech puzzle. Spoiler alert: **It's not as flaky as I first thought!** --- ## Getting Started with Nix Flakes I discovered **NixOS** some months ago and recognized a kindred spirit. Some 15 years ago, I started a project called **Levinux** that tried to make a completely portable minimal Linux system for cutting through the ages that would play host to such roles as my main development system and home of a private journal. I wanted to make my daily work routine timeless so I could get better and better at a smaller set of muscle memory skills over time. **Muscle memory**, I deduced, was the path to forever improving craft in tech. --- ## The Quest for a Timeless Development Environment But I based **Levinux** on ye old emulator, **QEMU**, and it lacked performance and viability as a main day-to-day platform. It was like a lowest common denominator virtual machine that used an extremely tiny Linux (**Tiny Core Linux**) based on the premise that all other "host" systems (Macs, Windows, and other Linuxes) could always easily run it. The flaw in this was that, though it was like a neat magic trick, the **performance was terrible**, and it was **not suitable as a daily platform** for work. Whatever I chose had to hold its own against anything else out there. --- ## The Disappointment of Virtual Machines and Containers Of course, one's mind goes to more powerful virtual machines like **VirtualBox** or containers like **Docker** or **LXC/LXD**. But over the years, both virtual machines and containers have disappointed me. You've now got these big virtual images that can get lost, trashed up, or just weigh you down with all the extra steps and tooling. There was always a "host" machine that was something else. While **Levinux** was a VM technology too, it built entire systems from scratch with recipe files. And I learned **Nix** was a language to do this, and **NixOS** was an OS built from it, and the benefit could be realized on Macs and Windows too in folders where you could do similar builds. --- ## The Promise of Nix: A Deterministic System Definition So I realized a language exists for you to **deterministically define a whole system with a text file**! Or you could determine **"flakes"** of a system and instantiate them under Macs and Windows. It'd be like a bash file that does a whole bunch of sequential `apt` installs without version issues because everything's **pinned**. In such a scenario, you can just **rebuild a system anytime, anywhere, and on almost anything**. Hardware changes. Platforms change. But a **phantom system** can cut through it all in time and space by **nixing all those versioning issues** for Unix-like development. --- ## Embracing the Nix Ecosystem What a brilliant name for a brilliant system. I would have preferred if **Nix** were just Python, but as it is, it's a new language similar to Haskell specifically for configuring systems in a way that fixes the **"not on my machine" problem**. It not being Python, it's going to take some work for me. **I'm a confirmed monoglot**. I speak English-only, in terms of spoken languages, and I speak Python only when I can help it, in machine terms. Much doesn't fit my head. Python does, just barely. **Taking on learning this system configuration language is a challenge for me, but one that is worth it**. --- ## Understanding Nix: Language, OS, and Package Manager **Nix is three things**: a language, an operating system, and a package manager. When Nix builds your whole OS, it's called **NixOS**, and is basically a Linux distro, though there is some debate around whether it qualifies as a Linux distro, being so radically different from mainstream versions like **Red Hat** or **Debian**. The system is built from the software in the Nix package management system using the Nix language. There's a **triangle diagram** out there that tries to explain it. It's all so well thought out and has been around long enough to now be battle-hardened and time-tested. It's a safe bet and like **Levinux perfected**. ### Visualizing Nix Components