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. Useflake-partsinstead — 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
- Always commit flake.lock: Ensures reproducibility
- Use follows for inputs: Avoid duplicate dependencies
- Pin critical inputs: Use specific commits for stability
- Update regularly: Keep inputs fresh for security
- Document inputs: Comment why each input is needed
- Test before updating: Use
nix flake check - Use templates: Standardize project structure
- Version outputs: Tag releases properly
- Minimal inputs: Only include what’s needed
- 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