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:
- 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.
- Uso de paquetes imperativos – Instalaciones con
apt,dnfo scriptscurl | bashcrean estado fuera del gestor de paquetes, dificultando la reproducibilidad. - 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.
- 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 cadaconfiguration.nix. - Versionar cada cambio, volver a un commit anterior y reproducir exactamente el mismo estado en cualquier máquina.
- Integrar Docker mediante
virtualisation.docker.enabley definir contenedores conservices.docker.containers.
Paso a paso general
- Crear el repositorio de flake
git init homelab-flake && cd homelab-flake nix flake init -t templates#simple - 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-composeodocker run).
- Crear un
flake.nixque exponga cada host. Cada host importacommon.nixy añade su propia secciónhardware-configuration.nix. - Activar Docker y declarar contenedores dentro de la configuración del host que los necesite.
- Construir y aplicar con
nixos-rebuildapuntando al flake y al host:sudo nixos-rebuild switch --flake .#nas - 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
devy unproda 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
- Estado del sistema
sudo nixos-rebuild switch --flake .#nas systemctl status btrfs-scrub.timer systemctl status docker - Comprobar contenedor de backup
docker ps | grep backblaze curl -s http://localhost:8080/health || echo "Backup container not healthy" - Validar BTRFS
btrfs filesystem df /mnt/storage btrfs scrub status /mnt/storage - Revisar alertas SMART
smartctl -a /dev/sda | grep -i "SMART overall-health"
Notas adicionales
- Bloqueo de versiones – Si una actualización de
nixpkgsrompe algún contenedor, fija la versión del input connixpkgs = { url = "github:NixOS/nixpkgs/22.11"; }. - Separar credenciales – Usa
sops-nixoagepara almacenar claves de Backblaze fuera del árbol git y referenciarlas enservices.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 .#naspuedes crear una entrada de arranque que use la última configuración probada, útil tras fallos de hardware.