The design goal
The best minimal NixOS config is not the one with the fewest lines. It is the one that keeps your system calm. You should be able to look at it six months later and still understand why each line exists.
That means a few practical decisions:
Use flakes for structure
Pin your inputs, keep one obvious entry point, and make rebuilding feel mechanical instead of mysterious.
Use Home Manager for user polish
Keep shell, Git, prompt, and editor setup out of the system layer unless they truly belong there.
Prefer defaults with intent
Install fewer packages, but choose the ones you touch constantly: terminal, shell, Git, fonts, and editors.
Leave room to grow
A small module layout today becomes the clean base for multiple machines later.
Project structure
Keep the repository small. One flake file, one system module, one Home Manager module. That already buys you a lot.
directory layout
.
├── flake.nix
├── hosts/
│ └── laptop.nix
└── home/
└── piotr.nix
This is enough to separate machine-level settings from your personal workflow. Once you need shared modules, you can add a modules/ directory later. Do not start with one unless you already feel the need.
The flake
The flake is the entry point. It pins Nixpkgs, pulls in Home Manager, and defines the machine you want to build.
flake.nix
{
description = "Minimal NixOS config that still feels premium";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
home-manager = {
url = "github:nix-community/home-manager/release-25.05";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, home-manager, ... }:
let
system = "x86_64-linux";
in {
nixosConfigurations.laptop = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
./hosts/laptop.nix
home-manager.nixosModules.home-manager
{
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.users.piotr = import ./home/piotr.nix;
}
];
};
};
}
Why this is enough
- One pinned Nixpkgs input keeps the system predictable.
- Home Manager follows the same Nixpkgs so your user packages do not drift onto a different package set.
- One named host keeps rebuild commands simple and memorable.
The system module
This file should do the heavy lifting for the machine itself: bootloader, networking, desktop, fonts, and a very small core package set.
hosts/laptop.nix
{ config, pkgs, ... }:
{
imports = [
./hardware-configuration.nix
];
networking.hostName = "laptop";
time.timeZone = "America/Chicago";
i18n.defaultLocale = "en_US.UTF-8";
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
nix.settings.experimental-features = [ "nix-command" "flakes" ];
nix.settings.auto-optimise-store = true;
networking.networkmanager.enable = true;
services.xserver.enable = true;
services.xserver.displayManager.gdm.enable = true;
services.xserver.desktopManager.gnome.enable = true;
services.printing.enable = true;
services.pipewire = {
enable = true;
pulse.enable = true;
alsa.enable = true;
alsa.support32Bit = true;
};
hardware.bluetooth.enable = true;
services.blueman.enable = true;
users.users.piotr = {
isNormalUser = true;
description = "Piotr";
extraGroups = [ "wheel" "networkmanager" ];
shell = pkgs.zsh;
};
programs.zsh.enable = true;
programs.git.enable = true;
programs.steam.enable = false;
fonts.packages = with pkgs; [
inter
jetbrains-mono
noto-fonts
noto-fonts-cjk-sans
noto-fonts-emoji
];
environment.systemPackages = with pkgs; [
curl
git
wget
vim
htop
fastfetch
];
environment.gnome.excludePackages = with pkgs; [
gnome-tour
epiphany
geary
];
system.stateVersion = "25.05";
}
What makes this feel premium instead of plain
Intentional fonts. Good typography changes the feel of a machine immediately. Inter for UI and JetBrains Mono for terminal/editor is a clean starting point.
Small package surface. The machine gets a tiny set of universal tools. Personal workflow tools move to Home Manager.
Minor desktop trimming. You are not “debloating” for sport. You are just removing the pieces you know you will never use.
Zsh declared once. The shell is part of the machine identity, so it belongs in the system layer.
The Home Manager module
This is where the setup starts to feel personal without becoming messy. Keep it focused on tools you use every day.
home/piotr.nix
{ config, pkgs, ... }:
{
home.username = "piotr";
home.homeDirectory = "/home/piotr";
home.stateVersion = "25.05";
home.packages = with pkgs; [
eza
fd
ripgrep
bat
zoxide
fzf
firefox
alacritty
vscode
];
programs.home-manager.enable = true;
programs.git = {
enable = true;
userName = "Piotr";
userEmail = "[email protected]";
extraConfig = {
init.defaultBranch = "main";
pull.rebase = false;
};
};
programs.zsh = {
enable = true;
autosuggestion.enable = true;
syntaxHighlighting.enable = true;
shellAliases = {
ll = "eza -la --icons=auto";
cat = "bat";
update = "nix flake update && sudo nixos-rebuild switch --flake .#laptop";
};
initExtra = ''
eval "$(zoxide init zsh)"
'';
};
programs.fzf.enable = true;
programs.bat.enable = true;
programs.eza.enable = true;
programs.alacritty = {
enable = true;
settings = {
window = {
padding = { x = 12; y = 12; };
opacity = 0.96;
};
font = {
normal.family = "JetBrains Mono";
size = 11.5;
};
};
};
programs.starship = {
enable = true;
settings = {
add_newline = false;
character = {
success_symbol = "[➜](bold green)";
error_symbol = "[➜](bold red)";
};
};
};
gtk = {
enable = true;
theme = {
name = "adw-gtk3";
package = pkgs.adw-gtk3;
};
iconTheme = {
name = "Papirus-Dark";
package = pkgs.papirus-icon-theme;
};
};
}
Why this layer matters
This is where “minimal” usually breaks. People either stop too early and end up with a machine that feels generic, or they keep stacking plugins and packages until the config turns into a scrapbook.
The middle path is better:
- Choose a terminal and set its padding, font, and opacity once.
- Choose a shell prompt that gives structure without turning every command into a Christmas tree.
- Pick better everyday CLI tools: eza, fd, ripgrep, bat, zoxide.
- Keep aliases few and memorable.
Commands that matter
NixOS feels premium when the maintenance loop is simple. These are the commands worth memorizing first.
basic workflow
# apply the current config
sudo nixos-rebuild switch --flake .#laptop
# test a config without making it the boot default
sudo nixos-rebuild test --flake .#laptop
# update pinned inputs
nix flake update
# inspect what changed in the lock file
git diff flake.lock
# clean old generations after you are comfortable
sudo nix-collect-garbage -d
The premium part is not that these commands are powerful. It is that they are repeatable. You can teach them to your future self.
What not to overdo
The easiest way to ruin a minimal NixOS config is to confuse “declarative” with “everything must be in here immediately.”
- Do not add five theming systems at once.
- Do not turn your shell into a plugin museum.
- Do not create ten modules before you have any repetition.
- Do not fill environment.systemPackages with every app you might someday try.
- Do not update blindly without skimming the lockfile diff and release notes.
Minimalism works when it reduces maintenance, not when it becomes another performance of self-control.