Nix Flakes for Infrastructure as Code
Managing heterogeneous systems with declarative configuration
Nix flakes provide a powerful framework for managing infrastructure as code across heterogeneous systems. In this post, I’ll walk through how I use Nix flakes to manage my infrastructure portfolio using my bertof/nix-dotfiles repository (commit 005a662).
Understanding the Core Concepts
What is Infrastructure as Code?
Infrastructure as Code (IaC) is the practice of managing and provisioning computing infrastructure through machine-readable definition files, rather than physical hardware configuration or interactive configuration tools. With IaC, infrastructure becomes:
- Version-controlled: Changes are tracked in Git
- Reproducible: Identical environments can be recreated
- Automated: Deployment and updates are scripted
- Documented: Configuration serves as documentation
The Nix Package Manager
Nix is a purely functional package manager that treats packages as immutable values built from pure functions. Key concepts:
- Pure: Packages are built in isolation without external dependencies
- Functional: Same inputs always produce same outputs
- Immutable: Once built, packages never change
- Atomic: Updates are atomic - either complete success or complete rollback
The Nix language uses a hashing system where each package’s hash is computed from all its dependencies, ensuring that the same package built on different systems will be bit-for-bit identical.
NixOS - The Declarative Linux Distribution
NixOS is a Linux distribution built on top of the Nix package manager that extends the functional approach to the entire operating system:
- Declarative Configuration: The entire system configuration is defined in
/etc/nixos/configuration.nix - Atomic Updates: System updates are atomic - you can roll back to any previous configuration
- Reproducible: The same configuration file will produce the same system state
- Multi-user Safe: Users can install packages without affecting the system
What are Nix Flakes?
Nix flakes are an experimental feature that standardizes and improves the Nix experience by:
- Lock Files: Automatically generating
flake.lockfiles that pin all dependencies - Standardized Interface: Consistent input/output interface for all flakes
- Improved Composability: Flakes can depend on other flakes cleanly
- Remote Building: Support for building on remote machines
- Better CLI: Simplified commands like
nix build,nix develop,nix run
Flakes solve the reproducibility problem by ensuring that all external dependencies are pinned to specific versions, making infrastructure configurations truly reproducible across different machines and over time.
A Simple NixOS Configuration Example
Here’s a simplified example of a NixOS configuration using flakes from my repository:
# flake.nix fragment
{
outputs = { self, nixpkgs, ... }@inputs: {
nixosConfigurations.thor = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
# Hardware-specific modules
inputs.nixos-hardware.nixosModules.common-cpu-amd
inputs.nixos-hardware.nixosModules.common-pc-ssd
# Base system configuration
./nixos/basics
./instances/thor/hardware-configuration.nix
./instances/thor/configuration.nix
];
};
};
}
Repository Structure Overview
My nix-dotfiles repository follows a modular architecture designed for managing heterogeneous infrastructure across desktop workstations, servers, and laptops. The structure emphasizes reusability and separation of concerns:
├── flake.nix # Main flake definition orchestrating all configurations
├── hosts.nix # Network host/IP definitions for DNS and VPN management
├── instances/ # System-specific configurations
│ ├── thor/ # AMD desktop workstation with gaming capabilities
│ ├── odin/ # Intel laptop/server hybrid with Home Assistant
│ ├── heimdall/ # Main home server running multiple services
│ └── baldur/ # Remote server for infrastructure monitoring
├── nixos/ # Reusable NixOS modules shared across systems
│ ├── basics/ # Common system settings (locales, networking, users)
│ ├── server/ # Server-specific optimizations and services
│ └── rice.nix # Desktop theming and graphical environment
└── hm/ # Home Manager modules for user-level configuration
├── development/ # Development tools categorized by language/stack
└── combined/ # Combined configurations for different user profiles
Each folder serves a specific purpose in the infrastructure management hierarchy. The instances/ directory contains hardware-specific configurations for individual machines, while nixos/ provides reusable modules that can be composed across different systems. The hm/ directory manages user-level applications and dotfiles through Home Manager, organized by functional domains and individual user preferences. This structure enables sharing common configurations while maintaining machine-specific customizations.
Flake Structure Deep Dive
The flake.nix file orchestrates the entire infrastructure:
Inputs Section
The inputs section defines external dependencies including NixOS/nixpkgs channels, Home Manager for user-level configuration, nixos-hardware for hardware-specific modules, nixos-generators for creating system images, and nix-systems for multi-architecture support.
inputs = {
# Nixpkgs channels
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
nixpkgs-s.url = "github:NixOS/nixpkgs/release-25.11";
# Home Manager
home-manager.url = "github:nix-community/home-manager";
# External modules
nixos-hardware.url = "github:NixOS/nixos-hardware";
nixos-generators.url = "github:nix-community/nixos-generators";
systems.url = "github:nix-systems/default";
};
Outputs Section - The Core Configuration
The outputs section defines all system configurations using flake-parts for modularity:
# Modular system configuration
nixosConfigurations = {
thor = inputs.nixpkgs-s.lib.nixosSystem {
system = "x86_64-linux";
modules = [
# Hardware modules
inputs.nixos-hardware.nixosModules.common-cpu-amd
inputs.nixos-hardware.nixosModules.common-pc-ssd
# Reusable modules
self.nixosModules.commonModules
self.nixosModules.mainModules
# Instance-specific configuration
./instances/thor/hardware-configuration.nix
./instances/thor/configuration.nix
];
};
odin = inputs.nixpkgs-s.lib.nixosSystem {
system = "x86_64-linux";
modules = [
# Intel laptop hardware
inputs.nixos-hardware.nixosModules.common-cpu-intel
inputs.nixos-hardware.nixosModules.common-pc-laptop
# Server configuration with cloud storage
self.nixosModules.commonModules
./nixos/server
self.nixosModules.bertof-rclone
./instances/odin/hardware-configuration.nix
./instances/odin/configuration.nix
];
};
};
Benefits of This Approach
- Reproducibility: Every system build is identical regardless of when or where it’s built
- Modularity: Share common configurations across different system types
- Version Control: All configurations are in Git with full history
- Declarative: System state is defined, not prescribed
- Multi-Architecture: Supports x86_64 and ARM64 systems seamlessly
Deploying Changes
To deploy changes to a specific system:
# Build and switch to new configuration
sudo nixos-rebuild switch --flake .#thor
# Build remote system configuration
nixos-rebuild switch --target-host thor --flake .#thor
# Generate installation images
nix build .#install-iso
Automatic Updates
Each configured device automatically updates daily, ensuring that any changes made to the repository are automatically deployed. The automation pipeline:
- Daily Cron Job: Systems run
nixos-rebuild switch --flake github:bertof/nix-dotfiles#<hostname>daily - Commit Monitoring: When changes are pushed to the repository, affected systems pull updates
- Rollback Safety: Failed updates automatically roll back to previous working configuration
- Health Checks: Systems verify service availability after updates
This automated approach ensures infrastructure consistency while maintaining the ability to manually apply specific configurations when needed.
Conclusion
Nix flakes provide a robust foundation for infrastructure as code that scales from single desktop systems to complex server environments. The declarative nature ensures consistency across deployments while maintaining flexibility for heterogeneous hardware configurations.
My repository demonstrates how to organize complex infrastructure configurations in a maintainable way, leveraging Nix’s powerful package management and reproducibility guarantees.
You can explore the complete configuration at bertof/nix-dotfiles on GitLab (commit 005a662).