NixOS 21.05 changes the defaults behind a system declaration
Okapi updates the kernel and toolchain while changing service privileges, readiness, and container defaults. Declarative configuration still needs an account of the new behaviours it selects.
A declarative system can change behaviour when the package collection interpreting its declaration changes. NixOS 21.05, Okapi, provides several concrete examples. Its release notes announce a newer kernel and toolchain, but they also describe changes to service privileges and readiness. An unchanged service declaration can therefore select a different operational policy after the upgrade.
The release is dated May 31 and plans support through the end of December. Its default Linux kernel moves from 5.4 to 5.10. GCC moves from 9.3.0 to 10.3.0, and glibc moves from 2.30 to 2.32. Those versions identify the new foundation, but they do not by themselves explain what happens when the generated system activates. The service changes provide that more useful account.
I am drawn to NixOS because desired state can be described in configuration and reviewed. A distribution release is a reminder that the description has an interpreter in the broader sense of its modules and package definitions. Understanding a change means reading what those definitions now generate, including defaults that the local configuration never mentioned explicitly.
A service can be running before it is ready
The Unbound module changes how systemd learns that the resolver is ready. It now uses the systemd-enabled package and a unit of type notify. The release notes explain that Unbound reads its configuration and starts listening before systemd marks it active. A dependent service can therefore wait for a state that includes the resolver's actual readiness.
This addresses an activation problem that a process-started signal cannot solve. A daemon can exist before it can answer a query. Ordering another unit after that process starts is insufficient if the dependency is working name resolution. The new module uses the daemon's notification to make the unit's active state more closely describe the condition consumers need.
The change also moves privilege management into the service definition. Unbound starts as its dedicated user with required capabilities, under a stricter runtime environment based largely on the upstream example unit. Previously the module relied more on Unbound dropping privileges and entering its own restricted environment. The result is a different allocation of responsibility between the service manager and daemon startup.
The notes also describe reload support. Configuration now resides at /etc/unbound/unbound.conf, the path expected by the command-line tooling. This permits the control utility to find it without a custom location. Reloading the running configuration can avoid taking the resolver offline. File placement, daemon integration, and unit semantics all contribute to that capability.
The configuration interface changes with the implementation. extraConfig is replaced by a structured settings interface, and services.unbound.interfaces becomes services.unbound.settings.server.interface. This is an upgrade of the declarative schema as well as the package. A configuration that describes the old option names needs to account for that migration.
Service privileges become explicit earlier
PowerDNS changes to run entirely as a dedicated pdns user instead of starting as root and later dropping privileges. Its socket directory moves from persistent state under /var/lib to /run/pdns. The release includes an update from the 4.2 series to the 4.3 series. These details affect clients that refer to the socket and permissions attached to the old location.
The distinction between starting unprivileged and dropping privileges later is operationally meaningful. The service manager can apply the identity from process creation. That does not establish that every possible action of the daemon is safe, but it makes the startup identity a property of the unit. Readers can inspect it without reconstructing a later transition in the daemon.
uWSGI receives a related change through POSIX capabilities. Emperor mode no longer requires root, and the service defaults to the unprivileged uwsgi user. Additional capabilities can be declared through a module option. The release notes also explain a change for vassals using a different user or group. A shared label such as privilege reduction would miss the specific configuration consequence for those deployments.
Redis changes its default bind address to 127.0.0.1. A service enabled with the default address now listens on loopback instead of all public interfaces. That is a change in reachability even when the local declaration still only enables the service. A remote consumer depending on the old default will need an explicit network policy appropriate to its deployment.
These examples show why defaults belong in an upgrade review. A declaration can be syntactically valid while producing a unit with a different user or a listener with a different address. Evaluation success establishes that the new configuration is accepted. It does not establish that external clients or stored permissions match the new generated system.
Container changes reach beyond the kernel version
The release defaults to the unified cgroup hierarchy. It provides systemd.enableUnifiedCgroupHierarchy = false as a way to retain the older behaviour when a container runtime does not support the new hierarchy, followed by a reboot. The notes identify runtime compatibility as the reason for that option. Merely naming Linux 5.10 does not describe the hierarchy a running system will expose.
The Kubernetes module also enables containerd by default and supports custom remote container runtimes. The release notes report stricter handling of image OCI compliance with containerd and name string-valued CMD or ENTRYPOINT definitions as an affected example. This is a distribution module change with consequences for the artifacts workloads supply, not only for the node's packages.
That interaction is especially relevant to declarative management. The host configuration can fully describe its selected runtime while an application image still assumes behaviour accepted by a different runtime. The generated host state and the application's artifact contract are separate inputs to whether the workload starts. A release note about the runtime helps identify the boundary to examine.
I consider these service and runtime details more useful than a single claim that the upgrade is reproducible. Rebuilding the declared system is valuable, but the declaration can intentionally select changed semantics. Reproducibility cannot decide whether those semantics match the requirements of a service using the system.
Package reproducibility is also a policy choice
The release disables Python optimizations by default again because optimized builds are not reproducible. An option permits enabling them. This states a deliberate default tradeoff in the package collection. It does not assert that optimized Python is always inappropriate, or provide a benchmark showing an identical performance cost for all Python workloads.
The available choice illustrates a useful distinction between describing a build and obtaining identical output bytes from repeated builds. A derivation can identify the intended inputs while a build procedure still produces variable output. Disabling an optimization mode can improve that second property. An operator enabling it should understand which default guarantee the package maintainers chose to favour.
The desktop packages also change substantially, with GNOME 40 and Plasma 5.21.3 listed in the notes. GNOME's package attribute set and corresponding NixOS options move from gnome3 to gnome. That naming change affects the source configuration a maintainer edits. It is not merely a visible desktop version update.
Likewise, the default linux_latest package moves to the 5.12 series, distinct from the default system kernel's 5.10 series. The notes say the latest series is not officially supported with ZFS at this point and recommend another kernel for that case. Naming which kernel attribute a configuration selects is necessary to understand the release's kernel impact.
Generations preserve system choices, not every data transition
The Nix 2.3.7 profile manual explains generations as separate environments selected through symlinks. A profile update creates a new environment and changes the link, and rollback selects an earlier generation. That mechanism gives an inspectable account of how an older package environment remains available.
It does not reverse arbitrary modifications performed by applications. The Okapi notes give a direct example with Paperwork 2, whose changed on-disk format does not support downgrading to 1.3. Nextcloud also requires sequential major-version upgrades rather than skipping directly across several versions. These are properties of the applications' data and upgrade paths.
The distinction limits what can be inferred from an available previous generation. Older executables can remain selectable while stored application data has advanced to a format they cannot use. A profile transition and a database or document-format transition therefore require different recovery mechanisms. The release notes identify specific applications for which that difference is immediately relevant.
NixOS 21.05 supplies a new declared-system foundation along with revised module contracts. Unbound readiness, dedicated service users, loopback binding, and runtime selection each describe a particular changed behaviour. Reviewing those contracts explains the upgrade more accurately than treating the version number as a uniform change applied to every machine.