NixOS Flakes transition: managing configuration drift during channel migration
0 reputation · 17 Aug 2024, 16:37 UTC
0 reputation · 17 Aug 2024, 16:37 UTC
NixOS traditionally relies on channels to manage system updates, where the state is derived from implicit channel versions. The introduction of Flakes shifts this model toward explicit dependency pinning via a flake.lock file to ensure absolute reproducibility across environments.
When transitioning a production system from channel-based deployments to a Flake-based workflow, there is a potential for configuration drift. This occurs because the nixos-rebuild command behaves differently depending on whether it is targeting a traditional configuration.nix or a Flake output.
Given that Flakes require the experimental-features = nix-command flakes configuration in nix.conf, the interaction between legacy channel updates and the new lock-file mechanism remains a point of operational uncertainty.
30275 reputation · 17 Aug 2024, 23:55 UTC
NixOS does not automatically blend channel‑based packages with flake‑pinned inputs. When you invoke nixos-rebuild with the --flake flag, the build uses only the inputs locked in flake.lock; the traditional channel mechanism is ignored. Without --flake, the system continues to rely on nix-channel entries. Therefore, during a phased migration you must decide which mode each rebuild uses, otherwise version conflicts can cause silent drift.
experimental-features = nix-command flakes in nix.conf.--flake flag makes Nix ignore $NIX_PATH and channel‑derived nixpkgs.nix-channel list and check /etc/nixos/configuration.nix for imports like or overlays that depend on a specific channel revision.# flake.nix
{
description = "NixOS system migrated to flakes";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
outputs = { self, nixpkgs }:
let
system = "x86_64-linux";
pkgs = import nixpkgs { inherit system; };
in
{
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
inherit system;
modules = [ ./configuration.nix ];
};
};
}
nix flake update (creates/updates flake.lock).nix build .#nixosConfigurations.myhost.config.system.build.toplevel. Verify no errors.sudo nix-store --realise $(readlink -f /run/current-system) (store path) or simply note the current generation number with sudo nix-env --list-generations -p /nix/var/nix/profiles/system.sudo nixos-rebuild switch --flake .#myhost.sudo nix-env --list-generations -p /nix/var/nix/profiles/system should show a new generation.nix store diff-closures (or use nix diff-closures) to ensure the closure of the new system matches the expected set of packages.nixos-version matches the revision recorded in flake.lock.sudo nixos-rebuild switch --rollback returns to the last working channel‑based generation.If your configuration.nix contains overlays, module imports, or pkgs lookups that depend on a channel‑specific nixpkgs revision (e.g., import {} with a pinned channel), you must replicate that pinning in the flake inputs. Without knowing whether such channel‑specific dependencies exist, the recommendation to keep the flake inputs unchanged may be insufficient. Please confirm whether any part of your configuration relies on a specific channel revision.
Use comments to ask for clarification. Post a solution as an answer.
30,275 reputation · 18 Aug 2024, 01:55 UTC
One clarification worth adding to the migration answer: even when nixos-rebuild --flake correctly ignores channels, other Nix invocations on the same host still consult NIX_PATH and the flake registry. Commands like nix-shell -p foo, nix run nixpkgs#foo, or any <nixpkgs> import in ad-hoc expressions will resolve against the old channel state, so the "drift" often shows up in developer tooling rather than in the system closure itself.
The common fix is to align those lookups with the locked input inside the flake's NixOS module:
{ inputs, ... }: {
nix.registry.nixpkgs.flake = inputs.nixpkgs;
nix.nixPath = [ "nixpkgs=${inputs.nixpkgs}" ];
}
With that in place, nix run nixpkgs#hello and <nixpkgs> resolve to the same revision as the system build, and channels become irrelevant even if they're still listed by nix-channel --list.
Two caveats to verify on your Nix version (behavior here has shifted across 2.x releases): the experimental-features requirement still applies for flake commands as of the 2.18–2.24 line, and setting nixPath to a store path string like above assumes the input is a flake source, not a tarball fetch. Confirm with nix eval --raw nixpkgs#path after rebuilding.