Hello everyone. My system is based on flakes (nix-channel is disabled). How can I use some packages from nixos-unstable while keeping the rest of my system on the stable branch? I’d like to achieve something like this:
environment.systemPackages = with pkgs; [
firefox
thunderbird
unstable.osu-lazer-bin
];
In other words, I wanna be able to access osu-lazer-bin from the unstable branch via pkgs.unstable.osu-lazer-bin.
I’ve already seen multiple approaches to achieve this, but none of them worked for me, maybe it’s because my system uses flakes, idk.
You should be able to add the unstable nixpkgs as part of your inputs, then import it and add that package directly. Something like this:
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/release-24.05";
nixpkgs-unstable.url = "github:nixos/nixpkgs/nixpkgs-unstable";
};
outputs = {nixpkgs, nixpkgs-unstable, ... }:
let
system = "x86_64-linux";
pkgs = import nixpkgs { inherit system; };
pkgsUnstable = import nixpkgs-unstable { inherit system; };
in
{
# now along with `pkgs` you also have `pkgsUnstable`
# and can do: pkgsUnstable.osu-lazer-bin
}
Never set pkgs directly in the NixOS module system, as the other answers are indicating.
Here’s a couple of concepts you need to know:
If you want to access some inputs, or some other value across your config without creating an explicit option for it, use specialArgs or _module.args. (For things that you import, you must pass them in specialArgs specifically to avoid infrec.)
Changing pkgs requires an overlay; however, for this application, you may not necessarily need one if you are willing to get away from the pkgs.unstable syntax specifically.
Which means the following:
Add an entry to your inputs, and pass them to your config via specialArgs, in your flake.nix:
or if you want, you can just blanket allow all unfree packages, that’s up to you:
{
nixpkgs.config.allowUnfree = true;
}
Then, use that config when importing the unstable nixpkgs, and then use the appropriate module arg in your package list.
a. EITHER create a new module arg:
{ config, inputs, pkgs, pkgsUnstable, ... }:
{
# this allows you to access `pkgsUnstable` anywhere in your config
_module.args.pkgsUnstable = import inputs.nixpkgs-unstable {
inherit (pkgs.stdenv.hostPlatform) system;
inherit (config.nixpkgs) config;
};
environment.systemPackages = [
pkgsUnstable.osu-lazer-bin
];
}
b. OR, add an overlay to modify the existing nixpkgs instance (pkgs):
{ inputs, pkgs, ... }:
{
nixpkgs.overlays = [
(final: _: {
# this allows you to access `pkgs.unstable` anywhere in your config
unstable = import inputs.nixpkgs-unstable {
inherit (final.stdenv.hostPlatform) system;
inherit (final) config;
};
})
];
environment.systemPackages = [
pkgs.unstable.osu-lazer-bin
];
}
I’m personally against 3b, because an overlay for this is completely unnecessary and will likely slow down eval.
PS this has got to be one of the most-asked questions, it’s silly that we are still having to rewrite the answers from scratch in 2024…
Hello, I am a bit new to flakes and the nix syntax. The tutorial i followed led me to a different outputs syntax so i am not sure how i can follow your step 1. Can you help? I am figuring this stuff out as i go so there are some holes in my understanding.
{
description = "My system configuration";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
unstable.url = "github:NixOS/nixpkgs/nixos-unstable";
};
outputs = { self, nixpkgs, ... }:
let
lib = nixpkgs.lib;
in {
nixosConfigurations = {
tartarus = lib.nixosSystem {
system = "x86_64-linux";
modules = [ ./configuration.nix ];
# revision = self.shortRev or self.dirtyShortRev or self.lastModified or "unknown";
};
};
};
}
This is a nix function. On the first line you can see the attribute set that would be used as an argument, and the big attribute set after : is the return of the function.
So when you rebuild your system with a flake that you provided, here
tartarus = lib.nixosSystem {
system = "x86_64-linux";
modules = [ ./configuration.nix ];
# revision = self.shortRev or self.dirtyShortRev or self.lastModified or "unknown";
};
you call a function lib.nixosSystem that creates a configuration tartarus for you. Somewhere inside that lib.nixosSystem function the following happens:
config is extracted from nixpkgs
pkgs is extracted from nixpkgs. It looks something like pkgs = nixpkgs.legacyPackages.${system}; (system is provided by you in argument to nixosSystem)
./configuration.nix is read and called with these (and other) arguments
What you need to do is to extract the unstable version of pkgs and provide it in inputs to function in ./configuration.nix.
To do this nixosSystem allows you adding attributes that would be used in calls to functions from modules. You can add attribute specialArgs = { /* your args */ }; and your args would be used in a call to function in ./configuration.nix. And you would also need to modify ./configuration.nix by adding unstable version of pkgs as input.
So you’ll get something like this:
# In your flake declaration
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
unstable.url = "github:NixOS/nixpkgs/nixos-unstable";
};
outputs = { self, nixpkgs, ... }:
let
system = "x86_64-linux"
lib = nixpkgs.lib;
pkgs-unstable = unstable.legacyPackages.${system};
in {
nixosConfigurations = {
tartarus = lib.nixosSystem {
inherit system; # inherited it from 'let' block
modules = [ ./configuration.nix ];
specialArgs = { inherit pkgs-unstable; };
};
};
};
# In your configuration.nix:
{ config, pkgs, pkgs-unstable, ... }:
{
# ...
environment.systemPackages = with pkgs; [
neovim # package from pkgs
pkgs-unstable.ripgrep # package from pkgs-unstable
];
# ...
}
Wow, thank you so much for the detailed explanation. This is literally a game changer. One can write a very dynamic configuration logic with this. It is awesome.