Nixos
what i want you to do is talk about of how nixos works and its philosophy. How to understand
the nix language, and its syntax and how to use it. Explain how to use the commands and the
common commands to use. Explain it as if i'm new to nixos, explanation needs to be simple and
easy to understand
Welcome to NixOS! Let's build this up from the ground, starting with the big idea, then the
language, then the commands.
The philosophy: "describe what you want, don't do it manually"
Most Linux systems are imperative — you run commands one by one and the system changes.
NixOS is declarative — you write a file that says "this is what my system should look like," and
NixOS figures out how to make it so.
That one file is /etc/nixos/configuration.nix. It's your whole system described in text. Want
Firefox installed? You say so in that file. Want SSH enabled? That file. Want a specific
timezone? That file. You then run one command and NixOS builds exactly what you described.
The magic ingredient is the Nix store at /nix/store. Every package, every dependency, every
config snippet lives here with a unique hash in its path — like /nix/store/abc123-firefox-120.0.
Nothing ever overwrites anything. Old files stay. New things are added. This is why you can:
Roll back if something breaks — just boot into last week's generation
Reproduce your system exactly on another machine — same config file = identical
result
Have two versions of the same package installed at once without conflict
The Nix language
Nix has its own small language for writing configurations. Don't panic — it's not a generalpurpose programming language. Think of it more like a very structured config file format that
can do a bit of logic.
The basic building block: attribute sets
An attribute set is just a collection of name-value pairs, like a dictionary. It uses curly braces:
{
name = "Alice";
age = 30;
city = "Portland";
}
Almost everything in NixOS config is a nested attribute set. Your whole configuration.nix is one
giant attribute set.
Values can be strings, numbers, booleans, or lists:
{
myString = "hello";
myNumber = 42;
myBool
= true;
myList
= [ "firefox" "git" "vim" ];
}
The let ... in pattern — making a variable
When you want to reuse a value, you define it with let:
let
myUsername = "alice";
in
{
users.users.${myUsername}.isNormalUser = true;
home.homeDirectory = "/home/${myUsername}";
}
The ${...} is string interpolation — it inserts the variable's value into the string.
Functions
Functions in Nix look a little unusual. They use a colon to separate the argument from the body:
# A function that takes x and returns x + 1
x: x + 1
Most of the time in NixOS configs you'll see functions that take an attribute set as input — this is
how modules receive options:
{ config, pkgs, ... }:
{
# your configuration here
environment.systemPackages = [ pkgs.git ];
}
The { config, pkgs, ... }: at the top is the function argument. NixOS passes these in automatically
when it builds your system. pkgs is the big package collection; config is the current system
config (useful for reading other options).
The with expression — a shortcut
Instead of writing pkgs. in front of every package name, you can use with:
environment.systemPackages = with pkgs; [
git
firefox
vim
htop
];
This is just a readability shortcut — it puts pkgs into scope so you don't have to repeat it.
A real configuration.nix — annotated
{ config, pkgs, ... }:
# This is a function. NixOS calls it with these args.
{
# --- Boot --boot.loader.grub.enable = true;
boot.loader.grub.device = "/dev/sda";
# --- Network --networking.hostName
= "my-nixos-machine";
networking.networkmanager.enable = true;
# --- Timezone & locale --time.timeZone
= "America/New_York";
# --- Packages --environment.systemPackages = with pkgs; [
git
firefox
vim
];
# --- A user account --users.users.alice = {
isNormalUser = true;
extraGroups = [ "wheel" "networkmanager" ]; # wheel = sudo access
};
# --- Enable a service --services.openssh.enable = true;
# NixOS version — don't change this unless you know why
system.stateVersion = "24.05";
}
Every option like services.openssh.enable corresponds to a module that NixOS ships with. You
don't write service configs from scratch — you just flip options.
The commands you'll use every day
After editing your config, apply it:
sudo nixos-rebuild switch
This builds your new system and switches to it immediately. If it fails, your old system is
untouched.
Apply but don't switch yet — test on next reboot:
sudo nixos-rebuild boot
Test without committing — it reverts on reboot:
sudo nixos-rebuild test
List your generations (snapshots of past system states):
nixos-rebuild list-generations
# or
nix-env --list-generations
Roll back to the previous generation:
sudo nixos-rebuild switch –rollback
Or just pick an older one from the GRUB boot menu on startup.
Search for a package:
nix search nixpkgs firefox
Install a package temporarily (without adding it to config):
nix-shell -p python3
Run a one-off command without entering a shell:
nix-shell -p cowsay --run "cowsay hello"
Update your package channel (fetch latest package versions):
sudo nix-channel –update
Then run nixos-rebuild switch to apply updates.
Clean up old generations to free disk space:
sudo nix-collect-garbage -d
The -d flag deletes all old generations. You can also keep recent ones:
sudo nix-collect-garbage --delete-older-than 30d
The mental model in one sentence
You write a config file describing your perfect system → run one command → NixOS builds it
exactly, saves it as a generation, and lets you roll back anytime.
The weirdest thing to adjust to is that you stop typing apt install or pacman -S and start editing a
file instead. But once it clicks, the payoff is huge: your entire system is version-controlled,
reproducible, and impossible to break permanently.
Part 1: Flakes
The problem Flakes solve
Remember how nixos-rebuild switch pulls packages from your channel — essentially a
snapshot of nixpkgs that you update with nix-channel --update? The problem is that channel
isn't pinned anywhere in your config file. Two people with the "same" configuration.nix could
build totally different systems just because they updated their channels on different days.
Flakes fix this by locking your exact version of nixpkgs (and any other inputs) into a file called
flake.lock. That lock file travels with your config. Now anyone who clones your config gets the
exact same packages, byte for byte.
Think of flake.lock like a package-lock.json in Node, or a Cargo.lock in Rust — it pins
everything.
A basic flake.nix, annotated
{
description = "My NixOS configuration";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
home-manager.url = "github:nix-community/home-manager/release-24.05";
home-manager.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { self, nixpkgs, home-manager, ... }:
{
nixosConfigurations.my-machine = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./configuration.nix
home-manager.nixosModules.home-manager
{
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.users.alice = import ./home.nix;
}
];
};
};
}
Walking through it:
description — just a label, shows up in nix flake show.
inputs — the things your flake depends on. Here it's nixpkgs pinned to the 24.05 release
branch, and home-manager pinned to match. The follows line is important: it tells homemanager to use your nixpkgs instead of fetching its own, so you don't end up with two
different versions of the package set.
outputs — a function that takes your inputs and produces something buildable.
nixosConfigurations.my-machine is the system NixOS will actually build when you run
nixos-rebuild switch. Note ./configuration.nix is still your familiar file — Flakes don't
replace it, they just wrap it.
When you first build this, Nix generates flake.lock automatically, recording the exact git commit
hash of nixpkgs and home-manager at that moment. Commit flake.lock to git — that's the
whole point, it's what makes your build reproducible elsewhere.
# Build and switch using a flake (run from the directory with flake.nix)
sudo nixos-rebuild switch --flake .#my-machine
# Update all inputs to their latest versions
nix flake update
# Update just one input
nix flake lock --update-input nixpkgs
# See what a flake provides
nix flake show
# Enter a temporary dev shell defined by a flake
nix develop
The #my-machine part after . refers to the name you gave your config in nixosConfigurations. If
you only have one, you can often omit it.
One practical note: Flakes are technically still an "experimental feature" even though almost
everyone uses them. You enable them once in your configuration.nix:
nix.settings.experimental-features = [ "nix-command" "flakes" ];
Part 2: Home Manager
What problem it solves
configuration.nix controls system-wide stuff — services, boot, hardware, system packages. But
your dotfiles, shell aliases, and personal app settings aren't really "system" things — they're
your things. If another user logged into the same machine, they wouldn't want your .bashrc.
Home Manager lets you manage your user-level configuration the same declarative way: one
file describes your shell, your terminal, your git config, your personal packages — and homemanager switch makes it real, just like nixos-rebuild switch does for the whole system.
A basic home.nix, annotated
{ config, pkgs, ... }:
{
home.username
= "alice";
home.homeDirectory = "/home/alice";
home.stateVersion = "24.05"; # don't change carelessly, like NixOS's stateVersion
# Personal packages (separate from system packages)
home.packages = with pkgs; [
ripgrep
fzf
htop
];
# Manage a dotfile-style program declaratively
programs.git = {
enable
= true;
userName = "Alice";
userEmail = "alice@example.com";
};
programs.bash = {
enable = true;
shellAliases = {
ll = "ls -la";
gs = "git status";
};
};
# Drop a raw config file somewhere in $HOME
home.file.".config/htop/htoprc".text = ''
color_scheme=6
show_cpu_temperature=1
'';
}
A few things worth noticing:
programs.X.enable = true is the same pattern as services.X.enable in NixOS — Home
Manager has its own big library of pre-built modules for common apps (git, bash, zsh,
neovim, vscode, alacritty, tmux...). You're not writing raw config syntax for these apps;
you're setting Nix options and Home Manager generates the actual dotfile for you.
home.file is the escape hatch for anything that doesn't have a dedicated module — you
just write the raw file content directly into your Nix config.
Two ways to run Home Manager
Option A — standalone (not tied to NixOS, works even on other distros):
home-manager switch
Option B — integrated into NixOS (what the flake.nix above does): Home Manager becomes
part of your system rebuild, so sudo nixos-rebuild switch applies both your system config and
your home config in one shot. This is the more common setup once you're using Flakes, since
you only need to remember one command.
Home Manager commands
# Apply your home.nix changes (standalone mode)
home-manager switch
# Apply via flake (standalone, flake-based)
home-manager switch --flake .#alice
# List generations (yes, Home Manager has rollback too!)
home-manager generations
# Roll back to a previous generation
/nix/store/<hash>-home-manager-generation/activate
Home Manager generations roll back by running the activation script of an older generation
directly — slightly clunkier than NixOS's rollback, but it works the same way underneath.)
How it all fits together
Once you're using both:
~/nixos-config/
├── flake.nix
← pins nixpkgs + home-manager versions
├── flake.lock
← exact locked versions (commit this)
├── configuration.nix ← system-wide config (services, boot, hardware)
└── home.nix
← your personal user config (dotfiles, shell, git)
One command, sudo nixos-rebuild switch --flake . #my-machine, rebuilds both the system and
your home environment together, fully reproducibly, with everything pinned and rollback-able.
Updating your system
If you're using channels (the traditional way)
# Step 1: fetch the latest package versions
sudo nix-channel --update
# Step 2: rebuild and switch
sudo nixos-rebuild switch
If you're using Flakes (the modern way)
# Step 1: update your flake inputs (updates flake.lock)
nix flake update
# Step 2: rebuild and switch
sudo nixos-rebuild switch --flake .#my-machine
You can also update just one input if you only want, say, a newer nixpkgs but not a newer homemanager:
nix flake lock --update-input nixpkgs
After updating, always commit your flake.lock to git so your locked versions stay in sync. A
good habit is to treat every update as a small git commit:
nix flake update
sudo nixos-rebuild switch --flake .#my-machine
git add flake.lock
git commit -m "update nixpkgs 2024-07-04"
Testing before you commit to an update
This is one of NixOS's biggest advantages — you can try an update without fully committing to
it.
# Build it and switch NOW, but revert on next reboot
sudo nixos-rebuild test --flake .#my-machine
If something feels wrong, just reboot and you're back to your last stable generation. If it looks
good:
# Make it permanent (survives reboots)
sudo nixos-rebuild switch --flake .#my-machine
Or if you want to build it but not switch yet — apply on next boot:
sudo nixos-rebuild boot --flake .#my-machine
Rolling back when something breaks
NixOS keeps every previous system state as a generation. Nothing is ever deleted until you
manually clean it up. So if an update breaks something:
# Roll back to the previous generation immediately
sudo nixos-rebuild switch –rollback
Or pick a specific generation:
# List all your generations
nix-env --list-generations --profile /nix/var/nix/profiles/system
# Roll back to generation 42 specifically
sudo nix-env --switch-generation 42 --profile /nix/var/nix/profiles/system
sudo /nix/var/nix/profiles/system/bin/switch-to-configuration switch
You can also roll back from the GRUB boot menu — every generation shows up there as a
separate boot option. So even if your system won't boot at all, you just restart, pick an older
generation from the menu, and you're back. This is the safety net that makes updates much less
scary than on other distros.
Garbage collection — freeing up disk space
Every update, every rebuild, every nix-shell adds files to /nix/store. Nothing is ever automatically
deleted. Over months this grows — it's normal to see /nix/store reach 50–100GB on an active
system. You reclaim it with garbage collection.
# Delete ALL old generations and collect garbage
sudo nix-collect-garbage -d
The -d flag deletes old generations first, then cleans the store. Without it, generations are kept
and only truly unreferenced packages are cleaned.
If you want to be safer — keep recent generations in case you need to roll back:
# Keep anything used in the last 30 days
sudo nix-collect-garbage --delete-older-than 30d
# Same but for Home Manager generations
nix-collect-garbage --delete-older-than 30d
After garbage collecting, also clean up the boot menu entries:
sudo /run/current-system/bin/switch-to-configuration boot
This rebuilds the GRUB menu so it only shows generations that still exist.
A good rhythm is to run garbage collection once a month, or after a few updates have stacked
up.
Upgrading to a new NixOS release
NixOS does major releases twice a year (like 24.05 in May, 24.11 in November). Upgrading is
just pointing your config at the new release.
With channels:
# Switch to the new release channel
sudo nix-channel --add https://nixos.org/channels/nixos-24.11 nixos
sudo nix-channel --update
sudo nixos-rebuild switch
With Flakes:
Just update the URL in your flake.nix:
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; # ← bump this
home-manager.url = "github:nix-community/home-manager/release-24.11"; # ← and this
};
Then:
nix flake update
sudo nixos-rebuild switch --flake .#my-machine
One important thing: after a major version upgrade, update system.stateVersion in your
configuration.nix only once — it's not a target version, it's a "this system was originally installed
at this version" marker that controls some migration behaviour. You usually bump it once per
upgrade cycle and leave it alone.
Keeping your config tidy over time
A few habits that save headaches after months of use:
Remove packages you no longer use. In NixOS, unused packages don't actually waste space
until you garbage collect — but your config file gets cluttered. Periodically audit
environment.systemPackages and home.packages and remove anything you haven't used in
months.
Keep your config in git. If you aren't already:
cd /etc/nixos
git init
git add .
git commit -m "initial config"
Every time you make a change and rebuild, commit it. You'll have a full history of every state
your system has ever been in, which is invaluable when something breaks weeks later and you
can't remember what changed.
Use nixos-rebuild test before switch for risky changes. Any time you're touching services,
networking, or boot settings — test first.
Check what changed before switching. You can compare what a new build would change
without applying it:
# Build without switching, prints the store path
sudo nixos-rebuild build --flake .#my-machine
# Then diff the current system against the new one
nix store diff-closures /run/current-system ./result
This shows exactly which packages changed version, which were added, which were removed
— before you commit to the switch.
Quick reference: the maintenance cycle
Task
Command
Update packages (Flakes)
nix flake update
Update packages (channels) sudo nix-channel --update
Apply changes
sudo nixos-rebuild switch
Test without committing
sudo nixos-rebuild test
Roll back
sudo nixos-rebuild switch --rollback
Clean up old generations
sudo nix-collect-garbage -d
Task
Command
Keep last 30 days
sudo nix-collect-garbage --delete-older-than 30d
List generations
nix-env --list-generations --profile /nix/var/nix/profiles/system
See what changed
nix store diff-closures /run/current-system ./result
The overall philosophy matches the rest of NixOS: you make deliberate, explicit changes, you
can always see what's different, and you can always go back. Updates are less of an event and
more of a routine — and the rollback safety net means a bad update is an inconvenience, not a
disaster.
0
You can add this document to your study collection(s)
Sign in Available only to authorized usersYou can add this document to your saved list
Sign in Available only to authorized users(For complaints, use another form )