Agent SkillsAgent Skills
olafkfreund

uv2nix

@olafkfreund/uv2nix
olafkfreund
13
0 forks
Updated 3/31/2026
View on GitHub

uv2nix Skill

Installation

$npx agent-skills-cli install @olafkfreund/uv2nix
Claude Code
Cursor
Copilot
Codex
Antigravity

Details

Path.gemini/skills/uv2nix/SKILL.md
Branchmain
Scoped Name@olafkfreund/uv2nix

Usage

After installing, this skill will be available to your AI coding assistant.

Verify installation:

npx agent-skills-cli list

Skill Instructions


name: uv2nix version: 1.0 description: uv2nix Skill

uv2nix Skill

A specialized skill for converting Python projects using uv to Nix expressions, enabling declarative and reproducible Python package management with Nix.

Skill Overview

Purpose: Provide comprehensive support for using uv2nix to generate Nix derivations from uv workspaces, manage Python dependencies, and integrate Python projects with Nix/NixOS.

Invoke When:

  • Converting uv Python projects to Nix
  • Creating Nix derivations for Python applications
  • Managing Python dependencies with Nix
  • Building reproducible Python environments
  • Setting up development environments for Python projects
  • Packaging Python tools for NixOS
  • Working with Python workspaces and monorepos

Core Capabilities

1. What is uv2nix?

uv2nix takes a uv workspace and generates Nix derivations dynamically using pure Nix code. It bridges the gap between uv (Astral's fast Python package manager) and Nix.

Key Benefits:

  • Fast: Leverages uv's Rust-based speed
  • Reproducible: Nix ensures deterministic builds
  • Declarative: Everything in configuration
  • Workspace Support: Handles monorepos and multi-package projects
  • Modern: Uses PEP-621 pyproject.toml standards

Built on pyproject.nix:

  • Uses pyproject.nix infrastructure
  • Supports PEP-621 compliant pyproject.toml
  • Integrates with nixpkgs Python ecosystem

2. Prerequisites

Install uv (if not already installed)

# Via curl
curl -LsSf https://astral.sh/uv/install.sh | sh

# Via nix
nix-env -iA nixpkgs.uv

# Or in NixOS configuration
environment.systemPackages = [ pkgs.uv ];

Install uv2nix

# Via nix
nix-env -f '<nixpkgs>' -iA uv2nix

# Or from flake
nix profile install github:pyproject-nix/uv2nix

# Verify
uv2nix --version

On NixOS

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

Via home-manager

home.packages = with pkgs; [
  uv
  uv2nix
];

3. Basic Usage

Initialize uv Project

# Create new Python project with uv
uv init my-python-app
cd my-python-app

# Add dependencies
uv add requests flask

# Generate lock file
uv lock

# Project structure:
# my-python-app/
# ├── pyproject.toml
# ├── uv.lock
# └── src/
#     └── my_python_app/
#         └── __init__.py

Generate Nix Expressions

# Generate Nix derivations from uv workspace
uv2nix

# This creates/updates:
# - default.nix (or checks flake.nix)
# - Nix expressions for the workspace

Build with Nix

# Build the package
nix-build

# Or with flakes
nix build

# Run the application
./result/bin/my-python-app

4. Project Structure

pyproject.toml - Project metadata:

[project]
name = "my-python-app"
version = "0.1.0"
description = "My Python application"
requires-python = ">=3.11"
dependencies = [
    "requests>=2.31.0",
    "flask>=3.0.0",
]

[project.scripts]
my-app = "my_python_app.main:main"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.uv]
dev-dependencies = [
    "pytest>=7.4.0",
    "black>=23.0.0",
    "ruff>=0.1.0",
]

uv.lock - Lock file with pinned versions:

# Generated by uv
# Contains exact dependency versions
# Should be committed to version control

5. Workspace Support

Monorepo Structure

# Create workspace
mkdir my-workspace
cd my-workspace

# Initialize workspace
cat > pyproject.toml <<EOF
[tool.uv.workspace]
members = ["packages/*"]
EOF

# Create packages
mkdir -p packages/lib packages/app

# Package 1: Library
cd packages/lib
uv init
cat > pyproject.toml <<EOF
[project]
name = "my-lib"
version = "0.1.0"
EOF

