nix-guidelines
Use when writing Nix code for flakes, modules, or packages
When & Why to Use This Skill
This Claude skill provides a comprehensive framework for writing idiomatic Nix code, specifically optimized for flakes, modules, and packages. It enforces industry-standard best practices, such as avoiding 'with' statements and 'rec' blocks, to ensure code is maintainable, readable, and compatible with static analysis tools like nixd and nil. By following these guidelines, users can generate robust NixOS configurations and Home Manager modules that adhere to the nixfmt-rfc-style.
Use Cases
- Generating idiomatic flake.nix files: Create well-structured flakes with optimized input follows and clear output definitions.
- Building NixOS and Home Manager modules: Design modular system configurations with properly namespaced options and strict type checking.
- Refactoring legacy Nix expressions: Clean up existing code by replacing ambiguous 'with' statements and 'rec' sets with explicit attribute paths and let-in blocks.
- Creating reproducible package derivations: Write standardized Nix packages with correct build inputs, fetchers, and metadata.
- Implementing complex system logic: Use advanced patterns like lib.mkMerge and assertions to create reliable, conditional configuration sets.
| name | nix-guidelines |
|---|---|
| description | Use when writing Nix code for flakes, modules, or packages |
Nix Guidelines
Guidelines for idiomatic Nix code with flakes and modules.
When to Use
- Writing flake.nix files
- Creating NixOS/Home Manager modules
- Writing Nix packages
- Reviewing Nix code
Critical Rule: No with Statements
NEVER use with statements. They:
- Break static analysis tools (nixd, nil)
- Create scope ambiguity
- Cripple IDE features
# ❌ WRONG
environment.systemPackages = with pkgs; [ git vim ];
meta = with lib; { license = licenses.mit; };
# ✅ CORRECT
environment.systemPackages = [ pkgs.git pkgs.vim ];
meta = { license = lib.licenses.mit; };
Core Principles
- Explicit over Implicit - Always use explicit attribute paths
- Declarative Design - Think in systems, not scripts
- Pure Functions - Leverage Nix's functional nature
- nixfmt-rfc-style - Standard formatter
Flake Structure
{
description = "My project";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs"; # Deduplicate
};
};
outputs = { self, nixpkgs, home-manager, ... }: {
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./configuration.nix
home-manager.nixosModules.home-manager
];
};
};
}
Module Structure
{
lib,
config,
pkgs,
...
}:
let
cfg = config.myNamespace.myModule;
in
{
options.myNamespace.myModule = {
enable = lib.mkEnableOption "my module";
package = lib.mkOption {
type = lib.types.package;
default = pkgs.myPackage;
description = "The package to use";
};
settings = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
default = { };
description = "Configuration settings";
};
};
config = lib.mkIf cfg.enable {
environment.systemPackages = [ cfg.package ];
assertions = [
{
assertion = cfg.settings != { };
message = "myModule.settings cannot be empty when enabled";
}
];
};
}
Best Practices Checklist
- No
withstatements - Explicit destructuring in function signatures
- nixfmt-rfc-style formatting
- Options properly namespaced
- Input follows optimized (for flakes)
- Prefer let-in over rec
- lib.mkIf for conditional config
- Assertions for validation
- Comments for complex logic
Common Patterns
Conditional Configuration
config = lib.mkMerge [
(lib.mkIf cfg.enable {
services.myService.enable = true;
})
(lib.mkIf (cfg.enable && cfg.monitoring.enable) {
services.prometheus.exporters.myService.enable = true;
})
{
# Always applied with lower priority
services.myService.port = lib.mkDefault 8080;
}
];
Package Definition
{ lib, stdenv, fetchurl, openssl }:
stdenv.mkDerivation {
pname = "mypackage";
version = "1.0";
src = fetchurl {
url = "https://example.com/source.tar.gz";
sha256 = lib.fakeSha256;
};
buildInputs = [ openssl ];
meta = {
description = "My package";
license = lib.licenses.mit;
};
}
Script Wrapper
pkgs.writeShellApplication {
name = "my-script";
runtimeInputs = [ pkgs.jq pkgs.curl ];
text = ''
curl -s https://api.example.com | jq .
'';
}
Anti-Patterns to Avoid
Don't Use rec
# ❌ Avoid rec
rec {
a = 1;
b = a + 1;
}
# ✅ Use let-in
let
a = 1;
in {
inherit a;
b = a + 1;
}
Explicit Destructuring
# ❌ Wrong
args: with args; stdenv.mkDerivation { ... }
# ✅ Correct
{ stdenv, fetchurl, lib }: stdenv.mkDerivation { ... }
Performance Tips
- Use
inputs.nixpkgs.followsto deduplicate inputs - Split outputs for granular dependencies
- Profile with
NIX_SHOW_STATS=1 nix build - Consider
builtins.readFilefor large strings
Validation Commands
# Check flake
nix flake check
# Format code
nixfmt-rfc-style .
# Evaluate without building
nix eval .#packages.x86_64-linux.default
# Show flake outputs
nix flake show
Module Option Types
lib.types.bool # Boolean
lib.types.str # String
lib.types.int # Integer
lib.types.path # Path
lib.types.package # Nix package
lib.types.listOf type # List of type
lib.types.attrsOf type # Attribute set of type
lib.types.nullOr type # Type or null
lib.types.enum ["a" "b"] # Enumeration
lib.types.submodule { } # Nested options
Remember
Minor verbosity from explicit patterns is a feature - it makes code self-documenting and machine-readable.