Tips and Tricks for NixOS Desktop in 2025
Jun 18, 2025 - ⧖ 3 minI've been using NixOS as my one and only desktop OS since September 2024. Since then, I've encountered plenty of challenges and obstacles. This article snapshots my experience working through (or around) many of the issues I've encountered while using NixOS as my desktop Linux distro.
NixOS Configuration Patterns
Flakes; Home-Manager; NixOS-Unstable
When I started using NixOS, there was no strong consensus about whether you should use flakes, if/how you should use home-manager, or which channel to use. After exploring a great swathe of the possible permutations, I've settled on the following:
Use Flakes
Install Home-Manager as a NixOS Module
nixos-unstable
Channel
Use the - Use a flake.
- Use Home-Manager as a NixOS module (not a standalone install).
- Use the
nixos-unstable
channel.
Store your SSH pubkey(s) in your flake
Or piggyback on GitHub
I needed to configure my ~/.ssh/authorized_keys
. For a long time, I used this snippet to dynamically update my keys at build-time from GitHub's .keys
endpoint, which exposes the public SSH keys attached to your GitHub account. (I found this extremely helpful any time I needed to configure a new machine via local terminal. curl https://github.com/Jafner.keys > ~/.ssh/authorized_keys
is very quick.)
users.users."${username}" = {
isNormalUser = true;
extraGroups = [ "networkmanager" "wheel" ];
description = "${username}";
openssh.authorizedKeys.keys = pkgs.lib.splitString "\n" (builtins.readFile (pkgs.fetchurl {
url = "https://github.com/Jafner.keys";
sha256 = "1i3Vs6mPPl965g3sRmbXGzx6zQBs5geBCgNx2zfpjF4=";
}));
};
Nowadays, I just store keys.txt
at the root of my repo and replace the chunky part of that config with:
openssh.authorizedKeys.keys = ./keys.txt;
username
to specialArgs
Pass In the let ... in ...
variable assignment block prepending my nixosConfiguration, I assign username = joey
. It looks like this:
outputs = { ... }: {
nixosConfigurations.desktop =
let
inherit inputs;
username = "joey";
...
in
inputs.nixpkgs.lib.nixosSystem { ... };
}
And then to make that variable accessible from any module, I add username
to specialArgs
like this:
specialArgs = {
inherit
inputs
username
hostname
system
;
};
And lastly, I use it like this:
docker.nix
{ username, ... }: {
virtualisation.docker.enable = true;
users.users.${username}.extraGroups = [ "docker" ];
}
Or like this:
audio.nix
snippet
{ pkgs, username, ... }: {
home-manager.users."${username}" = {
home.packages = with pkgs; [ goxlr-utility ];
};
}
Tools for Common Problems
nh
Nix Helper - Ergonomic CLI for managing your system
Plop a cute little snippet like this somewhere in your config:
programs.nh = {
enable = true;
flake = "/home/${username}/flake";
};
The config has to point at an existing flake.
I use: /home/${username}/Git/dotfiles Could do: /home/${username}/dotfiles Could do: /etc/flake
,
Comma - Run commands from packages you don't have installed
Sops Nix - Safely and securely include secrets in your NixOS code
NixGL - Make OpenGL applications work as expected
Common Nix/OS Usage Pitfalls
Nix can only see files that are checked into Git
dynamic attribute 'user' is already defined at ...
Error: This issue is triggered when you have two or more configuration nodes share a parent path that has a variable in it.
To illustrate, This one works:
home-manager.users.${username} = {
home.packages = [ pkgs.nixd ];
programs.zed-editor.enable = true;
};
This one breaks:
home-manager.users.${username}.home.packages = [ pkgs.nixd ];
home-manager.users.${username}.programs.zed-editor.enable = true;
Further, this one works:
home-manager.users.joey.home.packages = [ pkgs.nixd ];
home-manager.users.joey.programs.zed-editor.enable = true;
The workaround I prefer is to ensure that any home-manager configurations are structured like this:
home-manager.users.${username} = {
... my configuration ...
};
This issue does not occur when merging two files, only when evaluating a single file.