# Package 2: Application
cd ../app
uv init
cat > pyproject.toml <<EOF
[project]
name = "my-app"
version = "0.1.0"
dependencies = ["my-lib"]
EOF

cd ../..
uv lock

Generate Nix for Workspace

# At workspace root
uv2nix

# Generates derivations for all workspace members

6. Flake Integration

flake.nix for Python Project

{
  description = "My Python application using uv2nix";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
    pyproject-nix = {
      url = "github:pyproject-nix/pyproject.nix";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    uv2nix = {
      url = "github:pyproject-nix/uv2nix";
      inputs.nixpkgs.follows = "nixpkgs";
      inputs.pyproject-nix.follows = "pyproject-nix";
    };
  };

  outputs = { self, nixpkgs, pyproject-nix, uv2nix }:
    let
      forAllSystems = nixpkgs.lib.genAttrs [
        "x86_64-linux"
        "aarch64-linux"
        "x86_64-darwin"
        "aarch64-darwin"
      ];
    in
    {
      packages = forAllSystems (system:
        let
          pkgs = nixpkgs.legacyPackages.${system};

          # Load workspace
          workspace = uv2nix.lib.${system}.loadWorkspace { workspaceRoot = ./.; };

          # Create Python environment
          pythonSet = pkgs.callPackage pyproject-nix.build.packages {
            python = pkgs.python311;
          };

          # Build application
          app = pythonSet.mkPyprojectApplication {
            inherit workspace;
          };
        in
        {
          default = app;
          my-python-app = app;
        }
      );

      devShells = forAllSystems (system:
        let
          pkgs = nixpkgs.legacyPackages.${system};

          workspace = uv2nix.lib.${system}.loadWorkspace { workspaceRoot = ./.; };

          pythonEnv = pkgs.python311.withPackages (ps:
            uv2nix.lib.${system}.getWorkspacePackages workspace ps
          );
        in
        {
          default = pkgs.mkShell {
            packages = [
              pythonEnv
              pkgs.uv
            ];

            shellHook = ''
              echo "Python development environment"
              echo "Python: $(python --version)"
              echo "uv: $(uv --version)"
            '';
          };
        }
      );

      apps = forAllSystems (system: {
        default = {
          type = "app";
          program = "${self.packages.${system}.default}/bin/my-app";
        };
      });
    };
}

Usage:

# Build
nix build

# Run
nix run

# Development shell
nix develop

# Update dependencies
uv lock
nix flake lock --update-input uv2nix

7. Development Environment

Simple Shell

# shell.nix
{ pkgs ? import <nixpkgs> {} }:

let
  # Load uv2nix
  uv2nix = pkgs.callPackage (pkgs.fetchFromGitHub {
    owner = "pyproject-nix";
    repo = "uv2nix";
    rev = "main";
    sha256 = "...";
  }) {};

  workspace = uv2nix.lib.loadWorkspace { workspaceRoot = ./.; };

  pythonEnv = pkgs.python311.withPackages (ps:
    uv2nix.lib.getWorkspacePackages workspace ps
  );
in
pkgs.mkShell {
  packages = [
    pythonEnv
    pkgs.uv
    pkgs.ruff
    pkgs.black
  ];

  shellHook = ''
    echo "Python development environment ready"
    python --version
  '';
}

Enhanced Development Shell

{ pkgs ? import <nixpkgs> {} }:

let
  workspace = /* load workspace */;
  pythonEnv = /* create environment */;
in
pkgs.mkShell {
  packages = [
    pythonEnv
    pkgs.uv

    # Linting and formatting
    pkgs.ruff
    pkgs.black
    pkgs.mypy

    # Testing
    pkgs.python311Packages.pytest
    pkgs.python311Packages.pytest-cov

    # Development tools
    pkgs.python311Packages.ipython
    pkgs.python311Packages.ipdb
  ];

  shellHook = ''
    # Set up development environment
    export PYTHONPATH="$PWD/src:$PYTHONPATH"
    export UV_CACHE_DIR="$PWD/.uv-cache"

    # Create virtual environment if needed
    if [ ! -d .venv ]; then
      uv venv
    fi

    source .venv/bin/activate

    echo "🐍 Python development environment"
    echo "Python: $(python --version)"
    echo "uv: $(uv --version)"
    echo ""
    echo "Available commands:"
    echo "  uv add <package>  - Add dependency"
    echo "  uv lock           - Update lock file"
    echo "  pytest            - Run tests"
    echo "  ruff check .      - Lint code"
    echo "  black .           - Format code"
  '';
}

