How NixOS works#
NixOS is based on Nix, a purely functional package management system. Nix stores all packages in isolation from one another under /nix/store.
/nix/store/5rnfzla9kcx4mj5zdc7nlnv8na1najvg-firefox-3.5.4/The hash in the example path identifies a store object. For common derivations, the output path is determined by inputs such as the build description; it should not be treated as a hash of the output file contents. Once built, a package is not overwritten. Instead, changing its build description, or “Nix expression,” rebuilds it and installs it at a different path in /nix/store, avoiding conflicts with the old version. NixOS extends this by using Nix to build configuration files as well as packages. For example, the SSH daemon’s configuration is also built from a Nix expression and stored at a path like this.
/nix/store/s2sjbl85xnrc18rl4fhn56irkxqxyk4p-sshd_configBy building the entire system configuration from Nix expressions, NixOS ensures that configurations do not overwrite one another and can be rolled back, among other benefits.
One important consequence of the way Nix/NixOS stores packages is that it does not install software into shared directories such as /bin, /sbin, /lib, and /usr in the traditional distribution style. All packages are kept in /nix/store. Compatibility entry points such as /bin/sh remain; the exact layout varies with system modules. Avoiding “global” directories such as /bin is what allows multiple package versions to coexist. Nix does have an /etc directory for system-wide configuration files, but most files there are symbolic links to generated files in /nix/store.
Departing from the traditional FHS software layout means that third-party prebuilt programs relying on fixed /usr/lib or dynamic loader paths may need wrappers, patches, or compatibility environments. It does not mean that Nix-managed dependencies generally fail.
A declarative system configuration model#
In NixOS, the entire operating system, including the kernel, applications, system packages, and configuration files, is built by the Nix package manager from descriptions in a purely functional build language. This purely functional approach means that building a new configuration does not overwrite earlier configurations. Most other features follow from this.
You configure a NixOS system by writing a specification in /etc/nixos/configuration.nix that describes what you want the machine to do. For example, the following is a historical configuration fragment for a BIOS/GRUB system. A real installation also requires a complete generated hardware configuration and verification of the target disk; do not use this directly on a new machine:
{
boot.loader.grub.device = "/dev/sda";
fileSystems."/".device = "/dev/sda1";
services.openssh.enable = true;
}After editing /etc/nixos/configuration.nix, run the following command to apply it:
nixos-rebuild switchThis command performs everything needed by the configuration, including downloading and compiling OpenSSH and generating the SSH server’s configuration file.
Reliable upgrades#
Pinning build inputs reduces the influence of the existing system’s installation history on build results. Upgrades still require checks for configuration compatibility, activation scripts, and migrations of writable data.
Atomic upgrades#
NixOS uses _transactional_ configuration management: configuration changes such as upgrades are _atomic_. Separating old and new store artifacts and switching profiles atomically reduces the risk of overwriting files midway through an upgrade. Bootloader writes, service activation, and writable data still have separate failure boundaries.
Rollback#
Because the files in a new configuration do not overwrite old files, you can roll back atomically to an earlier configuration. For example, if you dislike a new configuration after running nixos-rebuild switch, you can simply roll back:
nixos-rebuild switch --rollbackIn fact, older configurations that have not been deleted and fall within the boot menu’s retention limit can appear in that menu. If a new configuration crashes or fails to boot properly, you can roll back by selecting an older configuration from the boot menu. Rollback is very fast: there is no need to restore large numbers of files from a backup.
Reproducible system configurations#
The declarative NixOS configuration model makes it straightforward to reproduce a system configuration on another machine, for example to test changes before applying them to a production server. After pinning dependencies and configuring hardware, disks, and boot settings for the target machine, move the declarative configuration to that NixOS machine and run nixos-rebuild switch. This gives you the same configuration, including the kernel, applications, and system services, except for mutable state such as the contents of /var.
Testing changes safely#
Rollback can help restore the declared environment, but cannot undo arbitrary service side effects or data migrations. For example, whether a change is as simple as enabling a system service or as complex as rebuilding the whole system with a new Glibc version, you can test it as follows:
nixos-rebuild testThis builds and activates the new configuration without making it the default boot configuration. After a reboot, the system therefore uses the existing boot default, provided it is still available.
For modules suitable for verification in a virtual machine, use the following approach:
nixos-rebuild build-vm
./result/bin/run-*-vmThis command builds and starts a virtual machine containing the new system configuration: a clone of the host configuration including all changes to configuration.nix. This mechanism can share the host’s Nix store, while the VM’s writable root filesystem remains separate from activation of the host system. Additional directories may also be mounted through configuration, so do not assume that a VM shares no data at all. Verify disks, networking, and shared directories against the current generated launch script and virtualisation options.
System rollback ≠ configuration rollback#
Rolling back a generation and the configuration files themselves are two different things.
回滚的是: Nix store 里构建好的系统(二进制结果)
不回滚的是:/etc/nixos/configuration.nix 这个文本文件What actually happens
你的操作流程:
configuration.nix (v2)
↓ nixos-rebuild switch
Generation 42 ← 当前启动
Generation 41 ← 上一个
nixos-rebuild switch --rollback
↓
Generation 41 ← 当前启动
Generation 42 ← 还在 nix store 里
但是 /etc/nixos/configuration.nix 还是 v2 的内容!In other words, if you run nixos-rebuild switch again after rolling back, it will redeploy v2 and return you to the problematic state.
The correct workflow is therefore to manage your configuration files with Git, giving you history along both dimensions:
cd /etc/nixos
git init
git add .
git commit -m "working config baseline"
# 改了配置
git add configuration.nix
git commit -m "try enabling some service"
nixos-rebuild switch
# 出问题了
nixos-rebuild switch --rollback # 系统恢复
git revert HEAD # 配置文件也恢复Generations handle rollback of system state, while Git handles rollback of configuration intent; you need both for a complete workflow.
build-vm is cleaner: the system inside the VM is completely separate from the host’s generations, and the host’s generation list does not change at all, so this problem does not arise with build-vm. You can first validate suitable services and modules this way; actual hardware, graphics, and driver behavior still require verification in the relevant environment.
Arch (imperative) vs NixOS (declarative)#
"As an Arch user, you are used to changing system state. In NixOS, you primarily describe system state through declarations, while writable runtime state still exists."
Core differences#
| Dimension | Arch Linux (imperative) | NixOS (declarative) |
|---|---|---|
| Installing software | sudo pacman -S git |
In environment.systemPackages, add git |
| Editing files | Manually edit /etc/fstab |
Define mount points in fileSystems |
| System state | The current state is the accumulation of all past commands | The current state is the evaluated result of configuration files |
| Rollback | Requires additional mechanisms such as package history, backups, or filesystem snapshots | Switch to an older closure when system generations are retained; restore data separately |
| Side effects | File edits take effect immediately and have side effects | Configuration edits do not take effect until you run nixos-rebuild |
The nature of NixOS “purity”#
1. /nix/store: a read-only haven
On Arch, /bin/git is the binary itself. On NixOS, Git in the system environment is usually accessed through profile links such as /run/current-system/sw/bin/git, pointing to a path like:
/nix/store/h3l9...-git-2.43.0/bin/git
- Hash prefix: represents all dependencies and configuration used to build the software. If a dependency changes, the hash changes.
- Immutability: you cannot modify anything in
/nix/store. This reduces the risk of overwriting package files, but does not prevent activation errors, data corruption, or forced changes by an administrator.
2. The truth about /etc
Most files in /etc on NixOS are read-only symbolic links.
- Imperative thinking: want to change the SSH configuration? Open
/etc/ssh/sshd_configand edit it. - Declarative thinking: in
configuration.nix, writeservices.openssh.settings.PermitRootLogin = "no";. Nix automatically generates a symbolic link to the correct configuration file.
Practical exercise: moving beyond the Arch approach#
Arch command:
sudo pacman -S hello
sudo systemctl enable --now dockerNixOS declaration:
In configuration.nix:
{ pkgs, ... }: {
environment.systemPackages = [ pkgs.hello ];
virtualisation.docker.enable = true;
}Apply it by running: sudo nixos-rebuild switch
Advice for Arch users#
Prefer changing the Nix module responsible for generating a file, rather than editing generated files and allowing configuration to diverge from intent. Source files in /etc/nixos and other writable, unmanaged files are a different matter.
About the Nix tool itself#
The background to nix-env’s design#
nix-env was Nix’s earliest user interface, introduced alongside Nix itself around 2003.
Design purpose#
Nix was not originally designed for NixOS, but as a package manager installable on any Linux/macOS system.
The envisioned scenario was:
你用的是 Ubuntu / macOS
但你想要:
- 多版本共存(python2 和 python3 同时装)
- 不需要 root 权限装包
- 可以回滚
- 不污染系统的 /usr/libnix-env was designed for this scenario, serving a role comparable to apt install / brew install. It provides profile management for Nix users and can also be used on NixOS.
nix-env -iA nixpkgs.firefox # 传统 channel/profile 的用户安装示例
nix-env -e firefox # 从该用户 profile 移除
nix-env --rollback # 回滚该用户 profileThe real problems it solved#
Coexisting versions:
# 同时装两个版本,互不干扰(不同版本的 store 路径可共存;同一 profile 的同名程序还可能冲突)
nix-env -iA nixpkgs.python2 # 历史版本示例,不保证当前 nixpkgs 提供
nix-env -iA nixpkgs.python3Installation without root:
# 普通用户在服务器上装自己的工具,不需要找管理员
nix-env -iA nixpkgs.ripgrepNix’s early design already centered on isolated package paths and profile rollback.
Trade-offs between declarative management and imperative interfaces#
Times have changed, and better alternatives have emerged:
Problems with nix-env |
Alternatives |
|---|---|
| Imperative; state is not recorded in a file | Declarative management with home.nix |
| Mixing nixpkgs versions is difficult to track | Pin versions with flakes |
| Temporary trials need not modify a long-term profile | nix-shell -p or nix shell leaves the long-term profile unchanged; artifacts may still remain in the store |
nix-env targets traditional user profiles, while nix profile targets profiles for the new CLI; the two formats cannot be freely mixed. When Home Manager already manages your stable software selection, you can reduce additional imperative installation interfaces.
Choosing a management interface#
nix-envis an early solution for "using Nix to manage packages on non-NixOS systems". Home Manager can manage a stable user environment, while traditional profile and channel workflows still have their uses.
The first half is adapted from the official NixOS explanation of how it works. Historical examples are included only to explain the mechanisms.
References & further reading
Adapted from technical notes. Examples use generic names.