Skip to content

Migration from Channels

Overview

Channels (nix-channel) plus NIX_PATH / <nixpkgs> are the classic way to supply Nixpkgs and other expression trees: you subscribe to a moving URL, run nix-channel --update, and import <nixpkgs> in Nix files or shell commands. Flakes replace that implicit lookup with explicit inputs in flake.nix and exact pins in flake.lock.

Migrating a project or NixOS configuration means enabling the flake CLI, declaring inputs, moving configuration into flake outputs, committing the lockfile, and switching rebuild commands to --flake. You stop treating channel updates as the source of truth for that repo; the lockfile is. See Flakes vs Channels for a side-by-side comparison and Flake (concept) for vocabulary.

Details

Enable the new CLI and flakes. Add to nix.conf (system-wide or user):

experimental-features = nix-command flakes

Or on NixOS / Home Manager: nix.settings.experimental-features = [ "nix-command" "flakes" ];. Both features remain experimental (Nix ≥ 2.4); see nix-command and flakes. Without them, nix build, nix flake, and flake-based nixos-rebuild are unavailable.

Add flake.nix with pinned inputs. Declare at least inputs.nixpkgs.url on a release branch or tag (for example github:NixOS/nixpkgs/nixos-26.05). Add other inputs (Home Manager, custom flakes, tarballs) the same way. Run any flake command once to generate flake.lock, then commit it so CI and collaborators share the same dependency graph. If the flake lives in a Git tree, only tracked files are copied into the evaluation; stage new config files before rebuild or Nix will not see them.

Move configuration into outputs. NixOS systems belong under nixosConfigurations.<name> via nixpkgs.lib.nixosSystem; standalone Home Manager setups use homeConfigurations. Package overrides and dev shells use packages, devShells, and related keys. Output shapes and rebuild commands are documented in nixosConfigurations and homeConfigurations—this page focuses on what changes relative to channels.

Replace <nixpkgs> imports. In channel workflows, modules and shells often do import <nixpkgs> { ... }. In flakes, nixpkgs comes from the realized input:

  • Inside outputs: nixpkgs.legacyPackages.${system}.hello or import nixpkgs { inherit system; config = ...; }.
  • In modules that need other inputs: pass inputs through specialArgs on nixosSystem / homeManagerConfiguration, then reference inputs.home-manager (or similar) in module code instead of hard-coded paths.

Stop relying on nix-channel --update for this project. Channel subscriptions can remain on the machine for ad hoc nix-shell or legacy tools, but the flake's locked inputs define what that repository builds. Bump nixpkgs intentionally with nix flake update (or a targeted input update such as nix flake update nixpkgs), review the diff in flake.lock, and rebuild.

Update rebuild and build commands. Classic NixOS on channels:

sudo nixos-rebuild switch

Flake-based equivalent (configuration name from nixosConfigurations):

sudo nixos-rebuild switch --flake .#hostname

Omitting #hostname makes nixos-rebuild look up nixosConfigurations.<current-hostname>. The same flake reference works for nix build, nix develop, and remote deploy. Pure evaluation applies to flake builds; impure channel paths are not part of the locked graph.

Registry vs project inputs. The flake registry still provides CLI convenience (nixpkgs#hello, nix run github:owner/repo#app) without a local channel. For projects, locked flake inputs—not the registry default—are the source of truth. Registry entries can move; your flake.lock does not unless you update it.

If you are not adopting flakes fully. Flakes are optional on nix.dev; alternatives include npins or builtins.fetchTarball with an explicit hash pin, or a thin flake.nix that only wraps fetched sources. Those patterns trade flake-native locking and nix flake check integration for smaller surface area. This wiki treats flakes as the primary reproducible path; mention the others when a team cannot enable experimental features yet.

Examples

Before — channel subscription + configuration.nix. Machine tracks nixpkgs-unstable; NixOS config imports the channel implicitly:

nix-channel --add https://channels.nixos.org/nixpkgs-unstable nixpkgs
nix-channel --update
# configuration.nix (fragment)
{ config, pkgs, ... }:

{
  imports = [ ./hardware-configuration.nix ];
  system.stateVersion = "26.05";
  environment.systemPackages = [ pkgs.hello ];
}

Rebuild: sudo nixos-rebuild switch (uses <nixpkgs> from NIX_PATH / default channel layout).

After — flake with nixosConfigurations + lock. Same logical config; nixpkgs revision is pinned in git:

# flake.nix
{
  description = "My NixOS system";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
  };

  outputs = { self, nixpkgs, ... }@inputs: {
    nixosConfigurations.hostname = nixpkgs.lib.nixosSystem {
      specialArgs = { inherit inputs; };
      modules = [
        ./configuration.nix
        ./hardware-configuration.nix
      ];
    };
  };
}
nix flake lock   # writes flake.lock; commit both files
sudo nixos-rebuild switch --flake .#hostname

To upgrade nixpkgs later: nix flake update nixpkgs, inspect flake.lock, then rebuild—not nix-channel --update for this configuration.

References

See also