8. Building Applications

CLI Application

# pyproject.toml
[project]
name = "my-cli"
version = "1.0.0"
description = "My CLI tool"

[project.scripts]
my-cli = "my_cli.main:cli"

dependencies = [
    "click>=8.0.0",
    "rich>=13.0.0",
]

Build with Nix:

{ pkgs, workspace }:

pkgs.python311Packages.buildPythonApplication {
  pname = "my-cli";
  version = "1.0.0";

  src = ./.;

  propagatedBuildInputs = with pkgs.python311Packages; [
    click
    rich
  ];

  # From workspace
  pyproject = true;

  meta = {
    description = "My CLI tool";
    license = pkgs.lib.licenses.mit;
    mainProgram = "my-cli";
  };
}

Web Application (Flask)

[project]
name = "my-webapp"
version = "1.0.0"

dependencies = [
    "flask>=3.0.0",
    "gunicorn>=21.0.0",
]

[project.scripts]
my-webapp = "my_webapp.app:main"

NixOS Service:

{ config, lib, pkgs, ... }:

let
  myWebapp = /* build from uv2nix */;
in
{
  systemd.services.my-webapp = {
    description = "My Python Web Application";
    after = [ "network.target" ];
    wantedBy = [ "multi-user.target" ];

    serviceConfig = {
      ExecStart = "${myWebapp}/bin/gunicorn -b 0.0.0.0:8000 my_webapp.app:app";
      Restart = "on-failure";
      DynamicUser = true;

      # Security hardening
      ProtectSystem = "strict";
      NoNewPrivileges = true;
      PrivateTmp = true;
    };

    environment = {
      PYTHONUNBUFFERED = "1";
      FLASK_ENV = "production";
    };
  };

  networking.firewall.allowedTCPPorts = [ 8000 ];
}

9. Testing Integration

Test Configuration

# pyproject.toml
[tool.uv]
dev-dependencies = [
    "pytest>=7.4.0",
    "pytest-cov>=4.1.0",
    "pytest-asyncio>=0.21.0",
]

