NixOS for Homelab: Declarative System Configuration

Discover how NixOS transforms homelab management with its declarative approach, offering reproducibility, atomic updates, and instant rollbacks for your infrastructure.

• 9 min read
nixoshomelabinfrastructure-as-codelinuxdevops
NixOS for Homelab: Declarative System Configuration

If you’ve ever spent hours debugging a server only to realize you made a manual configuration change six months ago and forgot about it, NixOS might be the solution you didn’t know you needed.

For homelab enthusiasts tired of configuration drift, unreproducible setups, and the dreaded “but it works on my machine” phenomenon, NixOS offers a fundamentally different approach: your entire system—packages, services, configurations, even the bootloader—is defined in code.

The Problem with Traditional Linux

Traditional Linux distributions follow an imperative model. You install packages with apt install or pacman -S. You edit configuration files in /etc/. You run commands to enable services. Over time, your system accumulates changes that exist nowhere but on that specific machine.

When something breaks, you’re left wondering: What did I change? Was it that package update last week? Did I modify a config file manually six months ago?

The homelab community often turns to tools like Ansible or Docker Compose to manage infrastructure. These help, but they manage applications or configuration state—not the entire system. The underlying operating system still drifts.

What Makes NixOS Different

NixOS treats your entire system as a single declarative configuration file. Rather than running commands to change your system state, you describe your desired state and let NixOS figure out how to achieve it.

This approach brings several key benefits:

Reproducibility: The same configuration file produces identical systems. Spin up a new server, apply your config, and you get an exact replica—every service, package, and setting identical to your original setup.

Atomic Updates: System changes are atomic. Either the entire update succeeds, or nothing changes. No more half-applied updates leaving your system in an inconsistent state.

Instant Rollbacks: Every system change creates a new “generation.” If an update breaks something, reboot and select the previous generation from the boot menu. Your system is back to working order in seconds.

Version Control Everything: Since your entire system is defined in configuration files, you can store those files in Git. Every change is tracked, every decision documented. Need to know why you enabled a particular service? Check the commit history.

The Nix Language: A Brief Introduction

NixOS configurations are written in the Nix language—a purely functional, lazily evaluated language designed specifically for describing software builds and configurations.

Don’t let “functional language” scare you. For homelab use, you’ll primarily work with attribute sets (similar to JSON objects) and basic functions:

# An attribute set (think: JSON object)
{
  services.nginx.enable = true;
  services.nginx.virtualHosts."localhost" = {
    locations."/" = {
      root = "/var/www/html";
    };
  };
}

The Nix language’s pure functional nature means functions always produce the same output for the same input—essential for reproducibility. If you define a service with certain settings today and rebuild in six months, you’ll get the exact same result.

Flakes: Modern Configuration Management

While NixOS can be configured without them, flakes have become the modern standard for managing NixOS systems. Flakes provide explicit dependency management, version pinning, and better reproducibility.

A flake is defined in a flake.nix file, which specifies:

  • Inputs: Dependencies like nixpkgs (the package repository) and home-manager (user configuration)
  • Outputs: Your system configurations
{
  description = "My Homelab NixOS Configuration";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
    home-manager = {
      url = "github:nix-community/home-manager/release-24.05";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = { self, nixpkgs, home-manager, ... }: {
    nixosConfigurations.homelab = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      modules = [
        ./configuration.nix
        home-manager.nixosModules.home-manager
      ];
    };
  };
}

The accompanying flake.lock file pins exact versions of all dependencies. This solves the “works on my machine” problem—if you share your flake, anyone can reproduce your exact setup.

Home Manager: User-Space Configuration

NixOS manages system-level configuration beautifully, but what about your personal dotfiles, shell configurations, and user-specific packages? That’s where home-manager comes in.

Home Manager extends NixOS’s declarative approach to your user environment:

{ config, pkgs, ... }:

{
  home.username = "myuser";
  home.homeDirectory = "/home/myuser";
  home.stateVersion = "24.05";

  # Personal packages
  home.packages = with pkgs; [
    neovim
    ripgrep
    fzf
    btop
  ];

  # Git configuration
  programs.git = {
    enable = true;
    userName = "Your Name";
    userEmail = "[email protected]";
    extraConfig.init.defaultBranch = "main";
  };

  # Shell configuration
  programs.bash = {
    enable = true;
    shellAliases = {
      ll = "ls -la";
      ".." = "cd ..";
      update = "sudo nixos-rebuild switch --flake .#homelab";
    };
  };
}

