Using Both Stable and Unstable Packages on NixOS

Associated Youtube Video: https://youtu.be/hlytf6Uxf4E

Associated Odysee Video:

Previous Tutorial: Program a Modular Control Center for Your Config Using Special Args in NixOS Flakes

Next Tutorial: Managing Your NixOS Config with Git

Have you ever wanted to dual wield different releases/branches of your packages? Well now you can, thanks to the power of Nix!

Disclaimer

This is a bit of a hacky solution which will essentially allow you to install different packages from different versions of nixpkgs simultaneously. This should mainly be used as a last resort to get things to work if they don’t work normally. Although Nix is basically magic, and this should™ work, there’s always the possibility of weird and undefined behavior resulting from tossing different versions around all at once.

Tools Needed

Setting Up Two (or more) Different Nixpkgs in Your Flake

In order to setup more than two versions of nixpkgs (i.e. stable and unstable), we’re going to utilize the specialArgs (and/or extraSpecialArgs) feature of the lib.nixosSystem (and/or the home-manager.lib.homeManagerConfiguration) function(s). My last blog post had a discussion about how to utilize those but the tldr is that you can add arbritrary arguments that get passed to every Nix module file, i.e the following example, which passes the username and name variables as extra arguments to the Nix modules:

{
  description = "My first flake!";

  inputs = {
    nixpkgs.url = "nixpkgs/nixos-23.05";
    home-manager.url = "github:nix-community/home-manager/release-23.05";
    home-manager.inputs.nixpkgs.follows = "nixpkgs";
  };

  outputs = { self, nixpkgs, home-manager, ... }:
    let
      system = "x86_64-linux";
      lib = nixpkgs.lib;
      pkgs = nixpkgs.legacyPackages.${system};
      username = "librephoenix";
      name = "Emmet";
    in {
      nixosConfigurations = {
        YOURHOSTNAME = lib.nixosSystem {
          inherit system;
          modules = [ ./configuration.nix ];
          specialArgs = {
            inherit username;
            inherit name;
          };
        };
      };
      homeConfigurations = {
        USERNAME = home-manager.lib.homeManagerConfiguration {
          inherit pkgs;
          modules = [ ./home.nix ];
          extraSpecialArgs = {
            inherit username;
            inherit name;
          };
        };
      };
    };
}

specialArgs are used by the nixosSystem function, whereas extraSpecialArgs are used by the homeManagerConfiguration function from home-manager.

In the last post, we utilized this simply to create reusable variables across every Nix module file in the configuration, but here we’re going to take this a step further.

You can see that pkgs is defined as nixpkgs.legacyPackages.${system}, where the nixpkgs parts comes from the inputs. Using this idea, you can add as many different arbitrary versions of nixpkgs to your inputs, and add multiple definitions of pkgs, like so:

{
  description = "My first flake!";

  inputs = {
    nixpkgs.url = "nixpkgs/nixos-23.05";
    nixpkgs-unstable.url = "nixpkgs/nixos-unstable";
    home-manager.url = "github:nix-community/home-manager/release-23.05";
    home-manager.inputs.nixpkgs.follows = "nixpkgs";
  };

  outputs = { self, nixpkgs, nixpkgs-unstable, home-manager, ... }:
    let
      system = "x86_64-linux";
      lib = nixpkgs.lib;
      pkgs = nixpkgs.legacyPackages.${system};
      pkgs-unstable = nixpkgs-unstable.legacyPackages.${system};
      username = "librephoenix";
      name = "Emmet";
    in {
      nixosConfigurations = {
        YOURHOSTNAME = lib.nixosSystem {
          inherit system;
          modules = [ ./configuration.nix ];
          specialArgs = {
            inherit username;
            inherit name;
            inherit pkgs-unstable;
          };
        };
      };
      homeConfigurations = {
        USERNAME = home-manager.lib.homeManagerConfiguration {
          inherit pkgs;
          modules = [ ./home.nix ];
          extraSpecialArgs = {
            inherit username;
            inherit name;
            inherit pkgs-unstable;
          };
        };
      };
    };
}

