Flake + Overlay + CallPackage + Unstable

Hey everyone !

Because I’m using Neovim both on my NixOS computer and Work computer (Ubuntu) I’ve decided to use a FHS environment to run Neovim. This helps avoid doing changes to my config in my work laptop to then see it doesn’t work when I’m on NixOS.

I’ve struggled a lot, but thanks to Ioga Master config I was able to make it work and understand better what overlays are.

My current config looks like this:

My global flake.nix
{
  description = "My first Flake";

  inputs = {
    neovim-flake = { url="./flakes/neovim"; };
    nixpkgs.url = "nixpkgs/nixos-25.11";
    home-manager = {
      url = "github:nix-community/home-manager/release-25.11";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    ashell.url = "github:romanstingler/ashell/e26d2591a3472548b199c5f9396fe7186f1cec56";
    
    
  };

  outputs = { ... }@inputs: {
  

    nixosConfigurations.nixos-btw = inputs.nixpkgs.lib.nixosSystem {
      specialArgs = {
        inherit inputs;
      };
      system = "x86_62-linux";
      modules = [
        ./configuration.nix
        {
          nixpkgs.overlays = [ inputs.neovim-flake.overlays.default ];  # <--- HERE
        } 
        inputs.home-manager.nixosModules.home-manager
        {
          home-manager = {
            extraSpecialArgs = {inherit inputs; };
            useGlobalPkgs = true;
            useUserPackages = true;
            users.henmir = import ./home.nix;
            backupFileExtension = "backup";
          };
        }
      ];
    };
  };
}
neovim/neovim.nix
{ lib, wrapNeovimUnstable, neovim-unwrapped, neovimUtils, writeShellScript
, lua5_1, luarocks, pkg-config, cargo, buildFHSEnv
, tree-sitter,}:

let
  nvim = let
    config = let
      extraPackages = [
        lua5_1
        luarocks
        tree-sitter

      ];
    in neovimUtils.makeNeovimConfig {
      withPython3 = false;
      withRuby = false;
      withNodeJs = false;

      extraLuaPackages = p: with p; [ ];

      inherit extraPackages;

      customRC = ''
        set runtimepath-=~/.config/nvim
        source ~/.config/nvim/init.lua
      '';
    } // {
      wrapperArgs =
        [ "--prefix" "PATH" ":" "${lib.makeBinPath extraPackages}" ];
    };
  in wrapNeovimUnstable neovim-unwrapped config;
in buildFHSEnv {
  name = "nvim";
  targetPkgs = pkgs: [ nvim ];


  runScript = writeShellScript "nvim-fhs.sh" ''
    
    exec ${nvim}/bin/nvim "$@"
  '';
} 
neovim/flake.nix
{
  description = "IogaMaster's Neovim Configuration";

  inputs.nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";

  outputs = { nixpkgs, ... }:
    let
      inherit (nixpkgs) lib;
      forAllSystems = function:
        nixpkgs.lib.genAttrs [ "x86_64-linux" "aarch64-linux" ]
        (system: function nixpkgs.legacyPackages.${system});
    in rec {
      devShells = forAllSystems (pkgs: {
        default =
          pkgs.mkShell { nativeBuildInputs = with pkgs; [ alejandra stylua ]; };
      });

      packages = forAllSystems (pkgs: rec {
        neovim = pkgs.callPackage ./neovim.nix { }; 
      });

      overlays.default = final: prev: {
        neovim = final.callPackage ./neovim.nix { };
      };
    };
}

Now I want to use the unstable channel in order to get the latest version of NeoVim, but was unable to make it work. From what I understand, callPackage automatically pass needed arguments to my neovim.nix file, and it’s using the stable channel to get the neovim-unwrapped package. So I think I need to tell it to get neovim-unwrapped from unstable.

And that’s where I’m stuck, I’ve search the doc to try to find an answer but I’ve been on this for 3 days and can’t find a solution.

I’m pretty sure the solution to this problem should be simple but I’m a big newbie with most tools used for this setup :sweat_smile:

If anyone can help me that would be great ! :grinning_face_with_smiling_eyes:

Edit

If anyone stumble on this in the future, here is what my config looks like after following @oo-infty advice:

