Pinning Nixpkgs with Flakes for Reproducible NixOS Deployments
Learn how NixOS flakes make system configuration reproducible by locking nixpkgs and other inputs, with a concrete minimal web server flake example and practical migration steps.
05 Aug 2025, 18:25 UTC

The problem: drifting channels and hidden state
In a traditional NixOS setup you edit /etc/nixos/configuration.nix and rely on nixpkgs channels. Over time each machine may end up on a different channel revision, making it hard to reproduce a specific system state or to share a configuration across teammates. The implicit NIX_PATH lookup also hides the exact dependency graph, which complicates CI/CD and troubleshooting.
Thesis: flakes make the dependency graph explicit and lockable
By moving your NixOS configuration into a flake, you declare every input such as nixpkgs, hardware configs and third party modules in flake.nix and lock their exact revisions in flake.lock. The flake outputs expose one or more nixosConfigurations, so a single repository can define many machines while sharing common modules. This gives you hermetic evaluation, deterministic builds, and a straightforward path to binary cache distribution.
Worked example: a minimal web server flake
Create a fresh directory for the flake as a regular user:
mkdir -p ~/my-nixos-flake && cd ~/my-nixos-flake nix flake init -t templates#nixosThis generates a starter flake.nix and flake.lock.
Edit flake.nix to add a simple nginx service. Keep the inputs block as is:
{ description = "A tiny NixOS web server flake"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; }; outputs = { self, nixpkgs }: let system = "x86_64-linux"; in { nixosConfigurations.webserver = nixpkgs.lib.nixosSystem { inherit system; modules = [ ./configuration.nix { services.nginx.enable = true; services.nginx.virtualHosts.example.com.documentRoot = "/var/www/html"; } ]; }; }; }Create a minimal configuration.nix that sets the hostname:
{ networking.hostname = "webserver"; }Check that the flake evaluates correctly:
nix flake checkBuild the system closure without sudo:
nix build .#nixosConfigurations.webserver.config.system.build.toplevelDeploy to a real machine with sudo:
sudo nixos-rebuild switch --flake ~/my-nixos-flake#webserverVerify the deployment:
systemctl status nginx
Trade offs and limitations
Flakes are gated behind experimental features. You must add experimental-features = [ nix-command flakes ] to nix.conf or set NIX_CONFIG before using flake commands.
Evaluation can slow in large monorepos with many transitive inputs. Mitigate by setting inputs..flake = false for non flake dependencies or splitting into smaller flakes.
Flakes do not handle secrets. Use sops-nix or agenix and ensure secrets are never committed to flake.lock.
Actionable closing steps
Enable flakes per user by editing ~/.config/nix/nix.conf with experimental-features = nix-command flakes
Start with a small self contained flake for one machine and keep existing /etc/nixos/configuration.nix as an imported module for incremental migration.
Run nix flake check and nix flake metadata after each change to confirm lockfile updates.
Push the flake to Git and configure CI to run nix build .#nixosConfigurations..config.system.build.toplevel and push to a binary cache.
Periodically run nix flake update, review the diff in flake.lock, then redeploy.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.