1. Config Structure
Generally speaking, it doesn’t matter how your configuration is structured. It’s the same as with programming: if you have a small project, the structure doesn’t matter. However, over time your configuration will certainly grow larger and larger (especially if you have multiple machines).
The configuration structure that I recommend most is called the “Dendritic pattern”. It allows you to remove most of the boilerplate and write reusable configurations that can be used across multiple machines.
I highly recommend those sources if you want to know more about this:
Basic Setup
Section titled “Basic Setup”To create a very basic dendritic-pattern project use this command:
nix flake init -t github:vic/flake-file#dendritic. I recommend creating it
inside a Git directory under /etc/nixos/. By using Git, you will be able to
easily sync your config with other devices and store it safely.
.├── flake.nix└── modules ├── audio.nix ├── fish.nix ├── hyprland.nix ├── otherExampleMoudule.nix └── hosts └── $host ├── $host.nix └── hwConfig.nixThis shows a basic structure of a dendritic design config. The core of it was
already created by the previous command. All the Nix files inside the
./modules/ directory, will be imported automatically by the
import tree flake, unless you prefix a
file or directory name with an underscore (_name).
Host Modules
Section titled “Host Modules”Under the modules directory create a hosts subdirectory.
Config that allow specific machines to behave in particular ways ways will go
there. This allows for configuring multiple machines using the same project as a
config.
Create another directory named after your host(laptop, desktop, homeLab,
etc.). I will reference it as $host through this tutorial.
Inside the $host.nix file:
### ./modules/host/$host/$host.nix
{ inputs, self, lib, ...}: { flake = { # Declare $host machine nixosConfigurations.$host = inputs.nixpkgs.lib.nixosSystem { modules = with self.nixosModules; [ $host #hyprland otherExampleMoudule fish ]; };
# module for additional configuration nixosModules.$host = {pkgs, ...}: { environment.systemPackages = with pkgs; [ blender ]; }; };}nixosConfigurations.$host = inputs.nixpkgs.lib.nixosSystem will allow us to
run: sudo nixos-rebuild switch --upgrade --flake .#$host" to use certain host
file as a config on a $host machine. Inside the
inputs.nixpkgs.lib.nixosSystem configuration you can import any of your
modules. Example of this is the flake.nixosModules.$host module created in the
previous snippet.
Hardware Config
Section titled “Hardware Config”Next, you need to move your hardware-config.nix from
/etc/nixos/hardware-config.nix to ./modules/host/$host/hw-config.nix. This
configuration file is required for NixOS to work with your hardware.
The only change you need to make is wrapping it in a flake parts module:
flake.nixosModules.$host. It should have the same name as the module inside
the $host.nix. This causes the flake-file to automatically merge their
contents, so we don’t have to import these two modules separately.
This is how my hw-config looks:
{ flake.nixosModules.desktop = { config, lib, modulesPath, ... }: { imports = [ (modulesPath + "/installer/scan/not-detected.nix") ];
boot.initrd.availableKernelModules = ["nvme" "xhci_pci" "ahci" "usbhid" "usb_storage" "sd_mod"]; boot.initrd.kernelModules = []; boot.kernelModules = ["kvm-amd"]; boot.extraModulePackages = [];
fileSystems."/" = { device = "/dev/disk/by-uuid/bac8161a-fcc7-465d-9944-549efd76e1a0"; fsType = "ext4"; };
boot.initrd.luks.devices."luks-9c6c352a-9681-4e52-885f-3a33e9ce92fe".device = "/dev/disk/by-uuid/9c6c352a-9681-4e52-885f-3a33e9ce92fe";
fileSystems."/boot" = { device = "/dev/disk/by-uuid/2BB5-71EB"; fsType = "vfat"; options = ["fmask=0077" "dmask=0077"]; }; environment.etc."crypttab".text = '' data_crypt UUID=e30f59fc-0341-4b86-8cfe-8084ff2a8c1d /root/keys/data.key luks '';
fileSystems."/data" = { device = "/dev/mapper/data_crypt"; fsType = "ext4"; };
# swapDevices = # [ { device = "/dev/disk/by-uuid/f923d65d-a077-43e4-9d5e-b9bb33be36f7"; } # ];
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking # (the default) this is the recommended approach. When using systemd-networkd it's # still possible to use this option, but it's recommended to use it in conjunction # with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`. networking.useDHCP = lib.mkDefault true; # networking.interfaces.enp14s0.useDHCP = lib.mkDefault true; # networking.interfaces.wlp13s0.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; };}Example Modules
Section titled “Example Modules”Here are some example NixOS modules, taken directly from my personal config:
1. Enable NVIDIA GPU Support
Section titled “1. Enable NVIDIA GPU Support”### ./modules/nvidia.nix{ flake.nixosModules.nvidia = { # Enable OpenGL hardware.graphics = { enable = true; };
#For nixos-unstable, they renamed it services.xserver.enable = true; services.xserver.videoDrivers = ["nvidia"];
hardware.nvidia = { modesetting.enable = true; powerManagement.enable = true; # Fine-grained power management. Turns off GPU when not in use. # Experimental and only works on modern Nvidia GPUs (Turing or newer). powerManagement.finegrained = false;
# Enable the Nvidia settings menu, # accessible via `nvidia-settings`. nvidiaSettings = true;
open = true; }; };}2. Docker with NVIDIA GPU Passthrough Support
Section titled “2. Docker with NVIDIA GPU Passthrough Support”### ./modules/docker.nix{ flake.nixosModules.docker = {pkgs, ...}: { hardware.nvidia-container-toolkit.enable = true; environment.systemPackages = with pkgs; [ nvidia-container-toolkit ]; virtualisation.docker = { enable = true; enableNvidia = true; daemon.settings.features.cdi = true; enableOnBoot = false; # enable by hand later with the command from hyprland }; };}As explained before, you can import those modules by just writing their names(eg. docker, nvidia) inside your host module:
nixosConfigurations.$host = inputs.nixpkgs.lib.nixosSystem { modules = with self.nixosModules; [ nvidia docker # anythign other ];}Using Flake-File
Section titled “Using Flake-File”Sometimes we need to use an external flake. An example use case is installing programs that don’t have a standard package, or whose packages lack features that the flake provides. One program for which I use an external flake is Zen Browser.
{inputs, ...}: { flake-file.inputs.zen-browser = { url = "github:youwen5/zen-browser-flake"; inputs.nixpkgs.follows = "nixpkgs"; }; flake.nixosModules.zen = {pkgs, ...}: { environment.systemPackages = [ inputs.zen-browser.packages.${pkgs.stdenv.hostPlatform.system}.default ]; };}Flake-files allow you to place flake-file.inputs.$AnyNameForThisFlake in any
module. Then you can use this flake’s properties in any of your modules by
referencing inputs.$AnyNameForThisFlake.
This also allows you to override any part of the flake in the standard way
(e.g., set inputs.nixpkgs.follows).
You also need to remember to run: sudo nix run .#write-flake before rebuilding
your configuration. This adds any newly added flakes to your source flake
(./flake.nix). I personally run this command every time I rebuild my config so
I don’t forget.
Home Manager Modules
Section titled “Home Manager Modules”Home Manager allows you to reliably manage your dotfiles that configure all of your programs. See Vimjoyer’s video about Home Manager.
We can define Home Manager modules the same way we define NixOS modules using
flake-parts: flake.homeModules.name. I personally like to split Home Manager
modules into two types:
- General – needed on all of my machines:
flake.homeModules.general - Machine-specific – needed on a specific machine:
flake.homeModules.$host
These modules automatically merge the contents of all files that have the
corresponding module type name. For example, I have only three Home Manager
modules: desktop, laptop, and general.
{ flake.homeModules.general = {lib, ...}: { programs.ghostty.enable = true; programs.ghostty.settings = lib.mkForce { "background-opacity" = 0.9; "background-blur" = true; "clipboard-paste-protection" = false; "mouse-hide-while-typing" = true; "shell-integration" = "fish"; "font-family" = "MesloLGM Nerd Font"; "command" = "fish --login --interactive"; "copy-on-select" = "clipboard"; }; };}Making Home Manager Work
Section titled “Making Home Manager Work”Now you need to change your $host module
### ./modules/host/$host/$host.nix
{ inputs, self, lib, ...}: { flake-file.inputs.home-manager = { url = lib.mkDefault "github:nix-community/home-manager"; inputs.nixpkgs.follows = "nixpkgs"; };
imports = [ inputs.home-manager.flakeModules.home-manager ]; flake = { nixosConfigurations.$host = inputs.nixpkgs.lib.nixosSystem { ## nothing changed };
nixosModules.$host = {pkgs, ...}: {
imports = [ inputs.home-manager.nixosModules.default ];
home-manager.users.f = { imports = with self.homeModules; [ $host general ]; home = { stateVersion = "25.11"; username = "$username"; homeDirectory = "/home/$username"; }; };
home-manager.backupFileExtension = "home-managebak"; }; };}This will import the Home Manager flake and set it up in a way that imports all the Home Manager modules.
Actually Using This Config
Section titled “Actually Using This Config”I really like having one command that does many things at once. Thats why I’ve set up commands for updating my OS. This is very convenient - I just run one command once a week and I can be sure that everything on my PC is up to date, without having to worry about auto-updates (looking at you, Macroslop).
-
rebuild– quickly rebuilds my configuration. Useful when testing new settings or installing a new application. -
updateNix- Sources the newest NixOS configuration for my machine, with the packages from the unstable branch.
- Pulls any new git commits from the GitHub.
- Removes old packages for backup system versions using
nix-collect-garbage. - Runs any commands inside
/etc/nixos/onUpdate.sh(useful for auto running tasks like updating Docker containers or performing other non-Nix tasks).
To make my update commands work on all of my machines, I put the hostname inside
/etc/nixos/host.txt so that my scripts can read it.
I use the
unstable branch of NixOS. The
main difference between the unstable and stable branches is that the unstable
branch follows a rolling release model. This means you don’t specify which
version of NixOS you are using — you always use the newest one. I prefer this
because I don’t want to worry about any NixOS versionning bullshit. When I run
the updateNix command, I want to always get the newest version of all programs
on my computer.
# for the fish shellonUpdate = "sudo /etc/nixos/onUpdate.sh";readHost = "set -g host (cat /etc/nixos/host.txt)";rebuild = "readHost ; cd /etc/nixos/NNC/ ; sudo nix run .#write-flake ; sudo nixos-rebuild switch --upgrade --flake .#$host";updateNix = "cd /etc/nixos/NNC/; git pull ; rebuild ; sudo nix flake update ; flatpak update -y ; onUpdate ; cleanup";cleanup = "sudo nix-collect-garbage --delete-older-than 14d";If you find anything to improve in this project’s code, please create an issue describing it on the GitHub repository for this project. For website-related issues, create an issue here.
Support
Section titled “Support”All pages on this site are written by a human, and you can access everything for free without ads. If you find this work valuable, please give a star to the GitHub repository for this project.