Agent skill

nix-best-practices

Nix patterns for flakes, overlays, unfree handling, and binary overlays. Use when working with flake.nix or shell.nix.

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/nix-best-practices

SKILL.md

Nix Best Practices

Flake Structure

Standard flake.nix structure:

nix
{
  description = "Project description";

  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 = import nixpkgs {
          inherit system;
        };
      in {
        devShells.default = pkgs.mkShell {
          buildInputs = with pkgs; [
            # packages here
          ];
        };
      });
}

Follows Pattern (Avoid Duplicate Nixpkgs)

When adding overlay inputs, use follows to share the parent nixpkgs and avoid downloading multiple versions:

nix
inputs = {
  nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";

  # Overlay follows parent nixpkgs
  some-overlay.url = "github:owner/some-overlay";
  some-overlay.inputs.nixpkgs.follows = "nixpkgs";

  # Chain follows through intermediate inputs
  another-overlay.url = "github:owner/another-overlay";
  another-overlay.inputs.nixpkgs.follows = "some-overlay";
};

All inputs must be listed in outputs function even if not directly used:

nix
outputs = { self, nixpkgs, some-overlay, another-overlay, ... }:

Applying Overlays

Overlays modify or add packages to nixpkgs:

nix
let
  pkgs = import nixpkgs {
    inherit system;
    overlays = [
      overlay1.overlays.default
      overlay2.overlays.default
      # Inline overlay
      (final: prev: {
        myPackage = prev.myPackage.override { ... };
      })
    ];
  };
in

Handling Unfree Packages

Option 1: nixpkgs-unfree (Recommended for Teams)

Use numtide/nixpkgs-unfree for EULA-licensed packages without requiring user config:

nix
inputs = {
  nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
  nixpkgs-unfree.url = "github:numtide/nixpkgs-unfree/nixos-unstable";
  nixpkgs-unfree.inputs.nixpkgs.follows = "nixpkgs";

  # Unfree overlay follows nixpkgs-unfree
  proprietary-tool.url = "github:owner/proprietary-tool-overlay";
  proprietary-tool.inputs.nixpkgs.follows = "nixpkgs-unfree";
};

This chains: proprietary-toolnixpkgs-unfreenixpkgs

Option 2: User Config

Users add to ~/.config/nixpkgs/config.nix:

nix
{ allowUnfree = true; }

Option 3: Specific Packages (Flake)

nix
let
  pkgs = import nixpkgs {
    inherit system;
    config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [
      "specific-package"
    ];
  };
in

Note: config.allowUnfree in flake.nix doesn't work with nix develop - use nixpkgs-unfree or user config.

Creating Binary Overlay Repos

When nixpkgs builds a community version lacking features (common with open-core tools), create an overlay that fetches official binaries.

Pattern (see 0xBigBoss/atlas-overlay, 0xBigBoss/bun-overlay)

nix
{
  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};

        version = "1.0.0";

        # Platform-specific binaries
        sources = {
          "x86_64-linux" = {
            url = "https://example.com/tool-linux-amd64-v${version}";
            sha256 = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
          };
          "aarch64-linux" = {
            url = "https://example.com/tool-linux-arm64-v${version}";
            sha256 = "sha256-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=";
          };
          "x86_64-darwin" = {
            url = "https://example.com/tool-darwin-amd64-v${version}";
            sha256 = "sha256-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC=";
          };
          "aarch64-darwin" = {
            url = "https://example.com/tool-darwin-arm64-v${version}";
            sha256 = "sha256-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD=";
          };
        };

        source = sources.${system} or (throw "Unsupported system: ${system}");

        toolPackage = pkgs.stdenv.mkDerivation {
          pname = "tool";
          inherit version;

          src = pkgs.fetchurl {
            inherit (source) url sha256;
          };

          sourceRoot = ".";
          dontUnpack = true;

          installPhase = ''
            mkdir -p $out/bin
            cp $src $out/bin/tool
            chmod +x $out/bin/tool
          '';

          meta = with pkgs.lib; {
            description = "Tool description";
            homepage = "https://example.com";
            license = licenses.unfree;  # or appropriate license
            platforms = builtins.attrNames sources;
          };
        };
      in {
        packages.default = toolPackage;
        packages.tool = toolPackage;

        overlays.default = final: prev: {
          tool = toolPackage;
        };
      })
    // {
      overlays.default = final: prev: {
        tool = self.packages.${prev.system}.tool;
      };
    };
}

Getting SHA256 Hashes

bash
nix-prefetch-url https://example.com/tool-linux-amd64-v1.0.0
# Returns hash in base32, convert to SRI format:
nix hash to-sri --type sha256 <base32-hash>

Or use SRI directly:

bash
nix-prefetch-url --type sha256 https://example.com/tool-linux-amd64-v1.0.0

Dev Shell Patterns

Basic Shell

nix
devShells.default = pkgs.mkShell {
  buildInputs = with pkgs; [
    nodejs
    python3
  ];

  shellHook = ''
    echo "Dev environment ready"
  '';
};

With Environment Variables

nix
devShells.default = pkgs.mkShell {
  buildInputs = with pkgs; [ postgresql ];

  # Set at shell entry
  DATABASE_URL = "postgres://localhost/dev";

  # Or in shellHook for dynamic values
  shellHook = ''
    export PROJECT_ROOT="$(pwd)"
  '';
};

Native Dependencies (C Libraries)

nix
devShells.default = pkgs.mkShell {
  buildInputs = with pkgs; [
    openssl
    postgresql
  ];

  # Expose headers and libraries
  shellHook = ''
    export C_INCLUDE_PATH="${pkgs.openssl.dev}/include:$C_INCLUDE_PATH"
    export LIBRARY_PATH="${pkgs.openssl.out}/lib:$LIBRARY_PATH"
    export PKG_CONFIG_PATH="${pkgs.openssl.dev}/lib/pkgconfig:$PKG_CONFIG_PATH"
  '';
};

Direnv Integration

.envrc for flake projects:

bash
use flake

For unfree packages without nixpkgs-unfree:

bash
export NIXPKGS_ALLOW_UNFREE=1
use flake --impure

Common Commands

bash
# Update all inputs
nix flake update

# Update specific input
nix flake update some-input

# Check flake validity
nix flake check

# Show flake metadata
nix flake metadata

# Enter dev shell
nix develop

# Run command in dev shell
nix develop -c <command>

# Build package
nix build .#packageName

# Run package
nix run .#packageName

Troubleshooting

"unexpected argument" Error

All inputs must be listed in outputs function:

nix
# Wrong
outputs = { self, nixpkgs }: ...

# Right (if you have more inputs)
outputs = { self, nixpkgs, other-input, ... }: ...

Unfree Package Errors with nix develop

config.allowUnfree in flake.nix doesn't propagate to nix develop. Use:

  1. nixpkgs-unfree input (recommended)
  2. User's ~/.config/nixpkgs/config.nix
  3. NIXPKGS_ALLOW_UNFREE=1 nix develop --impure

Duplicate Nixpkgs Downloads

Use follows to chain inputs to a single nixpkgs source.

Overlay Not Applied

Ensure overlay is in the overlays list when importing nixpkgs:

nix
pkgs = import nixpkgs {
  inherit system;
  overlays = [ my-overlay.overlays.default ];
};

Hash Mismatch

Re-fetch with nix-prefetch-url and update the hash. Hashes change when upstream updates binaries at the same URL.

Didn't find tool you were looking for?

Be as detailed as possible for better results