Tricks#
The big win of NixOS is that your system is just a function. Once you accept that, you can turn the same configuration.nix into a VM for testing, an ISO for rescue, a USB stick for cloning, a cloud image, or push it to a remote server over SSH. This page collects the recipes.
In this page#
Test a config as a VM without installing#
nixos-rebuild build-vm evaluates your configuration.nix and produces a QEMU VM runner. Perfect for iterating on a config before putting it on metal.
Classic#
nixos-rebuild build-vm -I nixos-config=./configuration.nix
./result/bin/run-*-vm
Flakes#
nixos-rebuild build-vm --flake .#myhost
./result/bin/run-*-vm
The VM shares the binary cache with your host, so rebuilds after the first one are nearly instant. The VM disk image is
./nixos.qcow2— delete it for a clean boot, keep it for persistence across runs.
Pass extra QEMU flags:
QEMU_OPTS="-m 8192 -smp 4 -vga virtio" ./result/bin/run-*-vm
Graphics-heavy (e.g. testing KDE with OpenGL):
QEMU_OPTS="-m 8192 -smp 4 -device virtio-gpu-gl -display gtk,gl=on" ./result/bin/run-*-vm
nixos-shell — lighter VM iteration#
nixos-shell gives you an even faster round-trip than build-vm — it mounts your home directory into the VM and drops you into a shell. Great for testing a single service.
nix-shell -p nixos-shell --run 'nixos-shell --flake .#myhost'
Build any image shape with nixos-generators#
nixos-generators turns a configuration.nix into any of: iso, install-iso, qcow, raw, raw-efi, vmware, virtualbox, amazon, proxmox, docker, lxc, sd-aarch64 (Raspberry Pi), and more.
Install it as a shell-only tool:
nix-shell -p nixos-generators
Build an installer ISO with your config embedded:
nixos-generate -f install-iso -c ./configuration.nix -o result-iso
Build a qcow2 VM image:
nixos-generate -f qcow -c ./configuration.nix -o result-qcow
Build a raw disk image for dd-ing to a physical disk:
nixos-generate -f raw-efi -c ./configuration.nix -o result-raw
The
install-isovariant boots to an installer that will wipe a target disk and install your config non-interactively. The plainisovariant is a live-only environment. Pick intentionally.
Personalized live USB from your own config#
The killer recipe: a USB stick that boots any machine straight into a pre-configured environment — your KDE, your shell, your tools, your dotfiles — without touching the machine’s disk.
Minimal live.nix:
{ pkgs, modulesPath, lib, ... }:
{
imports = [
"${modulesPath}/installer/cd-dvd/installation-cd-minimal.nix"
"${modulesPath}/installer/cd-dvd/channel.nix"
];
isoImage.isoName = lib.mkForce "nixos-live.iso";
services.xserver.enable = true;
services.desktopManager.plasma6.enable = true;
services.displayManager.sddm.enable = true;
services.displayManager.autoLogin = {
enable = true;
user = "nixos";
};
users.users.nixos = {
isNormalUser = true;
password = "nixos";
extraGroups = [ "wheel" "networkmanager" ];
shell = pkgs.fish;
};
programs.fish.enable = true;
environment.systemPackages = with pkgs; [
firefox kate partitionmanager
git neovim tmux ripgrep fd bat
gparted rsync
];
networking.networkmanager.enable = true;
# Don't bake in host-specific state
system.stateVersion = "24.11";
}
Build (classic):
nix-build '<nixpkgs/nixos>' -A config.system.build.isoImage -I nixos-config=./live.nix
# output: ./result/iso/nixos-live.iso
Build (flakes):
# flake.nix
outputs = { nixpkgs, ... }: {
nixosConfigurations.liveiso = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [ ./live.nix ];
};
};
nix build .#nixosConfigurations.liveiso.config.system.build.isoImage
# output: ./result/iso/nixos-live.iso
Or via nixos-generators:
nixos-generate -f iso -c ./live.nix -o result-iso
Flash to USB:
# identify the stick (be careful!)
lsblk
# write — adjust of= to match your USB device (NOT a partition)
sudo dd if=./result/iso/nixos-live.iso of=/dev/sdX bs=4M status=progress conv=fsync
sudo sync
Be absolutely sure
of=/dev/sdXis the USB stick and not your internal disk.ddwill happily overwrite anything. Double-check withlsblk -o NAME,SIZE,MODEL,TRAN— USB transports showusb.
Boot any machine with the stick: your desktop, your tools, your shell. Read-only system, writable /tmp and user home (in tmpfs).
Install NixOS remotely over SSH — nixos-anywhere#
nixos-anywhere installs NixOS onto any Linux box you can SSH into as root — cloud VM, VPS, a running Debian machine, a Raspberry Pi, anything. It wipes the target and replaces it with your flake’s config. No ISO or physical access needed.
nix run github:nix-community/nixos-anywhere -- \
--flake .#myhost \
root@target.example.com
nixos-anywherewill erase the target’s disk. For an existing Debian machine, this means nuking it from orbit — use only when that is the intent.
It also drives disko for declarative partitioning, so your disk layout is part of the config too. Repo link for examples: github.com/nix-community/nixos-anywhere.
Generations, rollback, diff#
List all system generations:
sudo nix-env --list-generations -p /nix/var/nix/profiles/system
Roll back one:
sudo nixos-rebuild switch --rollback
Activate a specific generation:
sudo /nix/var/nix/profiles/system-42-link/bin/switch-to-configuration switch
Boot an older generation: on the GRUB/systemd-boot menu at boot time, pick the “NixOS - Configuration N” entry. This always works, even if your current system is completely broken.
Diff two generations (what actually changed):
nix-shell -p nvd --run 'nvd diff /nix/var/nix/profiles/system-41-link /nix/var/nix/profiles/system-42-link'
Or the built-in:
nix store diff-closures \
/nix/var/nix/profiles/system-41-link \
/nix/var/nix/profiles/system-42-link
Preview a pending rebuild before applying:
sudo nixos-rebuild dry-build # what would be built/fetched
sudo nixos-rebuild dry-activate # what services would restart
sudo nixos-rebuild build # build, don't activate
nvd diff /run/current-system ./result
Garbage collection and store optimization#
One-shot:
# remove old profiles
sudo nix-collect-garbage -d
# only keep roots from the last 14 days
sudo nix-collect-garbage --delete-older-than 14d
# dedupe store paths by hardlinking identical files
sudo nix-store --optimise
Declarative (recommended):
nix.gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 14d";
};
nix.optimise = { automatic = true; dates = [ "weekly" ]; };
Aggressive GC (
-d) deletes every generation that isn’t the current one, including your rollback options. Prefer--delete-older-thanfor day-to-day.
Explore and introspect#
# current value of a specific option
nixos-option services.openssh.enable
# everything nixpkgs exposes, REPL-style
nix repl '<nixpkgs>'
# then: nix-repl> pkgs.firefox
# every module option visible interactively
nix repl '<nixpkgs/nixos>'
# explore a closure's dependencies
nix-shell -p nix-tree --run 'nix-tree /run/current-system'
# find which package ships a specific binary
nix-locate bin/ffprobe
Lightweight containers — nixos-container#
Spin up full NixOS systems as systemd-nspawn containers on the same host. Much lighter than VMs, useful for testing services in isolation — a real NixOS inside, sharing the host’s kernel.
containers.adguard = {
autoStart = true;
privateNetwork = true;
hostAddress = "10.42.0.1";
localAddress = "10.42.0.2";
config = { config, pkgs, ... }: {
services.adguardhome = {
enable = true;
openFirewall = true;
settings = {
bind_host = "0.0.0.0";
bind_port = 3000;
};
};
system.stateVersion = "24.11";
};
};
Real, packaged services work here: services.adguardhome, services.nginx, services.postgresql, services.grafana, services.jellyfin — anything with a NixOS module. Perfect for building a declarative homelab service on a real host without polluting the host’s service list.
Manage them:
sudo nixos-container list
sudo nixos-container start adguard
sudo nixos-container root-login adguard
sudo nixos-container update adguard # re-evaluate + restart after editing the host's configuration.nix
sudo nixos-container stop adguard
sudo nixos-container destroy adguard
Each container is a full NixOS evaluation — it gets its own
/etc/nixos/configuration.nixderivation, its own generations, its own journal.systemctl status systemd-nspawn@adguard.serviceon the host shows it.
Copy closures between machines#
# push your entire current system to a remote host's store
nix copy --to ssh://root@target /run/current-system
# pull a build result from a remote builder
nix copy --from ssh://builder.example.com /nix/store/<hash>-thing
Combined with nixos-rebuild --target-host root@..., you can build on a fast machine and activate on a slow one:
nixos-rebuild switch --flake .#myhost --target-host root@myhost --use-remote-sudo
Pin the kernel#
boot.kernelPackages = pkgs.linuxPackages_6_6; # LTS
# or:
boot.kernelPackages = pkgs.linuxPackages_latest;
# or pin to a specific:
boot.kernelPackages = pkgs.linuxPackagesFor (pkgs.linux_6_6.override {
# ...
});
Limit boot menu entries so you don’t accumulate:
boot.loader.systemd-boot.configurationLimit = 10;
boot.loader.grub.configurationLimit = 10;
Impermanence — wipe / on every boot#
Advanced but transformative: tmpfs-root so every boot is clean, with explicit environment.persistence."/persist" bind-mounts for the things you do want to keep. Forces you to be honest about what state actually matters. Full walkthrough in impermanence.md, and the partitioning that goes with it in disko.md.
Disaster recovery plan (worth writing down)#
Keep
configuration.nix(andflake.lockif flakes) in a private git repo.Build a personalized live USB (see above) and keep one physical copy.
Know how to boot an older generation from the bootloader menu.
Test
nixos-anywhereagainst a throwaway VM so you are comfortable with it.
With those four, “reinstalling” becomes: boot live USB → nix-shell -p nixos-anywhere → point it at the target disk → done. 15 minutes instead of a weekend.
See also#
getting-started.md — the VM-first learning loop.
configuration.md —
nixos-rebuildsubcommands.disko.md — declarative partitioning (required for
nixos-anywhereand impermanence).impermanence.md — wipe-on-boot root +
/persistbind-mounts.secrets.md — agenix / sops-nix for values that shouldn’t live in the store.
packages.md — for Flatpak and Steam alongside these tricks.