| name | Nix Ecosystem |
| description | This skill should be used when the user asks to "write nix", "nix expression", "flake.nix", "home-manager config", "programs.*", "services.*", "nixpkgs packaging", "buildGoModule", "buildRustPackage", or works with Nix language, flakes, or Home Manager. Provides comprehensive Nix ecosystem patterns and best practices. |
| version | 2.0.0 |
Provide comprehensive patterns for Nix language, flakes, and Home Manager configuration.
<nix_language>
Nix is lazily evaluated. Expressions are only computed when needed.
Only evaluates needed attributes
let
expensive = builtins.trace "Computing expensive" (1 + 1);
in
{ a = 1; b = expensive; }.a # Does not compute expensive
<concept name="pure_functions">
<description>All Nix functions are pure. Same inputs always produce same outputs.</description>
<example>
<note>Pure function - always returns same result for same input</note>
double = x: x * 2;
<note>Avoid side effects; use derivations for build actions</note>
buildResult = pkgs.stdenv.mkDerivation { ... };
</example>
</concept>
<concept name="attribute_sets">
<description>Primary data structure in Nix</description>
<example>
<note>Basic attribute set</note>
{ attr1 = value1; attr2 = value2; }
<note>Access patterns</note>
set.attr
set."attr-with-dashes"
<note>Recursive attribute set</note>
rec { a = 1; b = a + 1; }
</example>
</concept>
Local bindings for complex expressions
let
helper = x: x + 1;
value = helper 5;
in
value * 2
<pattern name="with">
<description>Bring attribute set into scope</description>
<example>
with pkgs; [ git vim tmux ]
</example>
<warning>Avoid nested with; prefer explicit references for clarity</warning>
</pattern>
<pattern name="inherit">
<description>Copy attributes from another set</description>
<example>
{ inherit (pkgs) git vim; inherit name version; }
</example>
</pattern>
<pattern name="overlay">
<description>Modify or extend nixpkgs</description>
<example>
final: prev: {
myPackage = prev.myPackage.override { ... };
}
</example>
<decision_tree name="when_to_use">
<question>Do you need to modify existing packages or add new ones globally?</question>
<if_yes>Use overlays to extend nixpkgs</if_yes>
<if_no>Use local package definitions with callPackage</if_no>
</decision_tree>
</pattern>
<pattern name="callPackage">
<description>Dependency injection pattern</description>
<example>
myPackage = pkgs.callPackage ./package.nix { };
</example>
</pattern>
<pattern name="mkDerivation">
<description>Standard package builder</description>
<example>
pkgs.stdenv.mkDerivation {
pname = "mypackage";
version = "1.0.0";
src = fetchFromGitHub { ... };
nativeBuildInputs = [ pkgs.cmake ];
buildInputs = [ pkgs.openssl ];
installPhase = ''
mkdir -p $out/bin
cp mypackage $out/bin/
'';
}
</example>
<note>Required attributes: pname, version, src</note>
<note>Standard phases: unpackPhase, patchPhase, configurePhase, buildPhase, installPhase</note>
</pattern>
<pattern name="build_inputs">
<description>Dependency specification in derivations</description>
<example>
{
# Tools run at build time (compilers, build tools)
nativeBuildInputs = [ cmake pkg-config ];
# Libraries linked at runtime
buildInputs = [ openssl zlib ];
}
</example>
</pattern>
<pattern name="options_config">
<description>NixOS/Home Manager module structure</description>
<example>
{ config, lib, pkgs, ... }:
{
options.myModule = {
enable = lib.mkEnableOption "my module";
setting = lib.mkOption {
type = lib.types.str;
default = "value";
description = "A setting";
};
};
config = lib.mkIf config.myModule.enable { # configuration when enabled
};
}
</example>
</pattern>
<pattern name="mkOption">
<description>Define module options with types and defaults</description>
<example>
options.myOption = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Enable my feature";
example = true;
};
</example>
<note>Common types: lib.types.bool, lib.types.str, lib.types.listOf, lib.types.attrsOf</note>
</pattern>
<pattern name="mkEnableOption">
<description>Shorthand for boolean enable option</description>
<example>
enable = lib.mkEnableOption "my service";
</example>
</pattern>
<pattern name="mkPackageOption">
<description>Option for specifying a package with a default from pkgs</description>
<example>
package = lib.mkPackageOption pkgs "mypackage" { };
# With nullable:
package = lib.mkPackageOption pkgs "mypackage" { nullable = true; };
</example>
</pattern>
<pattern name="mkMerge">
<description>Merge multiple configuration sets</description>
<example>
config = lib.mkMerge [
(lib.mkIf config.services.foo.enable { environment.systemPackages = [ pkgs.foo ]; })
(lib.mkIf config.services.bar.enable { environment.systemPackages = [ pkgs.bar ]; })
];
</example>
</pattern>
<pattern name="mkForce">
<description>Override a value with higher priority (force it over other definitions)</description>
<example>
services.openssh.settings.PermitRootLogin = lib.mkForce "no";
</example>
<warning>Use sparingly; prefer lib.mkDefault for setting lower-priority defaults instead</warning>
</pattern>
<anti_patterns>
Directly referencing absolute paths breaks reproducibility
Use fetchurl, fetchFromGitHub, or relative paths within the repository
<avoid name="nested_with">
<description>Multiple nested with statements reduce code clarity</description>
<instead>Prefer explicit attribute access (pkgs.git) for better readability</instead>
</avoid>
<avoid name="rec_overuse">
<description>Recursive attribute sets can be hard to understand and maintain</description>
<instead>Use let-in for complex recursive definitions</instead>
</avoid>
<avoid name="string_interpolation_abuse">
<description>Using string interpolation for path operations is error-prone</description>
<instead>Use lib functions for path manipulation (lib.concatStringsSep, builtins.path)</instead>
</avoid>
<avoid name="with_lib_extensively">
<description>Using "with lib;" at the top of modules pollutes scope and hides where functions come from</description>
<instead>Use explicit attribute access: lib.mkIf, lib.mkOption, lib.types.str. Only use "with" for narrow scopes like package lists (with pkgs; [ ... ])</instead>
</avoid>
<avoid name="fetchurl_without_hash">
<description>Using fetchurl or fetchTarball without a hash (sha256/hash) breaks reproducibility</description>
<instead>Always provide hash or sha256. Use nix-prefetch-url or lib.fakeHash to obtain the correct hash</instead>
</avoid>
<avoid name="legacy_nix_env">
<description>Using nix-env -i for imperative package management creates mutable unreproducible state</description>
<instead>Use declarative configuration via flakes, home.packages, or environment.systemPackages</instead>
</avoid>
<avoid name="mutable_nix_var_state">
<description>Relying on mutable state in /nix/var (e.g., nix-channel, nix-env profiles) breaks reproducibility</description>
<instead>Use flake inputs for pinning and lock files for reproducibility</instead>
</avoid>
</anti_patterns>
</nix_language>
Flakes are the de facto standard for Nix projects. While technically still behind an experimental flag in upstream Nix, they are universally adopted in the ecosystem. Lix (a community fork of the Nix evaluator) also supports flakes.
Basic structure of a flake.nix file
{
description = "Project description";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
};
outputs = { self, nixpkgs, ... }@inputs: { # output attributes
};
}
</example>
Derivations for nix build
packages.x86_64-linux.default = pkgs.hello;
<concept name="devShells">
<description>Development environments for nix develop</description>
<example>
devShells.x86_64-linux.default = pkgs.mkShell {
packages = [ pkgs.nodejs ];
};
</example>
</concept>
<concept name="apps">
<description>Runnable applications for nix run</description>
<example>
apps.x86_64-linux.default = {
type = "app";
program = "${pkgs.hello}/bin/hello";
};
</example>
</concept>
<concept name="overlays">
<description>Nixpkgs overlays</description>
<example>
overlays.default = final: prev: {
myPackage = prev.callPackage ./myPackage.nix { };
};
</example>
</concept>
<concept name="nixosModules">
<description>NixOS modules</description>
<example>
nixosModules.default = { config, lib, pkgs, ... }: {
options.services.myService = { ... };
config = { ... };
};
</example>
</concept>
<concept name="homeManagerModules">
<description>Home Manager modules</description>
<example>
homeManagerModules.default = { config, lib, pkgs, ... }: {
options.programs.myProgram = { ... };
config = { ... };
};
</example>
</concept>
<concept name="nixosConfigurations">
<description>Full NixOS system configurations</description>
<example>
nixosConfigurations.hostname = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [ ./configuration.nix ];
};
</example>
</concept>
<concept name="homeConfigurations">
<description>Home Manager configurations</description>
<example>
homeConfigurations."user@host" = home-manager.lib.homeManagerConfiguration {
pkgs = nixpkgs.legacyPackages.x86_64-linux;
modules = [ ./home.nix ];
};
</example>
</concept>
Reference GitHub repositories as flake inputs
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
# Specific branch
stable.url = "github:NixOS/nixpkgs/nixos-25.11";
# Specific revision
pinned.url = "github:owner/repo/abc123def";
};
<pattern name="follows">
<description>Share input between flakes to avoid duplication</description>
<example>
home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
</example>
</pattern>
<pattern name="flake_false">
<description>Non-flake inputs for legacy repositories</description>
<example>
my-source = {
url = "github:owner/repo";
flake = false;
};
</example>
</pattern>
<pattern name="per_system">
<description>Generate outputs for multiple systems</description>
<example>
outputs = { self, nixpkgs, ... }:
let
systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
forAllSystems = nixpkgs.lib.genAttrs systems;
in {
packages = forAllSystems (system:
let pkgs = nixpkgs.legacyPackages.${system};
in { default = pkgs.hello; }
);
};
</example>
<decision_tree name="when_to_use">
<question>Does your flake need to support multiple platforms?</question>
<if_yes>Use per-system pattern with genAttrs or flake-utils</if_yes>
<if_no>Define outputs for single system directly</if_no>
</decision_tree>
</pattern>
<pattern name="devShell">
<description>Development environment with packages and hooks</description>
<example>
devShells.default = pkgs.mkShell {
packages = with pkgs; [ nodejs yarn ];
shellHook = ''
echo "Development environment ready"
'';
};
</example>
</pattern>
Update all inputs to their latest versions
Regular dependency updates
<tool name="nix flake update input-name">
<description>Update a specific input</description>
<use_case>Selective updates to test compatibility</use_case>
</tool>
<tool name="nix flake show">
<description>Display all flake outputs</description>
<use_case>Exploring available packages and configurations</use_case>
</tool>
<tool name="nix flake check">
<description>Validate flake and run checks</description>
<use_case>CI/CD validation, pre-commit checks</use_case>
</tool>
nixfmt (RFC-style) is the standard Nix formatter, replacing nixpkgs-fmt
nix fmt # uses formatter defined in flake.nix
formatter.x86_64-linux = pkgs.nixfmt-rfc-style;
<tool name="lsp">
<description>Language servers for Nix: nil (nix-community) and nixd (nix-community) are the two main options</description>
<note>nil is lightweight and widely used; nixd provides richer nixpkgs-aware completions</note>
</tool>
<tool name="nix-direnv">
<description>Fast direnv integration for Nix flake devShells, caches environments to avoid slow re-evaluation</description>
<example>
programs.direnv = {
enable = true;
nix-direnv.enable = true;
};
</example>
</tool>
<ecosystem_note>
Lix is a community fork of the Nix evaluator with improved error messages and performance. It is a drop-in replacement for CppNix and supports flakes. Be aware of its existence when users reference it.
</ecosystem_note>
<home_manager>
Standard Home Manager module structure
{ config, pkgs, lib, ... }:
{
options.custom.feature = {
enable = lib.mkEnableOption "feature description";
};
config = lib.mkIf config.custom.feature.enable { # configuration when enabled
};
}
</example>
Organize modules by program name
home-manager/programs/git.nix
home-manager/programs/neovim.nix
home-manager/programs/tmux.nix
<pattern name="by_category">
<description>Organize modules by category</description>
<example>
home-manager/development/default.nix
home-manager/shell/default.nix
home-manager/editors/default.nix
</example>
</pattern>
<pattern name="imports">
<description>Import multiple modules</description>
<example>
imports = [
./programs/git.nix
./programs/neovim.nix
./shell/fish.nix
];
</example>
</pattern>
<pattern name="basic_enable">
<description>Enable a program with defaults</description>
<example>
programs.git.enable = true;
</example>
</pattern>
<pattern name="with_options">
<description>Enable and configure a program</description>
<example>
programs.git = {
enable = true;
userName = "name";
userEmail = "email";
extraConfig = {
core.editor = "nvim";
init.defaultBranch = "main";
};
};
</example>
<decision_tree name="when_to_use">
<question>Does Home Manager provide built-in module for this program?</question>
<if_yes>Use programs.* with configuration options</if_yes>
<if_no>Use home.file or xdg.configFile for manual configuration</if_no>
</decision_tree>
</pattern>
<pattern name="package_override">
<description>Use alternative package version</description>
<example>
programs.git = {
enable = true;
package = pkgs.gitFull;
};
</example>
</pattern>
<pattern name="home.file">
<description>Manage dotfiles directly</description>
<example>
home.file.".config/app/config" = {
source = ./config;
# or
text = ''
key = value
'';
};
</example>
</pattern>
<pattern name="xdg.configFile">
<description>XDG config directory files</description>
<example>
xdg.configFile."app/config".source = ./config;
</example>
</pattern>
<pattern name="home.sessionVariables">
<description>Environment variables for login shells</description>
<example>
home.sessionVariables = {
EDITOR = "nvim";
PAGER = "less";
};
</example>
</pattern>
<pattern name="home.sessionPath">
<description>Add directories to PATH</description>
<example>
home.sessionPath = [ "$HOME/.local/bin" ];
</example>
</pattern>
<common_modules>
Git version control configuration
programs.git = {
enable = true;
userName = "Your Name";
userEmail = "email@example.com";
signing = {
key = "KEY_ID";
signByDefault = true;
};
aliases = {
co = "checkout";
st = "status";
};
extraConfig = {
core.editor = "nvim";
};
};
<concept name="programs.neovim">
<description>Neovim editor configuration</description>
<example>
programs.neovim = {
enable = true;
viAlias = true;
vimAlias = true;
plugins = with pkgs.vimPlugins; [
vim-commentary
vim-surround
];
extraConfig = ''
set number
set relativenumber
'';
extraLuaConfig = ''
vim.opt.expandtab = true
'';
};
</example>
</concept>
<concept name="programs.fish">
<description>Fish shell configuration</description>
<example>
programs.fish = {
enable = true;
shellInit = ''
set -g fish_greeting
'';
shellAliases = {
ll = "ls -lah";
};
functions = {
gitignore = "curl -sL https://www.gitignore.io/api/$argv";
};
plugins = [
{ name = "z"; src = pkgs.fishPlugins.z.src; }
];
};
</example>
</concept>
<concept name="programs.tmux">
<description>Terminal multiplexer configuration</description>
<example>
programs.tmux = {
enable = true;
terminal = "screen-256color";
keyMode = "vi";
plugins = with pkgs.tmuxPlugins; [
sensible
yank
];
extraConfig = ''
set -g mouse on
'';
};
</example>
</concept>
<concept name="programs.direnv">
<description>Directory-specific environment loader</description>
<example>
programs.direnv = {
enable = true;
nix-direnv.enable = true;
enableBashIntegration = true;
enableZshIntegration = true;
};
</example>
</concept>
</common_modules>
<best_practices>
Use programs.* when available instead of manual configuration
<practice priority="critical">
Set home.stateVersion to your initial HM version and do not change after initial setup unless migrating
</practice>
<practice priority="high">
Group related configurations in separate modules for maintainability
</practice>
<practice priority="high">
Use lib.mkIf for conditional configuration
</practice>
<practice priority="medium">
Prefer xdg.configFile over home.file for XDG-compliant apps
</practice>
<practice priority="medium">
Use home.packages for additional packages not configured via programs.*
</practice>
</best_practices>
Track Home Manager state version for compatibility
home.stateVersion = "25.11"; # Stable baseline for 2026-05 configurations.
Do not change after initial setup unless migrating
HM 25.05+ supports minimal mode for faster evaluation
imports = [
"${modulesPath}/programs/fzf.nix"
];
Advanced users optimizing evaluation time
Basic NixOS configuration with Home Manager
nixosConfigurations.hostname = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./configuration.nix
home-manager.nixosModules.home-manager
];
specialArgs = { inherit inputs; };
};
<pattern name="standalone_home_manager">
<description>Standalone Home Manager without NixOS</description>
<example>
homeConfigurations."user@host" = home-manager.lib.homeManagerConfiguration {
pkgs = nixpkgs.legacyPackages.x86_64-linux;
modules = [ ./home.nix ];
extraSpecialArgs = { inherit inputs; };
};
</example>
</pattern>
<pattern name="as_nixos_module">
<description>Home Manager as a NixOS module</description>
<example>
{
imports = [ home-manager.nixosModules.home-manager ];
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.users.username = import ./home.nix;
}
</example>
</pattern>
<nixpkgs_packaging>
Nixpkgs provides language-specific packaging infrastructure documented in doc/languages-frameworks/. Use Context7 with library ID /nixos/nixpkgs to retrieve up-to-date packaging patterns for each language.
<context7_topics>
buildGoModule
<query_alt>go packaging</query_alt>
buildGoModule
<language_version>Go 1.26</language_version>
<related_skill>golang-ecosystem</related_skill>
<topic name="rust">
<query>rustPlatform.buildRustPackage</query>
<query_alt>rust packaging</query_alt>
<builders>rustPlatform.buildRustPackage, rustPlatform.importCargoLock</builders>
<language_version>Rust edition 2024</language_version>
<related_skill>rust-ecosystem</related_skill>
</topic>
<topic name="haskell">
<query>haskellPackages</query>
<query_alt>haskell packaging</query_alt>
<builders>haskellPackages.mkDerivation, haskellPackages.callCabal2nix</builders>
<language_version>GHC 9.14</language_version>
<related_skill>haskell-ecosystem</related_skill>
</topic>
<topic name="php">
<query>buildComposerProject2</query>
<query_alt>php packaging</query_alt>
<builders>php.buildComposerProject2</builders>
<language_version>PHP 8.5</language_version>
<related_skill>php-ecosystem</related_skill>
</topic>
<topic name="swift">
<query>swift packaging</query>
<builders>stdenv.mkDerivation with swift and swiftpm as nativeBuildInputs</builders>
<language_version>Swift 6.3</language_version>
<related_skill>swift-ecosystem</related_skill>
</topic>
<topic name="c_cpp">
<query>cmake</query>
<query_alt>meson</query_alt>
<builders>stdenv.mkDerivation with cmake or meson as nativeBuildInputs</builders>
<related_skill>c-ecosystem, cplusplus-ecosystem</related_skill>
</topic>
<topic name="nodejs">
<query>node packaging</query>
<query_alt>buildNpmPackage</query_alt>
<builders>buildNpmPackage, buildYarnPackage</builders>
<language_version>Node.js 24 LTS</language_version>
<related_skill>typescript-ecosystem</related_skill>
</topic>
<topic name="python">
<query>python packaging</query>
<query_alt>buildPythonPackage</query_alt>
<builders>python3Packages.buildPythonPackage, python3Packages.buildPythonApplication</builders>
<language_version>Python 3.13</language_version>
</topic>
<topic name="common_lisp">
<query>lisp packaging</query>
<builders>sbcl.buildASDFSystem, lispPackages_new.sbclPackages</builders>
<related_skill>common-lisp-ecosystem</related_skill>
</topic>
</context7_topics>
<decision_tree name="packaging_approach">
Is the target language listed in context7_topics above?
<if_yes>Use the corresponding Context7 topic query with library ID /nixos/nixpkgs</if_yes>
<if_no>Use get-library-docs with context7CompatibleLibraryID="/nixos/nixpkgs" and topic="LANGUAGE packaging" as a fallback</if_no>
</decision_tree>
<best_practices>
Always consult Context7 for the latest nixpkgs packaging patterns before writing language-specific derivations
Use language-specific builders (buildGoModule, rustPlatform.buildRustPackage, etc.) instead of raw mkDerivation
Cross-reference with the corresponding ecosystem skill for language-specific conventions
</best_practices>
</nixpkgs_packaging>
devenv 2.0 is the current version. See devenv-ecosystem skill for detailed patterns.
Understand Nix expression requirements
1. Identify target: flake, module, derivation, or expression
Workflow guidance
Step completed
2. Check existing patterns in project
Workflow guidance
Step completed
3. Consult Serena memories for conventions
Workflow guidance
Step completed
Write idiomatic Nix code
1. Follow patterns from decision trees
Workflow guidance
Step completed
2. Use appropriate lib functions
Workflow guidance
Step completed
3. Apply best practices for target type
Workflow guidance
Step completed
Verify Nix expression correctness
1. Check syntax with nix flake check
Workflow guidance
Step completed
2. Verify evaluation with nix eval
Workflow guidance
Step completed
3. Test build with nix build
Workflow guidance
Step completed
<error_escalation>
Style inconsistency in Nix expression
Note issue, suggest formatting
Evaluation error or type mismatch
Debug with --show-trace, fix expression
Build failure in derivation
Analyze build log, present options to user
Impure expression breaking reproducibility
Block operation, require pure alternatives
</error_escalation>
Use lib functions for complex operations
Follow project's existing Nix patterns
Maintain reproducibility in all expressions
Impure paths and absolute references
Nested with statements
Overusing rec for attribute sets
<related_skills>
Symbol operations for navigating Nix expressions and module definitions
Fetch latest nixpkgs and Home Manager documentation
Debug evaluation errors and understand derivation failures
Language-specific conventions for nixpkgs packaging via golang-ecosystem, rust-ecosystem, haskell-ecosystem, php-ecosystem, swift-ecosystem, c-ecosystem, cplusplus-ecosystem, common-lisp-ecosystem
Devenv 2.0 configuration patterns
</related_skills>