With home-manager, your entire development environment—editor configs, shell preferences, installed tools—becomes as reproducible as your system configuration.

Homelab Examples: Putting It All Together

Let’s walk through a practical homelab setup. Imagine you want to run a web server, a database, and a few containers.

Basic System Configuration

# configuration.nix
{ config, pkgs, ... }:

{
  imports = [
    /etc/nixos/hardware-configuration.nix
  ];

  # Bootloader
  boot.loader.systemd-boot.enable = true;
  boot.loader.efi.canTouchEfiVariables = true;

  # Networking
  networking.hostName = "homelab-server";
  networking.firewall.enable = true;
  networking.firewall.allowedTCPPorts = [ 22 80 443 ];

  # Essential packages
  environment.systemPackages = with pkgs; [
    vim
    git
    htop
    tmux
    curl
  ];

  # SSH access
  services.openssh = {
    enable = true;
    settings.PasswordAuthentication = false;
    settings.PermitRootLogin = "no";
  };

  # Automatic updates
  system.autoUpgrade = {
    enable = true;
    allowReboot = false;
  };

  # Garbage collection
  nix.gc = {
    automatic = true;
    dates = "weekly";
    options = "--delete-older-than 7d";
  };

  # Enable flakes
  nix.settings.experimental-features = [ "nix-command" "flakes" ];
}

Running Services

NixOS includes service modules for most common applications. Enabling a service is often just a few lines:

# PostgreSQL database
services.postgresql = {
  enable = true;
  package = pkgs.postgresql_15;
  ensureDatabases = [ "myapp" ];
  ensureUsers = [
    {
      name = "myapp";
      ensureDBOwnership = true;
    }
  ];
};

# Nginx reverse proxy
services.nginx = {
  enable = true;
  recommendedProxySettings = true;
  virtualHosts."service.local" = {
    locations."/" = {
      proxyPass = "http://127.0.0.1:8080";
    };
  };
};

# Jellyfin media server
services.jellyfin = {
  enable = true;
  openFirewall = true;
};

Container Management

For applications without native NixOS modules, Docker containers integrate cleanly:

virtualisation.docker.enable = true;

virtualisation.oci-containers.containers = {
  pihole = {
    image = "pihole/pihole:latest";
    ports = [ "53:53/tcp" "53:53/udp" "80:80" ];
    volumes = [
      "/etc/pihole:/etc/pihole"
      "/etc/dnsmasq.d:/etc/dnsmasq.d"
    ];
    environment = {
      TZ = "America/New_York";
    };
  };
};

Even better, tools like compose2nix can convert your existing Docker Compose files into native NixOS container definitions, letting you manage containers declaratively.

Rebuilding Your System

After modifying your configuration, apply changes with:

sudo nixos-rebuild switch --flake .#homelab

This command:

  1. Evaluates your configuration
  2. Builds a new system generation
  3. Activates the new generation
  4. Creates a boot entry for rollback

If something goes wrong, reboot and select the previous generation. No panic, no manual restoration.

Migration Guide: From Traditional Linux to NixOS

Transitioning to NixOS requires a mindset shift, but the process can be gradual.

1. Document Your Current Setup

Before migrating, inventory your existing system:

# On Debian/Ubuntu
dpkg -l > packages.txt

# On Arch Linux
pacman -Qe > packages.txt

# On RHEL/Fedora
rpm -qa > packages.txt

2. Start with a VM or Secondary Machine

Don’t migrate production directly. Create a NixOS VM or use a secondary machine to learn the ropes. The learning curve is real, but manageable with practice.

3. Begin with Minimal Configuration

{ config, pkgs, ... }:

{
  boot.loader.grub.enable = true;
  networking.hostName = "my-nixos";
  services.openssh.enable = true;

  environment.systemPackages = with pkgs; [
    vim
    git
    curl
  ];

  users.users.myuser = {
    isNormalUser = true;
    extraGroups = [ "wheel" ];
  };
}

Start simple. Add complexity incrementally.

4. Add Services One at a Time

Migrate services gradually. Enable one service, test it, then add the next. This approach makes troubleshooting easier and helps you learn NixOS service configuration patterns.

5. Adopt Version Control Early

From day one, store your configuration in Git:

