flake-update-20260505

Flakes Workflow

Work with Nix flakes: create, update, manage inputs and outputs.

When to Use

  • “create nix flake”
  • “update flake inputs”
  • “flake.lock”
  • “nix flake”

Quick Commands

Basic Flake Commands

# Initialize new flake
nix flake init

# Initialize with template
nix flake init -t templates#simple

# Show flake outputs
nix flake show

# Show flake metadata
nix flake metadata

# Check flake validity
nix flake check

# Update all inputs
nix flake update

# Update specific input
nix flake update nixpkgs

Lock File Management

# Lock dependencies
nix flake lock

# Update lock file
nix flake update

# Update specific input
nix flake lock --update-input nixpkgs

# Show lock file info
nix flake metadata --json | jq .locks

Flake Structure

Basic Flake Template

{
  description = "A very basic flake";

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

  outputs = { self, nixpkgs }: {
    packages.x86_64-linux.default = nixpkgs.legacyPackages.x86_64-linux.hello;
  };
}

Complete Flake Template (with flake-parts)

Always use flake-parts, NOT flake-utils. flake-parts uses a modular perSystem pattern that is cleaner and more composable.

{
  description = "Complete flake example";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
    flake-parts.url = "github:hercules-ci/flake-parts";
    home-manager = {
      url = "github:nix-community/home-manager";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = inputs@{ self, nixpkgs, flake-parts, home-manager }:
    flake-parts.lib.mkFlake { inherit inputs; } {
      systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];

      perSystem = { config, pkgs, system, ... }: {
        # Packages (per-system)
        packages = {
          default = pkgs.hello;
          myapp = pkgs.callPackage ./pkgs/myapp { };
        };

        # Checks (per-system)
        checks = {
          myapp = config.packages.myapp;
        };

        # Development shells (per-system)
        devShells.default = pkgs.mkShell {
          buildInputs = with pkgs; [ go gopls ];
        };
      };

      # Non-per-system outputs
      flake = {
        # Overlays
        overlays.default = final: prev: {
          myapp = final.callPackage ./pkgs/myapp { };
        };

        # NixOS configurations
        nixosConfigurations = {
          hostname = nixpkgs.lib.nixosSystem {
            system = "x86_64-linux";
            modules = [ ./configuration.nix ];
          };
        };

        # Home-manager configurations
        homeConfigurations = {
          "user@hostname" = home-manager.lib.homeManagerConfiguration {
            pkgs = nixpkgs.legacyPackages.x86_64-linux;
            modules = [ ./home.nix ];
          };
        };
      };
    };
}

Flake Inputs

Input Formats

inputs = {
  # GitHub repository (latest)
  nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";

  # Specific branch
  nixpkgs-stable.url = "github:nixos/nixpkgs/nixos-24.05";

  # Specific commit
  nixpkgs-pinned.url = "github:nixos/nixpkgs/abc123...";

  # GitLab
  myrepo.url = "gitlab:user/repo";

  # Git repository
  mylib.url = "git+https://git.example.com/repo.git";

  # Local path
  locallib.url = "path:/home/user/projects/lib";

  # Flake in subdirectory
  subflake.url = "github:owner/repo?dir=subdir";

  # With specific ref
  tagged.url = "github:owner/repo/v1.0.0";
};

Following Inputs

inputs = {
  nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";

  # Make home-manager use same nixpkgs
  home-manager = {
    url = "github:nix-community/home-manager";
    inputs.nixpkgs.follows = "nixpkgs";
  };

  # Multiple follows
  package = {
    url = "github:owner/repo";
    inputs = {
      nixpkgs.follows = "nixpkgs";
      flake-parts.follows = "flake-parts";
    };
  };
};

Flake Outputs

Output Types

outputs = { self, nixpkgs }: {
  # Packages
  packages.x86_64-linux.myapp = /* derivation */;

  # Default package
  packages.x86_64-linux.default = /* derivation */;

  # Apps
  apps.x86_64-linux.myapp = {
    type = "app";
    program = "${self.packages.x86_64-linux.myapp}/bin/myapp";
  };

  # Development shells
  devShells.x86_64-linux.default = /* mkShell */;

  # NixOS configurations
  nixosConfigurations.hostname = /* nixosSystem */;

  # Home-manager configurations
  homeConfigurations."user@host" = /* homeManagerConfiguration */;

  # Overlays
  overlays.default = final: prev: {
    mypackage = /* derivation */;
  };

  # NixOS modules
  nixosModules.default = /* module */;

  # Checks
  checks.x86_64-linux.test = /* derivation */;

  # Formatter
  formatter.x86_64-linux = nixpkgs.legacyPackages.x86_64-linux.nixfmt;
};

Per-System Outputs (with flake-parts)

flake-parts handles per-system iteration automatically via perSystem:

{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
    flake-parts.url = "github:hercules-ci/flake-parts";
  };

  outputs = inputs@{ self, nixpkgs, flake-parts }:
    flake-parts.lib.mkFlake { inherit inputs; } {
      systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];

      perSystem = { config, pkgs, ... }: {
        packages.default = pkgs.hello;
        devShells.default = pkgs.mkShell {
          buildInputs = [ pkgs.go ];
        };
      };
    };
}

Note: Do NOT use flake-utils. Use flake-parts instead — it is more modular, composable, and is the standard across all personal repositories.

Managing Lock Files

Lock File Structure

{
  "nodes": {
    "nixpkgs": {
      "locked": {
        "lastModified": 1234567890,
        "narHash": "sha256-...",
        "owner": "nixos",
        "repo": "nixpkgs",
        "rev": "abc123...",
        "type": "github"
      },
      "original": {
        "owner": "nixos",
        "ref": "nixos-unstable",
        "repo": "nixpkgs",
        "type": "github"
      }
    }
  }
}

