Smithery Logo
MCPsSkillsDocsPricing
Login
Smithery Logo

Accelerating the Agent Economy

Resources

DocumentationPrivacy PolicySystem Status

Company

PricingAboutBlog

Connect

© 2026 Smithery. All rights reserved.

    ojii3

    using-flake-parts

    ojii3/using-flake-parts
    DevOps
    13
    2 installs

    About

    SKILL.md

    Install

    Install via Skills CLI

    or add to your agent
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    ├─
    ├─
    └─

    About

    Expert guidance for using flake-parts framework in Nix flakes.

    SKILL.md

    Flake-Parts Expert

    Specialized guidance for the flake-parts framework - a modular system for organizing Nix flakes.

    What is Flake-Parts?

    Flake-parts is a framework that applies the NixOS module system to flake organization. It eliminates boilerplate for multi-system builds by generating per-system outputs automatically.

    Core benefit: Define packages once in perSystem, automatically generated for all target systems.

    Structure with mkFlake

    Flake-parts organizes flakes into logical sections:

    {
      inputs.flake-parts.url = "github:hercules-ci/flake-parts";
    
      outputs = inputs@{ flake-parts, ... }:
        flake-parts.lib.mkFlake { inherit inputs; } {
          # Target architectures - define once
          systems = [ "x86_64-linux" "aarch64-linux" ];
    
          # External modules
          imports = [ ./modules/packages.nix ];
    
          # Multi-system configuration (defined once, generated for all systems)
          perSystem = { config, pkgs, system, ... }: {
            packages.hello = pkgs.hello;
            devShells.default = pkgs.mkShell {
              packages = [ config.packages.hello ];
            };
          };
    
          # Traditional flake-level attributes (single-system)
          flake = {
            nixosConfigurations.machine = { };
          };
        };
    }
    

    Generated structure:

    # Input:
    perSystem.packages.hello = pkgs.hello;
    
    # Output:
    packages.x86_64-linux.hello = <derivation>;
    packages.aarch64-linux.hello = <derivation>;
    

    perSystem vs flake

    Use perSystem for things that build across multiple platforms:

    • Packages, devShells, apps
    • Formatters, checks
    • Anything that should exist per-system

    Use flake for unique, non-system-specific outputs:

    • nixosConfigurations (each machine is unique)
    • homeConfigurations (each config is unique)
    • Custom flake outputs

    Standard perSystem Options

    Flake-parts provides these standard options in perSystem:

    • packages - Derivations to build (e.g., packages.myapp = pkgs.hello;)
    • apps - Executable applications (for nix run)
    • devShells - Development environments (for nix develop)
    • checks - Tests and validation (run with nix flake check)
    • formatter - Code formatter (single package, run with nix fmt)
    • legacyPackages - Large package sets (not evaluated by default, for performance)

    All are automatically generated for each system in the systems list.

    Module Arguments

    Flake-parts provides special arguments to avoid repetitive .${system} interpolation.

    Per-System Arguments (in perSystem)

    pkgs - nixpkgs for current system:

    perSystem = { pkgs, ... }: {
      packages.myapp = pkgs.writeShellScriptBin "myapp" "echo hello";
    };
    

    system - Current architecture string:

    perSystem = { system, ... }: {
      # system = "x86_64-linux", "aarch64-linux", etc.
    };
    

    inputs' (inputs prime) - Inputs with system auto-selected:

    # Without inputs':
    packages.bar = inputs.foo.packages.${system}.bar;
    
    # With inputs':
    perSystem = { inputs', ... }: {
      packages.bar = inputs'.foo.packages.bar;
    };
    

    self' (self prime) - This flake's outputs with system pre-selected:

    perSystem = { self', ... }: {
      devShells.default = pkgs.mkShell {
        packages = [ self'.packages.myapp ];
      };
    };
    

    config - Per-system configuration values:

    perSystem = { config, ... }: {
      packages.foo = ...;
      packages.bar = ... config.packages.foo ...;  # Reference other packages
    };
    

    final (with easyOverlay) - Package set after overlays:

    perSystem = { pkgs, final, ... }: {
      imports = [ inputs.flake-parts.flakeModules.easyOverlay ];
    
      packages.lib = pkgs.callPackage ./lib.nix { };
      packages.app = pkgs.callPackage ./app.nix {
        my-lib = final.lib;  # Use overlaid version
      };
    };
    

    Top-Level Arguments

    withSystem - Enter a system's scope to access perSystem values:

    This bridges single-system outputs (like NixOS configs) with multi-system packages:

    flake.nixosConfigurations.machine = withSystem "x86_64-linux" (
      { config, ... }:
        # Now have access to all perSystem arguments
        nixpkgs.lib.nixosSystem {
          modules = [{
            environment.systemPackages = [
              config.packages.myapp    # Access perSystem packages
              config.packages.mytool
            ];
          }];
        }
    );
    

    Without withSystem: self.packages.x86_64-linux.myapp (repetitive and verbose).

    getSystem - Function to retrieve per-system config:

    let
      x86Packages = (getSystem "x86_64-linux").packages;
    in
      # Use packages from specific system
    

    moduleWithSystem - Brings perSystem arguments into top-level module scope (advanced).

    Function Signature Inspection

    The module system uses builtins.functionArgs to determine which arguments to pass:

    # ✅ CORRECT - explicitly name what you need
    { pkgs, system, config, ... }: { }
    
    # ❌ WRONG - catch-all doesn't get special arguments
    args: { }  # args won't contain pkgs, system, etc.
    

    Only named parameters in your function signature receive values.

    The @ Pattern

    Access multiple scopes without shadowing:

    { config, ... }: {
      myTopLevelOption = "foo";
    
      perSystem = toplevel@{ config, pkgs, ... }: {
        # config          = per-system config
        # toplevel.config = top-level config
    
        packages.example = pkgs.writeText "value"
          toplevel.config.myTopLevelOption;
      };
    }
    

    Essential Patterns

    Convert Standard Flake to Flake-Parts

    Before:

    outputs = { nixpkgs, ... }:
      let
        systems = [ "x86_64-linux" "aarch64-linux" ];
        forAllSystems = nixpkgs.lib.genAttrs systems;
      in {
        packages = forAllSystems (system: {
          hello = nixpkgs.legacyPackages.${system}.hello;
        });
      };
    

    After:

    outputs = inputs@{ flake-parts, ... }:
      flake-parts.lib.mkFlake { inherit inputs; } {
        systems = [ "x86_64-linux" "aarch64-linux" ];
        perSystem = { pkgs, ... }: {
          packages.hello = pkgs.hello;
        };
      };
    

    Simple Module Import

    # flake.nix
    {
      imports = [ ./modules/packages.nix ];
    }
    
    # modules/packages.nix
    { perSystem = { pkgs, ... }: {
      packages.hello = pkgs.hello;
    }; }
    

    importApply (pass flake-level context to modules)

    Flake-parts-specific utility for passing arguments like withSystem to modules:

    # flake.nix
    {
      imports = [
        (inputs.flake-parts.lib.importApply ./modules/nixos.nix {
          inherit withSystem;
        })
      ];
    }
    
    # modules/nixos.nix
    { withSystem }: { inputs, ... }: {
      flake.nixosConfigurations.machine = withSystem "x86_64-linux" (
        { config, ... }:
          inputs.nixpkgs.lib.nixosSystem { ... }
      );
    }
    

    Why importApply is needed: Modules imported via imports don't have access to the flake's lexical scope (like withSystem). importApply lets you pass those as arguments.

    easyOverlay Module

    Flake-parts module that auto-generates overlays from perSystem packages:

    perSystem = { config, pkgs, final, ... }: {
      imports = [ inputs.flake-parts.flakeModules.easyOverlay ];
    
      packages = {
        mylib = pkgs.stdenv.mkDerivation { ... };
        myapp = pkgs.stdenv.mkDerivation {
          buildInputs = [ final.mylib ];  # Use overlaid version
        };
      };
    
      # Automatically generates overlays.default
      overlayAttrs = {
        inherit (config.packages) mylib myapp;
      };
    };
    

    Key distinction:

    • pkgs = "previous" package set (before overlay)
    • final = "final" package set (after overlay)

    Use final when packages reference each other to get the overlaid versions.

    Reusable Flake Modules

    Export modules for use in other flakes:

    # your-tool/flake.nix
    {
      flake.flakeModules.default = {
        perSystem = { config, lib, pkgs, ... }: {
          options.your-tool = {
            enable = lib.mkEnableOption "your-tool";
            package = lib.mkOption {
              type = lib.types.package;
              default = pkgs.your-tool;
            };
          };
    
          config = lib.mkIf config.your-tool.enable {
            packages.your-tool = config.your-tool.package;
          };
        };
      };
    }
    
    # consumer-flake/flake.nix
    {
      inputs.your-tool.url = "github:you/your-tool";
    
      outputs = inputs@{ flake-parts, ... }:
        flake-parts.lib.mkFlake { inherit inputs; } {
          imports = [ inputs.your-tool.flakeModules.default ];
    
          perSystem.your-tool.enable = true;
        };
    }
    

    Best Practices

    1. Don't traverse inputs - Never iterate through inputs with mapAttrs or similar (causes unnecessary fetching and evaluation)
    2. Namespace options - Use mymodule.foo not just foo to avoid collisions
    3. Favor perSystem - Most work happens there (packages, shells, checks)
    4. Use specific options - Prefer foo.package over foo.flake for better granularity
    5. Use ' suffixed arguments - Prefer inputs' and self' over manual system selection

    Debugging

    Enable debug mode:

    {
      debug = true;
      # ... rest of config
    }
    

    Inspect with nix repl:

    nix repl
    :lf .
    currentSystem.allModuleArgs.pkgs        # Inspect current system pkgs
    debug.allSystems.x86_64-linux           # Inspect specific system
    currentSystem.options.packages.files    # See where values are defined
    debug.options.systems.declarations      # See where options are declared
    

    Common Issues

    "path does not exist" Error

    Files must be git-tracked for flakes to see them:

    git add .claude/skills/flake-parts/
    # OR for quick testing:
    git add -N file.nix  # Track without staging content
    

    Circular Dependency

    Don't access self directly in modules. Use self' in perSystem or return functions from top-level.

    Wrong Module Context

    Use @ syntax to access both top-level and perSystem config:

    perSystem = toplevel@{ config, ... }: {
      # config = perSystem config
      # toplevel.config = top-level config
    }
    

    Undefined Variable in Module Argument

    The module system only passes arguments you explicitly name:

    # ✅ CORRECT
    { pkgs, system, config, ... }: { }
    
    # ❌ WRONG
    args: { }  # Won't receive special arguments
    

    Beyond the Basics

    For specialized flake-parts features, load these guides:

    • module-arguments.md - Complete reference for all module arguments, the @ pattern, and function signature inspection
    • overlays.md - easyOverlay module details, final vs pkgs distinction
    • modular-organization.md - importApply patterns, reusable flakeModules, dogfooding
    • advanced.md - Partitions, custom outputs, debug mode, migration from standard flakes
    Repository
    ojii3/dotfiles
    Files