cd /etc/nixos
git init
git add .
git commit -m "Initial NixOS configuration"

Every change should be a commit. This practice proves invaluable when debugging.

Comparison: NixOS vs Ansible vs Docker Compose

How does NixOS stack up against other infrastructure tools? The answer depends on your needs.

AspectNixOSAnsibleDocker Compose
ScopeEntire OSMulti-host configContainer apps
ReproducibilityHighest (bit-for-bit)High (idempotent)Medium
RollbackInstant, built-inManualManual
Learning CurveSteepModerateLow
Multi-hostColmena/nixos-anywhereNativeSwarm/K8s required
StateConfig = stateExternal scriptsdocker-compose.yml

When to Choose NixOS

  • You want complete system reproducibility
  • You manage single or few machines (homelab scale)
  • You value atomic updates and rollbacks
  • You’re willing to invest in learning

When to Choose Ansible

  • Managing heterogeneous infrastructure (different OS types)
  • Multi-server deployments across many hosts
  • Team familiarity with YAML/Python
  • Managing existing non-NixOS systems

When to Choose Docker Compose

  • Application isolation is primary goal
  • Quick container orchestration needed
  • Team is familiar with Docker ecosystem
  • Portability across environments

The Hybrid Approach

You’re not forced to choose exclusively. Many homelabs run NixOS as the host system with Docker Compose for specific applications, combining system-level reproducibility with container flexibility.

# NixOS manages the host
virtualisation.docker.enable = true;
environment.systemPackages = with pkgs; [ docker-compose ];

# Docker Compose manages applications
# (Run compose files normally)

The Trade-Off: Learning Curve vs. Long-Term Gain

Let’s be honest: NixOS has a steeper learning curve than traditional Linux distributions. The Nix language is unlike anything most administrators have encountered. Concepts like derivations, the Nix store, and flakes require new mental models.

But here’s the payoff: Once you’ve invested that learning time, maintaining your homelab becomes trivial. Need to rebuild a server? One command spins up an identical system. Want to try a risky configuration change? Apply it, test, and if something breaks, rollback is one reboot away.

The initial time investment pays dividends over years of homelab management.

Common Pitfalls for Newcomers

Learning NixOS comes with some common mistakes:

1. Trying to Edit /etc Files Manually

In NixOS, /etc files are generated from your configuration. Edit configuration.nix instead.

2. Ignoring Home Manager

Users often put personal packages and configs in system configuration. Use home-manager for user-space settings.

3. Skipping Flakes

Legacy NixOS guides often discuss older patterns. Start with flakes from the beginning—they’re the modern standard.

4. Not Using Version Control

Every configuration should be in Git. If you’re editing files without commits, you’re missing the benefit.

5. Over-Engineering Early

Start with a simple configuration. Refactor into modules as you learn. Don’t try to build the perfect structure immediately.

Getting Started

Ready to try NixOS? Here’s a minimal path forward:

  1. Download the ISO: Get the minimal ISO from nixos.org
  2. Create a VM: Use VirtualBox, Proxmox, or your preferred virtualization platform
  3. Follow the Manual: The NixOS manual provides installation instructions
  4. Start Small: Configure a basic system with SSH access first
  5. Add Services: Enable one service at a time, testing after each
  6. Learn Flakes: Once comfortable, transition to a flake-based setup

The NixOS & Flakes book provides an excellent introduction to flakes once you’ve got the basics down.

Is NixOS Right for Your Homelab?

NixOS isn’t for everyone. If you prefer quick setup over long-term maintainability, or if you’re uncomfortable with learning new paradigms, traditional distributions may serve you better.

But if you:

  • Have experienced configuration drift
  • Want exact reproducibility across machines
  • Value version-controlled infrastructure
  • Appreciate atomic updates and instant rollbacks
  • Are willing to invest in learning

Then NixOS might be exactly what your homelab needs.

The ecosystem has matured significantly. Flakes are stable. Home-manager is well-maintained. Community documentation has improved. There’s never been a better time to dive in.

Your homelab deserves infrastructure you can rebuild from a single configuration file. NixOS makes that possible.

Anthony Lattanzio

Anthony Lattanzio

Tech Enthusiast & Builder

I'm a tech enthusiast who loves building things with hardware and software. By night, I run a homelab that's grown way beyond what any reasonable person needs. Check out about me for more.

Comments

Powered by GitHub Discussions