Help a NixOS newbie understand Flakes?

I’m really interested in NixOS and so I’ve been playing with it a lot lately. I love the idea of managing a whole system with a single config file, and the idea of being able to do that with home-manager at the $HOME level is really appealing, too. I’ve got the basic idea of configuring a system with configuration.nix, and I’ve gotten home-manager installed successfully for a single user and (with less success) for all users. Now I’m trying to dip my toe into Flakes, which everyone says is the future.

I’m trying to make a super-simple Flake that mostly reads from configuration.nix, but pulls a few key packages (in this case: Home-manager, Hyprland, and Waybar) from their more-recent repo tags, compiles them, and adds them to nixpkgs so I can use the up-to-date version. I kind of get that they need to be “inputs”, so would the following “simple” /etc/nixos/flake.nix do that, if either my configuration.nix or username.nix request those packages?

  description = "Hostname Flake";
  inputs = {
    nixpkgs.url =  "github:nixos/nixpkgs/nixos-23.05";
    home-manager.url = "github:nix-community/home-manager/release-23.05";
    home-manager.inputs.nixpkgs.follows = "nixpkgs";
    hyprland.url = "github:hyprwm/Hyprland/v0.29.1";
    hyprland.inputs.nixpkgs.follows = "nixpkgs";
    waybar.url = "github:Alexays/waybar/0.9.22";
    waybar.inputs.nixpkgs.follows = "nixpkgs";
  outputs = { nixpkgs, home-manager, hyprland, waybar, ... }@inputs: {
    nixosConfigurations = {
      "hostname" = nixpkgs.lib.nixosSystem {
        specialArgs = { inherit inputs; }; # Pass flake inputs to our config
        modules = [ ./configuration.nix ];
    homeConfigurations = {
      "username@hostname" = home-manager.lib.homeManagerConfiguration {
        pkgs = nixpkgs.legacyPackages.x86_64-linux; # home-manager requires 'pkgs' instance
        extraSpecialArgs = { inherit inputs; }; # Pass flake inputs to our config
        modules = [ ./home-manager/username.nix ];

Also: am I right in thinking that Flakes are (for newbies’ purposes) a tool that lets you follow a nixpkgs release channel if you’d like, but also specify packages to be pulled from their repos by tag, compiled, and then added to the local Nix store, for use by the user/users?

I think this is one of the best overviews of what flakes actually is: Flakes — documentation


Much as I love flakes, it’s still mostly just sugar. You can always use other mechanisms to pull in various other nix sources (like disparate nixpkgs versions), like niv or builtins.fetchtTarball.

As far as propagating those input’s outputs into nixos/home-manager, I see there being sort of three options, depending on your preference:

  1. As you mention, passing inputs as an extraSpecialArgs will let you reach directly into the flake inputs deeper inside your HM/NixOS configurations, as it will be available as another input to your config modules.
  2. You can configure pkgs explicitly and directly in your flake with overrides, such that various packages from your flake inputs mask/overlay their original nixpkgs counter-parts.
  3. You can use nixpkgs.overlays inside (at least, I know for sure) your NixOS config to modify the pkgs that nixosSystem was called with.

For better or worse, I use both #1 and #3:

  • for some things, I directly set some_program.package = inputs.blah.outputs.packages.${pkgs.stdenv.hostPlatform.system}.some_program
  • for other things that might be “used” in multiple places, I do something more like this:
        nixpkgs.overlays = [
          (final: prev:
            let nwPkgs = inputs.nixpkgs-wayland.packages.${pkgs.stdenv.hostPlatform.system}; in
              inherit (nwPkgs)
              # wayland-protocols =;

These have various trade-offs. For example, the way that I use nixpkgs.overlays down inside my NixOS configuration means that, depending on exactly how I configure things, I have the potential to have differences between systems. Imagine that one of my hosts includes that particular config, but another doesn’t. This could be an argument for approach #2, where pkgs is configured explicitly in the flake and then all derivations built with that pkgs are using the exact same set of packages.