Problema

En muchos homelabs la gestión de varios servidores con roles diferentes (NAS, hypervisor, contenedores) se vuelve un caos cuando cada máquina tiene su propio conjunto de scripts, paquetes y configuraciones manuales. El resultado típico es:

  • Cambios que se olvidan replicar en otro nodo.
  • Inconsistencias entre versiones de Docker, BTRFS o herramientas de backup.
  • Tiempo invertido en volver a aplicar una configuración después de una reinstalación o un fallo de hardware.

El patrón que se repite es la falta de una fuente única de verdad para la configuración del sistema. Cuando se añaden o quitan servicios, el proceso de propagación es manual y propenso a errores.

Causa

Las causas más frecuentes son:

  1. Repositorios separados por host – Cada máquina tiene su propio árbol de configuración. Cuando se actualiza una opción global (por ejemplo, la política de snapshots en BTRFS) hay que editar varios archivos.
  2. Uso de paquetes imperativos – Instalaciones con apt, dnf o scripts curl | bash crean estado fuera del gestor de paquetes, dificultando la reproducibilidad.
  3. Falta de abstracción de componentes comunes – Sin un módulo que agrupe, por ejemplo, la configuración de SMART o de un contenedor de backup, cada host lo declara de forma independiente.
  4. No versionar la configuración – Cuando la configuración vive en una carpeta local sin control de versiones, cualquier cambio se pierde al reinstalar el nodo.

Solución

Adoptar NixOS Flakes como capa única de definición de infraestructura resuelve los puntos anteriores. Un flake permite:

  • Declarar todos los paquetes, servicios y archivos de configuración en un único repositorio Git.
  • Compartir módulos comunes (common.nix) entre hosts y sobrescribir solo lo necesario en cada configuration.nix.
  • Versionar cada cambio, volver a un commit anterior y reproducir exactamente el mismo estado en cualquier máquina.
  • Integrar Docker mediante virtualisation.docker.enable y definir contenedores con services.docker.containers.

Paso a paso general

  1. Crear el repositorio de flake
    git init homelab-flake && cd homelab-flake
    nix flake init -t templates#simple
    
  2. Definir un módulo común (common.nix) con:
    • Configuración de BTRFS (scrub mensual, snapshots).
    • Política de SMART y alertas.
    • Contenedor de backup a Backblaze (usando docker-compose o docker run).
  3. Crear un flake.nix que exponga cada host. Cada host importa common.nix y añade su propia sección hardware-configuration.nix.
  4. Activar Docker y declarar contenedores dentro de la configuración del host que los necesite.
  5. Construir y aplicar con nixos-rebuild apuntando al flake y al host:
    sudo nixos-rebuild switch --flake .#nas
    
  6. Automatizar la actualización con un cron o un systemd timer que ejecute nix flake update && sudo nixos-rebuild switch --flake .#<host>.

Cuándo aplicar esta solución

  • Múltiples servidores con configuración similar – Cuando al menos dos nodos comparten paquetes, políticas de backup o servicios Docker.
  • Necesidad de reproducibilidad – Si reinstalas hardware con frecuencia o pruebas actualizaciones antes de desplegarlas en producción.
  • Entorno de pruebas y producción – Flakes permiten crear un dev y un prod a partir del mismo código, cambiando solo los valores de entrada.
  • No aplica cuando el entorno es monolítico (un solo servidor) y no se planea escalar; la sobrecarga de NixOS puede no justificar el beneficio.

Código

# flake.nix (esqueleto)
{
  description = "Homelab NixOS Flake con configuraciones compartidas";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, flake-utils, ... }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = import nixpkgs { inherit system; };
        common = import ./common.nix { inherit pkgs; };
      in {
        nixosConfigurations = {
          nas = pkgs.lib.nixosSystem {
            system = "x86_64-linux";
            modules = [
              ./hosts/nas/configuration.nix
              common
            ];
          };
          proxmox = pkgs.lib.nixosSystem {
            system = "x86_64-linux";
            modules = [
              ./hosts/proxmox/configuration.nix
              common
            ];
          };
        };
      });
}
# common.nix (fragmento)
{ pkgs, ... }:
{
  services.btrfs = {
    enable = true;
    scrub = {
      enable = true;
      interval = "monthly";
    };
    snapshots = {
      enable = true;
      keep = 30;
    };
  };

  services.smartd = {
    enable = true;
    devices = [ "/dev/sda" "/dev/sdb" ];
  };

  virtualisation.docker.enable = true;
  services.docker.containers.backblaze = {
    image = "backblaze/b2:latest";
    ports = [ "8080:8080" ];
    volumes = [ "/var/backups:/backups" ];
    restart = "always";
  };
}
# hosts/nas/configuration.nix (solo lo específico)
{ pkgs, ... }:
{
  networking.hostName = "nas";
  fileSystems."/mnt/storage" = {
    device = "/dev/disk/by-id/nvme-XYZ";
    fsType = "btrfs";
    options = [ "defaults" "compress=zstd" ];
  };
  # Servicios adicionales solo para el NAS
  services.nfs.server.enable = true;
  services.samba.enable = true;
}

Verificación

  1. Estado del sistema
    sudo nixos-rebuild switch --flake .#nas
    systemctl status btrfs-scrub.timer
    systemctl status docker
    
  2. Comprobar contenedor de backup
    docker ps | grep backblaze
    curl -s http://localhost:8080/health || echo "Backup container not healthy"
    
  3. Validar BTRFS
    btrfs filesystem df /mnt/storage
    btrfs scrub status /mnt/storage
    
  4. Revisar alertas SMART
    smartctl -a /dev/sda | grep -i "SMART overall-health"
    

Notas adicionales

  • Bloqueo de versiones – Si una actualización de nixpkgs rompe algún contenedor, fija la versión del input con nixpkgs = { url = "github:NixOS/nixpkgs/22.11"; }.
  • Separar credenciales – Usa sops-nix o age para almacenar claves de Backblaze fuera del árbol git y referenciarlas en services.docker.containers.
  • Monitorización ligera – Cockpit funciona bien en contenedores Docker; añádelo al flake como otro contenedor para evitar instalar paquetes extra en el host.
  • Recuperación rápida – Con nixos-rebuild boot --flake .#nas puedes crear una entrada de arranque que use la última configuración probada, útil tras fallos de hardware.