In the above example, you can see that we have definitions for both pkgs and pkgs-unstable. If these are added to specialArgs and/or extraSpecialArgs, you can now utilize them in your configuration.nix, home.nix, or any other Nix module. Here’s a trivial example showing how you would install both stable and unstable packages installed in the same module:

{ config, lib, pkgs, pkgs-unstable, ... }:
{
  environment.systemPackages =
    (with pkgs; [
      # list of stable packages go here
    ])

    ++

    (with pkgs-unstable; [
      # list of unstable packages go here
    ]);
}

Some Caveats

You Can’t Install Two Versions of the Same Package

The following example obviously doesn’t work, unless the package definition is identical between the two versions of nixpkgs in question:

{ config, lib, pkgs, pkgs-unstable, ... }:
{
  ## TWO VERSIONS OF SAME PACKAGE (BINARY) DOESN'T WORK!!
  environment.systemPackages =
    (with pkgs; [
      vim
      emacs
    ])

    ++

    (with pkgs-unstable; [
      vim
      emacs
    ]);
}

Keep in Mind What is Actually Happening!

Going back to the example from above again:

{
  description = "My first flake!";

  inputs = {
    nixpkgs.url = "nixpkgs/nixos-23.05"; # <-- STABLE
    nixpkgs-unstable.url = "nixpkgs/nixos-unstable"; # <-- UNSTABLE
    home-manager.url = "github:nix-community/home-manager/release-23.05"; # <-- STABLE
    home-manager.inputs.nixpkgs.follows = "nixpkgs"; # <-- STABLE
  };

  outputs = { self, nixpkgs, nixpkgs-unstable, home-manager, ... }:
    let
      system = "x86_64-linux";
      lib = nixpkgs.lib;
      pkgs = nixpkgs.legacyPackages.${system};
      pkgs-unstable = nixpkgs-unstable.legacyPackages.${system};
      username = "librephoenix";
      name = "Emmet";
    in {
      nixosConfigurations = {
        YOURHOSTNAME = lib.nixosSystem { # NIXOS SYSTEM BUILDER FUNCTION FROM STABLE
          # though pkgs isn't directly inherited here, like with home manager
          # pkgs <-- STABLE
          inherit system;
          modules = [ ./configuration.nix ];
          specialArgs = {
            inherit username;
            inherit name;
            inherit pkgs-unstable; # <-- UNSTABLE
          };
        };
      };
      homeConfigurations = {
        USERNAME = home-manager.lib.homeManagerConfiguration { # <-- HOME MANAGER BUILDER FUNCTION FROM STABLE
          inherit pkgs; # <-- STABLE
          modules = [ ./home.nix ];
          extraSpecialArgs = {
            inherit username;
            inherit name;
            inherit pkgs-unstable; # <-- UNSTABLE
          };
        };
      };
    };
}

note the comments to the side of the nixosSystem function, homeManagerConfiguration function and pkgs / pkgs-unstable inherits. It’s easy to see that, even though we’re including two differents versions of nixpkgs, we can only choose one nixpkgs version to actually build the system (or home manager configuration).

I’d like to think that since Nix is magic, it probably has the capacity to always work out in the end. However, it’s more likely the case that there could still be some weird behavior leading to breakages, especially if something is deprecated or added in the definitions for the nixosSystem function (or homeManagerConfiguration function).

The End

I hope you found this helpful! I’ve found this trick especially useful whenever some packages I really need fail to build (since I actually run the unstable branch of nixpkgs myself). This also should allow easy access to packages that are currently only in the unstable branch, if you’d like to keep the rest of your system on stable.

In any case, happy configuring! ;)

Donation Links

If you have found my work to be helpful, please consider donating via one of the following links. Thank you very much!