[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["src"]

Run Tests in Nix

{ pkgs, workspace }:

let
  pythonEnv = /* build environment with dev deps */;
in
pkgs.stdenv.mkDerivation {
  name = "my-app-tests";
  src = ./.;

  buildInputs = [ pythonEnv ];

  checkPhase = ''
    pytest tests/ -v --cov=src
  '';

  installPhase = ''
    mkdir -p $out
    echo "Tests passed" > $out/result
  '';
}

CI Integration

# .github/workflows/test.yml
name: Test
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: cachix/install-nix-action@v22
      - uses: cachix/cachix-action@v12
        with:
          name: pyproject-nix
      - run: nix build .#checks.x86_64-linux.default

10. Overlay Creation

Custom Overlay

# overlay.nix
final: prev: {
  pythonPackagesExtensions = prev.pythonPackagesExtensions ++ [
    (python-final: python-prev: {
      my-package = python-final.buildPythonPackage {
        pname = "my-package";
        version = "1.0.0";
        src = ./.;

        propagatedBuildInputs = with python-final; [
          requests
          flask
        ];

        pyproject = true;
      };
    })
  ];
}

Use Overlay

{ pkgs ? import <nixpkgs> {
    overlays = [ (import ./overlay.nix) ];
  }
}:

{
  my-app = pkgs.python311Packages.my-package;
}

11. Common Patterns

Pattern 1: Simple Python Package

# Create project
uv init my-package
cd my-package

# Add dependencies
uv add requests click

# Lock dependencies
uv lock

# Generate Nix expressions
uv2nix

# Build
nix-build

# Install
nix-env -f default.nix -iA package

Pattern 2: Python Library

# pyproject.toml
[project]
name = "my-lib"
version = "0.1.0"
description = "My Python library"

dependencies = [
    "numpy>=1.24.0",
    "pandas>=2.0.0",
]

[build-system]
requires = ["setuptools>=68.0"]
build-backend = "setuptools.build_meta"

Pattern 3: Data Science Project

# Initialize with scientific packages
uv init data-analysis
cd data-analysis

uv add numpy pandas matplotlib scipy jupyter

# Lock
uv lock

# Generate Nix with development deps
# shell.nix for data science
{ pkgs ? import <nixpkgs> {} }:

let
  pythonEnv = pkgs.python311.withPackages (ps: with ps; [
    numpy
    pandas
    matplotlib
    scipy
    jupyter
    ipython
    scikit-learn
  ]);
in
pkgs.mkShell {
  packages = [
    pythonEnv
    pkgs.uv
  ];

  shellHook = ''
    jupyter notebook
  '';
}

Pattern 4: FastAPI Application

[project]
name = "my-api"
version = "1.0.0"

dependencies = [
    "fastapi>=0.104.0",
    "uvicorn[standard]>=0.24.0",
    "pydantic>=2.0.0",
]

[project.scripts]
my-api = "my_api.main:run"

NixOS Service:

systemd.services.my-api = {
  serviceConfig = {
    ExecStart = "${myApi}/bin/uvicorn my_api.main:app --host 0.0.0.0 --port 8000";
    DynamicUser = true;
  };
};

Pattern 5: Django Project

uv init my-django-app
cd my-django-app

uv add django psycopg2-binary gunicorn

# Lock
uv lock
# NixOS module
{ config, pkgs, ... }:

let
  djangoApp = /* build with uv2nix */;
in
{
  services.postgresql = {
    enable = true;
    ensureDatabases = [ "myapp" ];
  };

  systemd.services.django = {
    serviceConfig = {
      ExecStart = "${djangoApp}/bin/gunicorn myapp.wsgi:application";
      Environment = [
        "DJANGO_SETTINGS_MODULE=myapp.settings"
        "DATABASE_URL=postgresql:///myapp"
      ];
    };
  };
}

12. Dependency Management

Adding Dependencies

# Add runtime dependency
uv add requests

# Add with version constraint
uv add "flask>=3.0.0,<4.0.0"

# Add development dependency
uv add --dev pytest black ruff

# Add optional dependency group
uv add --group docs sphinx sphinx-rtd-theme

# Lock after changes
uv lock

Removing Dependencies

# Remove dependency
uv remove requests

# Lock
uv lock

Updating Dependencies

# Update all dependencies
uv lock --upgrade

# Update specific package
uv lock --upgrade-package requests

# Check outdated
uv pip list --outdated

13. NixOS Integration

System Package

# /etc/nixos/configuration.nix
{ config, pkgs, ... }:

let
  myPythonApp = import /path/to/app {
    inherit pkgs;
  };
in
{
  environment.systemPackages = [
    myPythonApp
  ];
}

User Package (home-manager)

# home.nix
{ pkgs, ... }:

let
  myTool = import /path/to/tool {
    inherit pkgs;
  };
in
{
  home.packages = [
    myTool
  ];
}

Custom Module

# modules/my-python-service.nix
{ config, lib, pkgs, ... }:

with lib;

let
  cfg = config.services.my-python-service;

  myApp = import ../python-app {
    inherit pkgs;
  };
in
{
  options.services.my-python-service = {
    enable = mkEnableOption "My Python Service";

    port = mkOption {
      type = types.int;
      default = 8000;
    };

    workers = mkOption {
      type = types.int;
      default = 4;
    };
  };

  config = mkIf cfg.enable {
    systemd.services.my-python-service = {
      description = "My Python Service";
      after = [ "network.target" ];
      wantedBy = [ "multi-user.target" ];

      serviceConfig = {
        ExecStart = ''
          ${myApp}/bin/gunicorn \
            -w ${toString cfg.workers} \
            -b 0.0.0.0:${toString cfg.port} \
            app:application
        '';
        Restart = "on-failure";
        DynamicUser = true;
      };
    };

    networking.firewall.allowedTCPPorts = [ cfg.port ];
  };
}

14. Troubleshooting

Issue 1: Lock File Out of Sync

Problem: Changes to pyproject.toml not reflected

Solution:

# Regenerate lock file
uv lock

# Force update
uv lock --upgrade

# Regenerate Nix expressions
uv2nix

Issue 2: Build Failures

Problem: Package fails to build with Nix

Solution:

# Add missing build dependencies
{ pkgs, ... }:

pkgs.python311Packages.buildPythonPackage {
  # ...

  nativeBuildInputs = with pkgs; [
    python311Packages.setuptools
    python311Packages.wheel
  ];

  buildInputs = with pkgs; [
    # System dependencies
    gcc
    pkg-config
    openssl
  ];
}

Issue 3: Import Errors

Problem: Module not found at runtime

Solution:

# Ensure proper Python path
{
  shellHook = ''
    export PYTHONPATH="$PWD/src:$PYTHONPATH"
  '';
}

Issue 4: Native Extensions

Problem: Package with C extensions fails

Solution:

# Ensure build tools in environment
buildInputs = with pkgs; [
  python311Packages.setuptools
  gcc
  python311.pkgs.cython
];

Issue 5: Workspace Resolution

Problem: Workspace packages not found

Solution:

# Ensure proper workspace structure
# pyproject.toml at root with:
[tool.uv.workspace]
members = ["packages/*"]

# Regenerate
uv lock
uv2nix

15. Best Practices

DO ✅

  1. Always commit lock files

    git add uv.lock
    git commit -m "Lock dependencies"
    
  2. Use PEP-621 pyproject.toml

    [project]
    name = "my-package"
    version = "0.1.0"
    requires-python = ">=3.11"
    
  3. Pin Python version

    [project]
    requires-python = ">=3.11,<3.12"
    
  4. Version control Nix expressions

    git add default.nix flake.nix
    
  5. Use development dependencies

    uv add --dev pytest ruff black
    
  6. Test in clean environment

    nix-build
    ./result/bin/my-app
    
  7. Document dependencies

    [project]
    dependencies = [
        "requests>=2.31.0",  # HTTP client
        "click>=8.0.0",      # CLI framework
    ]
    
  8. Use flakes for reproducibility

    inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
    
  9. Leverage workspace features

    [tool.uv.workspace]
    members = ["packages/*"]
    
  10. Keep uv and uv2nix updated

    uv self update
    nix profile upgrade uv2nix
    

DON'T ❌

  1. Don't commitpycache

    __pycache__/
    *.pyc
    
  2. Don't skip lock file

    # ❌ Bad
    uv add requests  # without uv lock
    
    # ✅ Good
    uv add requests && uv lock
    
  3. Don't mix pip and uv

    # ❌ Don't use pip install with uv project
    # ✅ Use uv add
    
  4. Don't hardcode paths

    # ❌ Bad
    config_path = "/home/user/config.json"
    
    # ✅ Good
    config_path = os.path.expanduser("~/.config/myapp/config.json")
    
  5. Don't commit secrets

    # Use environment variables or secret management
    # Never commit API keys, passwords
    
  6. Don't skip tests

    # Always run tests before committing
    pytest tests/
    
  7. Don't ignore warnings

    # Address deprecation warnings
    # Fix type errors
    

Command Reference

# uv commands
uv init [project]              # Initialize project
uv add <package>              # Add dependency
uv add --dev <package>        # Add dev dependency
uv remove <package>           # Remove dependency
uv lock                       # Generate lock file
uv lock --upgrade             # Update all dependencies
uv sync                       # Install dependencies
uv run <command>              # Run in environment
uv tree                       # Show dependency tree

# uv2nix commands
uv2nix                        # Generate Nix expressions
uv2nix --help                 # Show help

# Nix commands
nix-build                     # Build package
nix-shell                     # Enter dev environment
nix flake init               # Initialize flake
nix build                     # Build (flakes)
nix develop                   # Dev shell (flakes)
nix run                       # Run app (flakes)

Success Metrics

  • Reproducible: Same pyproject.toml → same build
  • Fast: uv's Rust-based speed + Nix caching
  • Declarative: Everything in configuration
  • Workspace Support: Monorepo capabilities
  • Modern: PEP-621 compliant
  • Integrated: Works with NixOS services
  • Tested: CI integration validated

Ready to build reproducible Python applications with uv2nix! 🐍