Lock File Operations

# Create or update lock file
nix flake lock

# Update all inputs
nix flake update

# Update single input
nix flake lock --update-input nixpkgs

# Update and commit
nix flake update
git add flake.lock
git commit -m "chore: update flake inputs"

Pin Specific Revision

inputs = {
  # Pin to specific commit
  nixpkgs.url = "github:nixos/nixpkgs/abc123def456";
  # Or in lock file manually, then:
};
# Lock without updating
nix flake lock --no-update-lock-file

# Override locked input temporarily
nix build --override-input nixpkgs github:nixos/nixpkgs/main

Flake Templates

Create Template

# templates/go/flake.nix
{
  description = "Go project template";

  inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";

  outputs = { self, nixpkgs }: let
    system = "x86_64-linux";
    pkgs = nixpkgs.legacyPackages.${system};
  in {
    devShells.${system}.default = pkgs.mkShell {
      buildInputs = with pkgs; [
        go
        gopls
        golangci-lint
      ];
    };
  };
}

Use Template

# List available templates
nix flake show templates

# Initialize from template
nix flake init -t templates#go

# Use remote template
nix flake init -t github:user/repo#template-name

Provide Templates

# In your flake
{
  outputs = { self }: {
    templates = {
      go = {
        path = ./templates/go;
        description = "Go project template";
      };
      rust = {
        path = ./templates/rust;
        description = "Rust project template";
      };
    };

    defaultTemplate = self.templates.go;
  };
}

Flake Registries

Add Registry Entry

# Add flake to registry
nix registry add myflake github:user/repo

# Use registry entry
nix build myflake#package

Pin Registry Entry

# Pin to specific revision
nix registry pin nixpkgs

# Remove pin
nix registry remove nixpkgs

Custom Registry

# ~/.config/nix/registry.json
{
  "flakes": [
    {
      "from": {
        "id": "myflake",
        "type": "indirect"
      },
      "to": {
        "owner": "user",
        "repo": "repo",
        "type": "github"
      }
    }
  ],
  "version": 2
}

Flake Development Workflows

Local Development

# Test changes without committing
nix build .

# Test with dirty tree
nix build .#package --impure

# Build from specific commit
nix build github:user/repo/commit-hash

Override Inputs

# Override input temporarily
nix build --override-input nixpkgs path:/local/nixpkgs

# Use local checkout
nix build --override-input mylib path:/home/user/mylib

Flake References

# Current directory
nix build .
nix build .#package

# GitHub
nix build github:user/repo
nix build github:user/repo/branch
nix build github:user/repo/commit-hash
nix build github:user/repo#package

# GitLab
nix build gitlab:user/repo

# Path
nix build path:/absolute/path
nix build path:./relative/path

# URL
nix build git+https://example.com/repo.git

Flake Compatibility

For Non-Flake Users

# default.nix
(import (
  fetchTarball {
    url = "https://github.com/edolstra/flake-compat/archive/master.tar.gz";
    sha256 = "0000000000000000000000000000000000000000000000000000";
  }
) {
  src = ./.;
}).defaultNix

Shell.nix for Legacy

# shell.nix
(import (
  fetchTarball {
    url = "https://github.com/edolstra/flake-compat/archive/master.tar.gz";
    sha256 = "0000000000000000000000000000000000000000000000000000";
  }
) {
  src = ./.;
}).shellNix

Flake Best Practices

  1. Always commit flake.lock: Ensures reproducibility
  2. Use follows for inputs: Avoid duplicate dependencies
  3. Pin critical inputs: Use specific commits for stability
  4. Update regularly: Keep inputs fresh for security
  5. Document inputs: Comment why each input is needed
  6. Test before updating: Use nix flake check
  7. Use templates: Standardize project structure
  8. Version outputs: Tag releases properly
  9. Minimal inputs: Only include what’s needed
  10. Use flake-parts: Simplify multi-system outputs (NOT flake-utils)

Common Patterns

Multi-System Support

Use flake-parts — it handles multi-system automatically:

# systems list in mkFlake handles all the iteration
systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
perSystem = { pkgs, ... }: {
  packages.default = pkgs.hello;
};

Overlay Pattern

{
  outputs = { self, nixpkgs }: {
    overlays.default = final: prev: {
      mypackage = final.callPackage ./pkgs/mypackage { };
    };

    packages.x86_64-linux = let
      pkgs = import nixpkgs {
        system = "x86_64-linux";
        overlays = [ self.overlays.default ];
      };
    in {
      inherit (pkgs) mypackage;
    };
  };
}

Module Pattern

{
  outputs = { self, nixpkgs }: {
    nixosModules.myservice = { config, lib, pkgs, ... }: {
      options.services.myservice = {
        enable = lib.mkEnableOption "my service";
        port = lib.mkOption {
          type = lib.types.port;
          default = 8080;
        };
      };

      config = lib.mkIf config.services.myservice.enable {
        # service configuration
      };
    };

    nixosConfigurations.hostname = nixpkgs.lib.nixosSystem {
      modules = [
        self.nixosModules.myservice
        ./configuration.nix
      ];
    };
  };
}

Troubleshooting

Dirty Tree Errors

error: Git tree is dirty
# Commit changes
git add .
git commit -m "wip"

# Or use --impure
nix build --impure

Input Not Found

error: input 'nixpkgs' not found
# Check inputs are defined
nix flake metadata

# Update lock file
nix flake lock

Lock File Conflicts

# After merge conflict in flake.lock
nix flake lock

# Commit resolved lock file
git add flake.lock
git commit

Resources