My global flake.nix
{
  description = "My first Flake";

  inputs = {
    neovim-flake = { url="./flakes/neovim"; };

    nixpkgs.url = "nixpkgs/nixos-25.11";
    nixpkgs-unstable.url = "nixpkgs/nixos-unstable";

    home-manager = {
      url = "github:nix-community/home-manager/release-25.11";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    ashell.url = "github:romanstingler/ashell/e26d2591a3472548b199c5f9396fe7186f1cec56";
  };

  outputs = { ... }@inputs: 
  let
    system = "x86_64-linux";
    pkgs-unstable = import inputs.nixpkgs-unstable{
      inherit system;
      overlays = [ inputs.neovim-flake.overlays.default ];
    };
  in {
    nixosConfigurations.nixos-btw = inputs.nixpkgs.lib.nixosSystem {
      specialArgs = {
        inherit inputs;
      };
      modules = [
        ./configuration.nix
        {
          nixpkgs.overlays = [ inputs.neovim-flake.overlays.default ];
        }
        inputs.home-manager.nixosModules.home-manager
        {
          home-manager = {
            extraSpecialArgs = {
              inherit inputs;
	      inherit pkgs-unstable;
            };
            useGlobalPkgs = true;
            useUserPackages = true;
            users.henmir = import ./home.nix;
            backupFileExtension = "backup";
          };
        }
      ];
    };
  };
}
neovim/neovim.nix
{ lib, wrapNeovimUnstable, neovim-unwrapped, neovimUtils, writeShellScript
, lua5_1, luarocks, pkg-config, cargo, buildFHSEnv
, tree-sitter,}:

let
  nvim = let
    config = let
      extraPackages = [
        lua5_1
        luarocks
        tree-sitter

      ];
    in neovimUtils.makeNeovimConfig {
      withPython3 = false;
      withRuby = false;
      withNodeJs = false;

      extraLuaPackages = p: with p; [ ];

      inherit extraPackages;

      customRC = ''
        set runtimepath-=~/.config/nvim
        source ~/.config/nvim/init.lua
      '';
    } // {
      wrapperArgs =
        [ "--prefix" "PATH" ":" "${lib.makeBinPath extraPackages}" ];
    };
  in wrapNeovimUnstable neovim-unwrapped config;
in buildFHSEnv {
  name = "nvim";
  targetPkgs = pkgs: [ nvim ];


  runScript = writeShellScript "nvim-fhs.sh" ''
    echo "tototo"
    exec ${nvim}/bin/nvim "$@"
  '';
} 
neovim/flake.nix
{
  description = "IogaMaster's Neovim Configuration";

  inputs.nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";

  outputs = { nixpkgs, ... }:
    let
      inherit (nixpkgs) lib;
      forAllSystems = function:
        nixpkgs.lib.genAttrs [ "x86_64-linux" "aarch64-linux" ]
        (system: function nixpkgs.legacyPackages.${system});
    in rec {
      devShells = forAllSystems (pkgs: {
        default =
          pkgs.mkShell { nativeBuildInputs = with pkgs; [ alejandra stylua ]; };
      });

      packages = forAllSystems (pkgs: rec {
        neovim = pkgs.callPackage ./neovim.nix { }; 
      });

      overlays.default = final: prev: {
        neovim = final.callPackage ./neovim.nix { };
      };
    };
}

And I’ve modified my home.nix with this:

{ config, pkgs, inputs, pkgs-unstable, ... }:

# Stuff

  home.packages = 
    (with pkgs; [
      # All my installed packages
    ])
    ++
    (with pkgs-unstable; [
      neovim
    ]);

The reason is that overlays are used to extend an existing nixpkgs package set. In the overlay definition, neovim = final.callPackage ./neovim.nix { }; uses final, and what final resolves to depends on which nixpkgs the overlay is applied to. In your top-level flake.nix, the overlay is applied to the nixos-25.11 nixpkgs, so final is the nixos-25.11 package set, meaning neovim-unwrapped comes from the stable channel.

To use the unstable channel’s neovim-unwrapped, you have two options:

  1. Apply the overlay to an unstable nixpkgs instance instead. For example, add a separate flake input nixpkgs-unstable in your global flake.nix, and instantiate a separate pkgs instance using import inputs.nixpkgs-unstable { overlays = [ ... ]; } with the overlay applied there.

  2. Don’t use final.callPackage in the overlay. Instead, explicitly import and use an unstable nixpkgs instance to callPackage your neovim.nix. For instance, you could add nixpkgs-unstable as an input to your neovim flake and use nixpkgs-unstable.legacyPackages.${system}.callPackage ./neovim.nix { } in the overlay or packages.

1 Like

Thanks a lot ! It seem so obvious now that the solution is right in front of me :sweat_smile:

It surfaced some other unrelated issues, but I can handle them I think.

Just a question though: While both approach work, which one would be considered best practice ?

I prefer the first approach. Because typically overlays are used to extend a package set in a coherent way, which means that the added package should rely on the dependencies from the package set that is extended by the overlay, rather than external ones. In other words, overlays should not specify the exact version of each dependency (via a pinned flake inputs or whatever), but use the dependency from the provided package set (final & prev). It’s up to the consumer of an overlay to decide which set of dependencies to use. So when authoring overlays, don’t use pinned package set’s callPackage. If pinning dependencies is desired, then using flake’s packages.<system>.foo or legacyPackages.<system>.foo is much more recommended, and you reference to the package directly without extending a package set with an overlay.

1 Like

Okay I understand better now, first approach seems to be cleaner indeed.