| name | Nix |
| description | Expert guidance on Nix, NixOS, and home-manager best practices. USE WHEN working with Nix expressions, NixOS configuration, home-manager, flakes, or Nix package development. |
Nix Best Practices
Purpose
Expert guidance on Nix, NixOS, and home-manager following best practices.
Context Detection
This skill activates when:
- Current directory contains
flake.nix,default.nix,shell.nix, orconfiguration.nix - Git repository contains Nix configuration files
- User is working with
.nixfiles - User explicitly mentions NixOS, home-manager, flakes, or Nix packages
- Commands like
nix build,nixos-rebuild, orhome-managerare mentioned
Workflow Routing
When the user's request matches specific Nix operations, route to the appropriate workflow:
| Workflow | Trigger | File |
|---|---|---|
| Build | "build nix package", "nixos-rebuild build", "compile nix" | workflows/Build.md |
| Debug | "debug nix", "nix error", "troubleshoot build", "evaluation error" | workflows/Debug.md |
| Develop | "development shell", "nix develop", "devShell", "direnv" | workflows/Develop.md |
| Deploy | "deploy nixos", "nixos-rebuild switch", "remote deployment" | workflows/Deploy.md |
| Package | "create package", "nix derivation", "buildGoModule", "package app" | workflows/Package.md |
| Flakes | "create flake", "flake.lock", "update inputs", "flake outputs" | workflows/Flakes.md |
| Secrets | "manage secrets", "agenix", "encrypt secrets", "age encryption" | workflows/Secrets.md |
| Security | "harden nixos", "apparmor", "firewall", "security hardening" | workflows/Security.md |
| Troubleshoot | "hash mismatch", "nix failing", "common errors", "fix nix issue" | workflows/Troubleshoot.md |
When to use workflows:
- Route when the user explicitly asks about one of these operations
- Workflows provide comprehensive, focused guidance for specific Nix tasks
- For general Nix guidance or module configuration, continue with this main skill
Core Principles
1. Declarative Configuration Over Imperative
# Good: Declarative
services.nginx.enable = true;
# Bad: Imperative
systemd.services.nginx.postStart = "systemctl start nginx";
2. Reproducibility
Same inputs = Same outputs
- Pin versions explicitly
- Use flake.lock for consistency
- Avoid impure operations
3. Modularity
Break configurations into focused, reusable modules
# Good: Modular
imports = [
./hardware.nix
./networking.nix
./services.nix
];
# Bad: Everything in one file
4. Version Control Everything
- Track all Nix configurations in git
- Commit flake.lock changes
- Document why changes were made
5. Use Flakes for Modern Nix
Flakes provide:
- Hermetic evaluation
- Standardized structure
- Dependency locking
- Better caching
NixOS Configuration Patterns
Host Configuration Structure
# systems/<hostname>/
├── boot.nix # Bootloader, initrd, kernel modules
├── hardware.nix # Hardware settings, filesystems, mounts
├── extra.nix # Optional: additional host-specific config
└── home.nix # Optional: host-specific home-manager config
Using mkHost Pattern
# In flake.nix
nixosConfigurations = {
hostname = libx.mkHost {
hostname = "hostname";
system = "x86_64-linux";
hardwareType = "desktop"; # or "rpi4"
desktop = "sway"; # or "niri", or null
nixpkgs = nixpkgs; # or nixpkgs-25_05 for stable
};
};
Common Module Organization
systems/common/
├── base/ # Essential base configuration
├── desktop/ # Desktop environment configs
├── hardware/ # Hardware-specific modules
├── programs/ # Application configurations
├── services/ # System services
└── users/ # User account definitions
Checking globals.nix
Always check globals.nix for:
- Machine definitions (IPs, SSH keys)
- DNS zone configurations
- VPN settings
- Syncthing device IDs
- Network topology
Module Best Practices
Define Options Properly
{ config, lib, pkgs, ... }:
{
options = {
services.myservice = {
enable = lib.mkEnableOption "my service";
port = lib.mkOption {
type = lib.types.port;
default = 8080;
description = "Port to listen on";
};
configFile = lib.mkOption {
type = lib.types.path;
description = "Path to configuration file";
};
};
};
config = lib.mkIf config.services.myservice.enable {
# Implementation
};
}
Use Types Correctly
Common types:
types.bool- Boolean valuestypes.int- Integerstypes.str- Stringstypes.path- File system pathstypes.port- Network ports (1-65535)types.listOf types.str- Liststypes.attrs- Attribute setstypes.package- Nix packages
Leverage mkIf, mkMerge, mkDefault
# Conditional configuration
config = lib.mkIf config.services.myservice.enable {
# ...
};
# Merge multiple configurations
config = lib.mkMerge [
{ always.present = true; }
(lib.mkIf condition { conditional.value = true; })
];
# Provide defaults that can be overridden
services.myservice.port = lib.mkDefault 8080;
Package Development
Use callPackage Pattern
# In pkgs/default.nix
{
mypackage = pkgs.callPackage ./mypackage { };
mytool = pkgs.callPackage ./mytool { };
}
Package Definition
# pkgs/mypackage/default.nix
{ lib
, stdenv
, fetchFromGitHub
, buildGoModule # or rustPlatform, python3Packages, etc.
}:
buildGoModule rec {
pname = "mypackage";
version = "1.0.0";
src = fetchFromGitHub {
owner = "owner";
repo = "repo";
rev = "v${version}";
hash = "sha256-...";
};
vendorHash = "sha256-...";
meta = with lib; {
description = "Package description";
homepage = "https://example.com";
license = licenses.mit;
maintainers = with maintainers; [ ];
platforms = platforms.linux;
};
}
Using Overlays
# overlays/default.nix
{ inputs }:
{
additions = final: _prev: import ../pkgs { pkgs = final; };
modifications = final: prev: {
# Override existing packages
somepackage = prev.somepackage.overrideAttrs (old: {
version = "custom";
});
};
}
Flake Management
Essential Commands
# Lock dependencies
nix flake lock
# Update all inputs
nix flake update
# Update specific input
nix flake update nixpkgs
# Check flake validity
nix flake check
# Show flake outputs
nix flake show
# Show flake metadata
nix flake metadata
Flake Structure
{
description = "Flake description";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
};
outputs = { self, nixpkgs }: {
nixosConfigurations = { ... };
homeConfigurations = { ... };
packages = { ... };
devShells = { ... };
};
}
Home-Manager Patterns
Environment Variables
home.sessionVariables = {
EDITOR = "vim";
VISUAL = "vim";
BROWSER = "firefox";
};
XDG Config Files
# Symlink static files
xdg.configFile."myapp/config.yml".source = ./myapp-config.yml;
# Generate files dynamically
xdg.configFile."myapp/generated.conf".text = ''
setting1 = ${someValue}
setting2 = value2
'';
# Make executable
xdg.configFile."bin/script.sh" = {
source = ./script.sh;
executable = true;
};
Services
# User-level systemd service
systemd.user.services.myservice = {
Unit = {
Description = "My Service";
After = [ "network.target" ];
};
Service = {
ExecStart = "${pkgs.mypackage}/bin/myservice";
Restart = "on-failure";
};
Install = {
WantedBy = [ "default.target" ];
};
};
Secrets Management with agenix
Define Secrets
# secrets.nix
let
user = "ssh-ed25519 AAAAC3...";
system = "ssh-ed25519 AAAAC3...";
in {
"secret.age".publicKeys = [ user system ];
}
Use Secrets in Configuration
{
age.secrets.mySecret = {
file = ../secrets/mySecret.age;
owner = "myuser";
group = "mygroup";
};
# Reference in config
services.myservice.passwordFile = config.age.secrets.mySecret.path;
}
Encrypt Secrets
# Encrypt a secret
agenix -e secrets/mySecret.age
# Re-key all secrets
agenix -r
Safety and Testing
Build Without Switching
# Build configuration
nixos-rebuild build --flake .#<hostname>
# Dry run (show what would change)
nixos-rebuild dry-build --flake .#<hostname>
# Test without adding to bootloader
nixos-rebuild test --flake .#<hostname>
Rollback Strategy
# List generations
nixos-rebuild list-generations
# Rollback to previous generation
nixos-rebuild switch --rollback
# Switch to specific generation
nixos-rebuild switch --switch-generation <number>
Keep Old Generations
- Never delete all old generations
- Keep at least 2-3 recent generations for rollback
- Clean periodically with:
nix-collect-garbage -d
Common Patterns
Conditional Imports
imports = [
./base.nix
] ++ lib.optionals (desktop != null) [
./desktop/${desktop}
];
String Interpolation
# Simple
message = "Hello ${name}";
# Multi-line
config = ''
setting1 = ${value1}
setting2 = ${value2}
'';
# Escape $
script = ''
echo "Nix variable: ${nixVar}"
echo "Shell variable: ''${shellVar}"
'';
List Operations
# Concatenation
all = list1 ++ list2;
# Filter
filtered = lib.filter (x: x > 5) list;
# Map
doubled = map (x: x * 2) list;
Attribute Set Operations
# Merge
merged = set1 // set2;
# Recursive merge
merged = lib.recursiveUpdate set1 set2;
# Filter attributes
filtered = lib.filterAttrs (n: v: v != null) attrs;
# Map attributes
mapped = lib.mapAttrs (n: v: v * 2) attrs;
Debugging
Print Values
# Use lib.traceVal for debugging
value = lib.traceVal someExpression;
# Trace with message
value = lib.traceValSeq "message" someExpression;
Evaluate Expressions
# Evaluate Nix expression
nix eval .#nixosConfigurations.hostname.config.services.nginx.enable
# Show derivation
nix show-derivation .#package
# Inspect store path
nix path-info .#package
Common Issues
Hash Mismatch
# Update hash for fetchFromGitHub
nix-prefetch-github owner repo --rev <commit-hash>
# Update vendor hash for Go modules
# Set vendorHash = lib.fakeSha256;
# Build will fail with correct hash
Import Cycles
- Check for circular imports
- Use
lib.mkIfto break cycles - Restructure module organization
Performance
Build Optimization
- Use binary caches
- Avoid rebuilding unnecessarily
- Keep flake.lock updated but stable
- Use
nix-direnvfor development shells
Evaluation Speed
- Minimize use of
import - Use
builtinswisely - Avoid expensive list operations in hot paths
Security
Security-First Approach
NixOS provides unique security advantages through its declarative model and immutable store, but requires active hardening for production systems.
Quick Security Wins
{
# 1. Enable firewall (default deny)
networking.firewall.enable = true;
# 2. Harden SSH
services.openssh.settings = {
PermitRootLogin = "no";
PasswordAuthentication = false;
};
# 3. Automatic security updates
system.autoUpgrade = {
enable = true;
allowReboot = false;
};
# 4. Enable AppArmor
security.apparmor.enable = true;
# 5. Use encrypted secrets
age.secrets.mySecret.file = ../secrets/mySecret.age;
}
Hardened Profile
For security-critical systems, use the hardened profile:
imports = [
<nixpkgs/nixos/modules/profiles/hardened.nix>
];
See workflows/Security.md for comprehensive hardening guidance.
Security Checklist
- Firewall enabled with default deny
- SSH hardened (no root, no passwords)
- Secrets encrypted with agenix
- Regular system updates configured
- AppArmor or systemd service hardening enabled
- Audit logging configured for critical services
- Minimal package installation
- Strong password policies enforced
Resources
- NixOS Manual: https://nixos.org/manual/nixos/stable/
- Nix Package Manual: https://nixos.org/manual/nixpkgs/stable/
- Home-Manager Manual: https://nix-community.github.io/home-manager/
- Nix Pills: https://nixos.org/guides/nix-pills/
Repository-Specific Patterns
For ~/src/home Repository
Adding a New Host
- Create
/systems/<hostname>withboot.nix,hardware.nix - Add to
flake.nixusinglibx.mkHost - Update
globals.nixwith machine metadata - Add to
secrets.nixif using secrets
Adding a New Package
- Create
/pkgs/<package-name>/default.nix - Add to
/pkgs/default.nixwithcallPackage - Test with
nix build .#<package-name>
Common Commands
- Build:
make switch - Format:
make fmt - Clean:
make clean - Deploy:
make host/<hostname>/switch(ask first!)
Remember: Nix is about reproducibility and declarative configuration. When in doubt, consult the manuals and follow the patterns established in the repository.
Examples
Example 1: Creating a Nix package
User: "Package this Go application for Nix"
→ Creates derivation with buildGoModule
→ Adds proper vendorHash for dependencies
→ Includes meta attributes (description, license, etc.)
→ Tests build with nix-build
→ Adds to home-manager or NixOS configuration
→ Result: Application properly packaged for Nix
Example 2: Debugging Nix build issues
User: "My Nix build is failing, can you help?"
→ Reviews error message from nix-build
→ Checks for common issues (missing dependencies, wrong hash)
→ Uses nix-shell for interactive debugging
→ Tests fix incrementally
→ Updates derivation with correct values
→ Result: Build succeeds, issue resolved
Example 3: Using Nix flakes
User: "Convert this to use flakes"
→ Creates flake.nix with inputs and outputs
→ Migrates configuration to flake structure
→ Updates flake.lock with nix flake update
→ Tests with nix build or nix develop
→ Documents flake usage in README
→ Result: Modern Nix flake structure