Skip to content

Instantly share code, notes, and snippets.

@avisek
Created August 8, 2025 15:24
Show Gist options
  • Select an option

  • Save avisek/e439660563fa2604275b00433d62e4f5 to your computer and use it in GitHub Desktop.

Select an option

Save avisek/e439660563fa2604275b00433d62e4f5 to your computer and use it in GitHub Desktop.

Nix Flakes Tips and Tricks

This is by no means complete, please add to it and share your own!

1. Shallow Clone nixpkgs

Shallow clone nixpkgs when the full Git history isn't always necessary - this can speed up build times.

The only issue I've had is nix-index-database not working well with the shallow clone. Other than that, no issues after running for a few months.

inputs = {
    nixpkgs.url = "git+https://github.com/NixOS/nixpkgs?shallow=1&ref=nixos-unstable";
};

Sometimes when you might need a full clone are debugging and working with repository history, but those are rare cases.

2. Importing Non-Flake Repositories

You can import your non-flake repositories (like wallpapers) as inputs:

inputs = {
    wallpapers = {
      url = "git+ssh://[email protected]/TSawyer87/wallpapers.git";
      flake = false;
    };
}

After adding the input, you can access individual wallpapers by adding the inputs argument and something like:

path = "${inputs.wallpapers}/Aesthetic Scenery.jpg";

3. Understanding @-patterns

@-patterns allow you to reference your outputs argument set as a whole. An @-pattern is a way for a function to access variadic attributes (i.e., varying number of arguments).

inputs = {
    home-manager.url = "github:nix-community/home-manager/master";
    home-manager.inputs.nixpkgs.follows = "nixpkgs";
    stylix.url = "github:danth/stylix";
};

outputs = {
    self,
    nixpkgs,
    home-manager,
} @ inputs:

With the above example, to add the modules to your nixosConfigurations you would add something like this:

nixosConfigurations.${host} = nixpkgs.lib.nixosSystem {
  inherit system;
  specialArgs = {
    inherit inputs username host email systemSettings;
  };
  modules = [
    ./hosts/${host}/config.nix
    inputs.stylix.nixosModules.stylix
    home-manager.nixosModules.home-manager
    # .. snip ..
  ];

Important note: Since home-manager was explicitly listed in the outputs arguments (outputs = { self, nixpkgs, home-manager, }), the inputs prefix is unnecessary. If home-manager was removed from the outputs arguments (outputs = { self, ... }), then you would need modules = [ inputs.home-manager.nixosModules.home-manager ];

This can be confusing because many docs assume you're not using an @-pattern, so if you have one in your flake you need to prefix with inputs.

4. Understanding specialArgs and extraSpecialArgs

Building on @-patterns, using specialArgs (NixOS) and extraSpecialArgs (home-manager) is a way to pass arguments from your flake to your NixOS and home-manager modules.

For example, here's a snippet of some variables you might set:

outputs = {
  self,
  nixpkgs,
  home-manager,
  ...
} @ inputs: let
  system = "x86_64-linux";
  host = "magic";
  username = "jr";
  userVars = {
    timezone = "America/New_York";
    locale = "en_US.UTF-8";
    gitUsername = "TSawyer87";
    dotfilesDir = "~/.dotfiles";
    wm = "hyprland";
    browser = "firefox";
    term = "ghostty";
    editor = "hx";
    keyboardLayout = "us";
  };
in

Now you can pass them as special args like this:

nixosConfigurations = {
  ${host} = nixpkgs.lib.nixosSystem {
    inherit system;
    specialArgs = {
      inherit
        inputs
        username
        system
        host
        userVars
        ;
    };
    modules = [
      ./hosts/${host}/configuration.nix
      home-manager.nixosModules.home-manager
      inputs.stylix.nixosModules.stylix
      {
        home-manager.useGlobalPkgs = true;
        home-manager.useUserPackages = true;
        home-manager.users.${username} = import ./hosts/${host}/home.nix;
        home-manager.backupFileExtension = "backup";
        home-manager.extraSpecialArgs = {
          inherit
            inputs
            username
            system
            host
            userVars
            ;
        };
      }
    ];
  };
};

To access values in userVars, for example:

{ userVars, ... }: {
  programs = {
    git = {
      enable = true;
      userName = userVars.gitUsername;
    };
  };
}

5. Set Up Checks and Formatter Outputs with treefmt-nix

Add treefmt-nix to your inputs and outputs arguments. Inside the let expression from tip 4, add:

let
  # ... snip ...
  pkgs = import nixpkgs {
    inherit system;
    config.allowUnfree = true;
  };
  treefmtEval = treefmt-nix.lib.evalModule pkgs ./treefmt.nix;
in
{
  checks.x86_64-linux.style = treefmtEval.config.build.check self;
  formatter.x86_64-linux = treefmtEval.config.build.wrapper;
  # ... snip ...
}

And in the treefmt.nix:

{
  projectRootFile = "flake.nix";
  programs = {
    deadnix.enable = true;
    statix.enable = true;
    keep-sorted.enable = true;
    nixfmt = {
      enable = true;
      strict = true;
    };
  };
  settings.excludes = [
    "*.age"
    "*.jpg"
    "*.nu"
    "*.png"
    ".jj/*"
    "flake.lock"
    "justfile"
  ];
  settings.formatter = {
    deadnix = {
      priority = 1;
    };
    statix = {
      priority = 2;
    };
    nixfmt = {
      priority = 3;
    };
  };
}

Use treefmt-nix to manage code formatters and linters as flake outputs. This ensures consistent styling and catches issues with tools like deadnix, statix, and nixfmt.

Usage:

  • Use nix fmt in the flake directory to format your whole configuration
  • Run nix flake check to run your checks
  • Run nix flake show to list your outputs
  • Tools like nix-fast-build rely on flake checks and can be used after setting this up

6. Create a Development Shell

Make a devShell output:

in
{
  checks.x86_64-linux.style = treefmtEval.config.build.check self;
  formatter.x86_64-linux = treefmtEval.config.build.wrapper;
  devShells.${system}.default = import ./lib/dev-shell.nix { inherit inputs; };
}

And in the dev-shell.nix you could put something like this:

{
  inputs,
  system ? "x86_64-linux",
}:
let
  # Instantiate nixpkgs with the given system and allow unfree packages
  pkgs = import inputs.nixpkgs {
    inherit system;
    config.allowUnfree = true;
    overlays = [
      # Add overlays if needed, e.g., inputs.neovim-nightly-overlay.overlays.default
    ];
  };
in
pkgs.mkShell {
  name = "nixos-dev";
  packages = with pkgs; [
    # Nix tools
    nixfmt-rfc-style # Formatter
    deadnix # Dead code detection
    nixd # Nix language server
    nil # Alternative Nix language server
    nh # Nix helper
    nix-diff # Compare Nix derivations
    nix-tree # Visualize Nix dependencies

    # Code editing
    helix

    # General utilities
    git
    ripgrep
    jq
    tree
  ];

  shellHook = ''
    echo "Welcome to the NixOS development shell!"
    echo "System: ${system}"
    echo "Tools available: nixfmt, deadnix, nixd, nil, nh, nix-diff, nix-tree, helix, git, ripgrep, jq, tree"
  '';
}

You can enter this development shell with nix develop or automatically with direnv.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment