NixOS 25.11: Instalación reproducible, cero deriva de configuración y control máximo

¿Te fue útil?

Conoces la sensación: un servidor “funciona mayormente”, salvo que nadie es capaz de explicar por qué. La regla del firewall se cambió “temporalmente” el trimestre pasado, se editó un parámetro del kernel a las 2 a. m. y el esquema de particiones es una sorpresa esperando ocurrir en la próxima caída.

NixOS es el antídoto si lo tratas como un sistema operativo, no como un hobby. Esta es una instalación orientada a producción de NixOS 25.11 enfocada en reproducibilidad, mínima deriva y el tipo de control que solo valoras después de depurar un cargador de arranque roto en un centro de datos frío.

Qué estás construyendo (y qué no)

Estás construyendo una máquina que puede reconstruirse. No “reinstalarse con notas.” Reconstruirse. Desde un repositorio. Bajo demanda. Con rastro de auditoría.

Eso significa tres no negociables:

  • Estado del sistema declarativo: servicios, usuarios, paquetes, red, sistemas de archivos, parámetros del kernel.
  • Cambios atómicos: no “aplicas parches”; construyes una nueva generación del sistema y cambias a ella.
  • Reversión como característica de primera clase: porque producción ama las sorpresas y a NixOS no le gustan.

Lo que no estás construyendo: una caja única “especial” ajustada con ediciones manuales en /etc y un montón de historial de shell. Si quieres esa vida, cualquier otra distribución Linux te lo permitirá con gusto.

Además, pongamos una cosa clara: NixOS no te va a salvar de decisiones malas. Lo que sí hará es preservarlas perfectamente y de forma reproducible. Eso es una característica, no una amenaza.

Datos interesantes y contexto (por qué existe esto)

NixOS no surgió porque alguien quisiera un nuevo sistema init para discutir. Es el resultado práctico de tratar el despliegue de software como un problema de construcción funcional.

8 hechos breves que vale la pena conocer

  1. Nix precede a los contenedores como herramienta operativa mainstream. Las ideas del gestor de paquetes Nix se remontan a investigaciones de los años 2000 sobre despliegue puramente funcional.
  2. /nix/store está diseñado casi como direccionamiento por contenido. Las rutas en el store incluyen hashes para que puedas instalar varias versiones y variantes lado a lado sin colisiones de archivos.
  3. NixOS hace que las actualizaciones del sistema sean transaccionales. Las nuevas configuraciones construyen un nuevo “cierre del sistema” y se generan entradas de arranque para cada generación.
  4. La reversión está integrada en el menú de arranque. Puedes arrancar la última versión conocida buena incluso cuando la red está caída y SSH es un recuerdo.
  5. Las configuraciones son código, no un montón de archivos antiparámetros de diff. Por eso NixOS es implacablemente bueno en coherencia de flotas.
  6. Nixpkgs es una de las colecciones de paquetes más grandes. No porque sea una moda, sino porque el aislamiento de construcción de Nix escala de forma mantenible.
  7. “SO declarativo” no lo inventó NixOS, pero NixOS lo operacionalizó. Puedes encontrar ecos en herramientas como cfengine/puppet/chef, pero NixOS hace declarativo al propio sistema operativo.
  8. Las generaciones son baratas en comparación con las imágenes tradicionales. Mantienes múltiples estados del sistema sin clonar discos completos, porque el store desduplica contenido.

Una cita (idea parafraseada): El trabajo sobre fiabilidad de Gene Kim empuja repetidamente el mismo principio: haz cambios pequeños, reversibles y rutinarios. NixOS incorpora eso en el flujo de trabajo del sistema.

Decisiones que importan: discos, arranque, secretos, actualizaciones

Elige tu sistema de archivos como si fueras a atenderlo

Para servidores: quieres recuperación predecible, herramientas sólidas y un modelo de fallas que entiendas. Elige según la comodidad de tu equipo, no por puntos en Internet.

  • ext4: aburrido, estable, suficientemente rápido. Si no necesitas instantáneas a nivel de sistema de archivos, ext4 es la opción “llega a casa sano”.
  • Btrfs: las instantáneas y subvolúmenes son útiles; send/receive ayuda; puede ser fantástico. También: debes respetar sus aristas operativas.
  • ZFS: lo mejor en checksums, snapshots y flujos de replicación. También: te comprometes a la disciplina operativa de ZFS (memoria, comportamiento del ARC, salud del pool).

Esta guía usa root en ZFS como la ruta de “control máximo”, con notas para ext4/Btrfs donde cambien las decisiones.

Solo UEFI, GPT y sin particionado “ingenioso”

Usa UEFI + GPT. Crea una EFI System Partition. Manténlo aburrido. Puedes ser ingenioso después, cuando tengas monitorización, backups y un laboratorio para probar.

Flakes: sí, salvo que tengas una razón estricta para no usarlos

Usa flakes para instalaciones nuevas. No son perfectos, pero estandarizan el fijado de versiones y la composición. La alternativa son “canales”, que es básicamente “confía en mí, está actualizado”.

Secretos: decide ahora, no después

Si vas a desplegar algo más que un portátil personal, tendrás secretos: claves host SSH, certificados TLS, tokens, PSK de Wi‑Fi, claves de API. Decide tu estrategia de secretos antes de ponerlo en producción:

  • Para setups pequeños: archivos encriptados en git + herramientas basadas en age (patrón común), descifrados en tiempo de despliegue.
  • Para entornos corporativos: intégralo con un backend de secretos real y conecta tu configuración NixOS para obtener secretos en tiempo de ejecución.

No almacenes secretos en rutas del store de Nix con permisos de lectura global. El store está diseñado para reproducibilidad, no para confidencialidad.

Actualizaciones: elige una cadencia y escríbela

NixOS facilita las actualizaciones. Eso no es lo mismo que seguro. Necesitas un ritmo:

  • Fija las entradas (flake.lock) y actualiza de forma intencional.
  • Prueba las compilaciones (incluso en el host) antes de cambiar.
  • Mantén siempre una vía de reversión (entradas de arranque + generaciones previas).

Broma #1: La deriva de configuración es como la entropía: no puedes discutir con ella, solo presupuestar para la muerte térmica de tu /etc.

Listas de comprobación / plan paso a paso

Lista de prevuelo (antes de arrancar el ISO)

  • Decide sistema de archivos: ext4 vs Btrfs vs ZFS. Si eliges ZFS, decide nombre del pool y diseño de datasets.
  • Decide arranque: UEFI + systemd-boot está bien para la mayoría. Si necesitas Secure Boot, pláncalo ahora.
  • Decide identidad: hostname, IP estática o DHCP, modelo de acceso SSH, usuarios administrativos y cómo almacenarás la configuración (git).
  • Decide canal de actualizaciones: rama de release estable vs seguimiento. Para servidores, preferir estable + bumps planificados.
  • Decide secretos: repositorio encriptado, gestor de secretos externo o aprovisionamiento manual (último recurso).
  • Decide gestión remota: ¿desplegarás localmente o vía SSH desde un host de construcción?

Lista de instalación (alto nivel)

  1. Arrancar el ISO de NixOS, confirmar que la red funciona.
  2. Particionar discos (GPT + EFI + partición ZFS).
  3. Crear pool ZFS + datasets (o equivalentes ext4/Btrfs).
  4. Montar sistemas de archivos en /mnt.
  5. Generar la configuración inicial de NixOS.
  6. Convertir a estructura basada en flake.
  7. Configurar bootloader, red, usuarios, sshd, hora, locale.
  8. Instalar NixOS, establecer contraseña de root (o deshabilitarla y confiar en llaves SSH).
  9. Reiniciar, validar entradas de arranque, validar importación de ZFS, validar SSH.
  10. Commit del config en git. Trátalo como infraestructura.

Lista post-instalación (primera hora)

  • Confirmar que existen entradas de reversión en el menú de arranque.
  • Confirmar que puedes reconstruir y cambiar sin errores.
  • Configurar monitorización básica (disco, memoria, carga, salud de ZFS).
  • Configurar backups (snapshots + replicación, o backups a nivel de archivo).
  • Decidir cómo se disparan las actualizaciones (manual, timer, impulsadas por CI).

Tareas prácticas: comandos, salida esperada y decisiones

Estas son las tareas que realmente ejecuto. Cada una incluye qué significa la salida y qué decides después. Asume que estás en el entorno del instalador de NixOS a menos que se indique otra cosa.

Task 1: Confirmar que arrancaste en modo UEFI

cr0x@server:~$ ls /sys/firmware/efi
efivars  fw_platform_size

Qué significa: Si ese directorio existe, estás en modo UEFI. Si no existe, arrancaste en BIOS legacy.

Decisión: Si falta UEFI, reinicia y corrige la configuración del firmware. No instales en modo legacy a menos que tengas una restricción muy específica.

Task 2: Identificar discos y su topología

cr0x@server:~$ lsblk -o NAME,SIZE,TYPE,MODEL,SERIAL,FSTYPE,MOUNTPOINTS
NAME        SIZE TYPE MODEL           SERIAL    FSTYPE MOUNTPOINTS
nvme0n1   953.9G disk Samsung SSD     S6ABC123
├─nvme0n1p1   1G part                 vfat   /mnt/boot
└─nvme0n1p2 952.9G part                 zfs_member
sda        3.6T disk ST4000NM         Z4XYZ789

Qué significa: Ves los nombres de dispositivo, tamaños y si ya existe un sistema de archivos. También: verifica que no estés a punto de borrar el disco equivocado.

Decisión: Elige el objetivo de instalación. Si es una configuración de arranque espejada, querrás dos discos. Si es una VM, uno está bien.

Task 3: Comprobar tablas de particiones existentes (y limpiar si procede)

cr0x@server:~$ sudo sgdisk -p /dev/nvme0n1
Disk /dev/nvme0n1: 2000409264 sectors, 953.9 GiB
Sector size (logical/physical): 512/512 bytes
Disk identifier (GUID): 4A5F0B45-7E62-4C6D-A3D2-8D5F7E0C1A1B
Partition table holds up to 128 entries
Number  Start (sector)    End (sector)  Size       Code  Name
   1            2048         2099199   1024.0 MiB  EF00  EFI System
   2         2099200      2000408575   952.9 GiB   BF01  ZFS

Qué significa: GPT está presente. Los códigos de partición EF00 (EFI) y BF01 (ZFS) son lo que queremos.

Decisión: Si ves un diseño viejo y desordenado y estás reinstalando, limpia la etiqueta del disco cuidadosamente (no reutilices medias particiones a menos que también escribas un runbook para el tú del futuro).

Task 4: Particionar un disco (ejemplo de disco único)

cr0x@server:~$ sudo sgdisk --zap-all /dev/nvme0n1
Creating new GPT entries in memory.
GPT data structures destroyed! You may now partition the disk using fdisk or other utilities.

Qué significa: Se eliminan datos GPT/MBR antiguos.

Decisión: Procede solo si estás absolutamente seguro de haber seleccionado el disco correcto.

cr0x@server:~$ sudo sgdisk -n1:1M:+1G -t1:EF00 -c1:"EFI System" /dev/nvme0n1
The operation has completed successfully.
cr0x@server:~$ sudo sgdisk -n2:0:0 -t2:BF01 -c2:"ZFS" /dev/nvme0n1
The operation has completed successfully.

Qué significa: Has creado una ESP de 1 GiB y una partición ZFS que consume el resto.

Decisión: Si planeas hibernación o requisitos especiales de swap, crea esa partición ahora. De lo contrario, manténlo simple y usa swap mediante archivo más tarde (o ninguno, según la carga).

Task 5: Formatear la EFI System Partition

cr0x@server:~$ sudo mkfs.fat -F 32 -n EFI /dev/nvme0n1p1
mkfs.fat 4.2 (2021-01-31)

Qué significa: La ESP es FAT32 y está etiquetada como EFI.

Decisión: Si estás haciendo arranque espejado, formatearás una ESP en cada disco y montarás ambas (o usarás un enfoque de sincronización). Disco único es más simple.

Task 6: Crear el pool ZFS (root en ZFS)

cr0x@server:~$ sudo zpool create -f -o ashift=12 \
  -O atime=off -O compression=zstd -O xattr=sa -O normalization=formD \
  -O mountpoint=none -O acltype=posixacl \
  rpool /dev/nvme0n1p2
cr0x@server:~$ sudo zpool status
  pool: rpool
 state: ONLINE
config:

        NAME           STATE     READ WRITE CKSUM
        rpool          ONLINE       0     0     0
          nvme0n1p2    ONLINE       0     0     0

errors: No known data errors

Qué significa: Pool creado y saludable.

Decisión: Si ves algo distinto de ONLINE con cero errores, detente. Arregla cableado, salud del disco o rutas de dispositivo. No “continúes y esperes.”

Task 7: Crear datasets (root separado, store nix y estado persistente)

cr0x@server:~$ sudo zfs create -o mountpoint=none rpool/local
cr0x@server:~$ sudo zfs create -o mountpoint=none rpool/safe
cr0x@server:~$ sudo zfs create -o mountpoint=legacy rpool/local/root
cr0x@server:~$ sudo zfs create -o mountpoint=legacy -o compression=zstd rpool/local/nix
cr0x@server:~$ sudo zfs create -o mountpoint=legacy rpool/safe/persist
cr0x@server:~$ sudo zfs list -o name,mountpoint,compression
NAME               MOUNTPOINT  COMPRESSION
rpool              none        zstd
rpool/local        none        zstd
rpool/local/nix    legacy      zstd
rpool/local/root   legacy      zstd
rpool/safe         none        zstd
rpool/safe/persist legacy      zstd

Qué significa: Has separado datasets por “puede reconstruirse” (local) frente a “debe sobrevivir a reimágenes” (safe/persist).

Decisión: Pon /nix en su propio dataset. Crece. Tiene churn. Querrás hacer snapshots con distinta cadencia que tu estado persistente.

Task 8: Montar sistemas de archivos para la instalación

cr0x@server:~$ sudo mount -t zfs rpool/local/root /mnt
cr0x@server:~$ sudo mkdir -p /mnt/nix /mnt/persist /mnt/boot
cr0x@server:~$ sudo mount -t zfs rpool/local/nix /mnt/nix
cr0x@server:~$ sudo mount -t zfs rpool/safe/persist /mnt/persist
cr0x@server:~$ sudo mount /dev/nvme0n1p1 /mnt/boot
cr0x@server:~$ mount | head -n 5
rpool/local/root on /mnt type zfs (rw,xattr,posixacl)
rpool/local/nix on /mnt/nix type zfs (rw,xattr,posixacl)
rpool/safe/persist on /mnt/persist type zfs (rw,xattr,posixacl)
/dev/nvme0n1p1 on /mnt/boot type vfat (rw,relatime,fmask=0022,dmask=0022)

Qué significa: El root objetivo está en /mnt, con montajes separados para /nix, /persist y /boot.

Decisión: Si /mnt/boot no está montado, la instalación de systemd-boot producirá silenciosamente un sistema que no arranca. Arréglalo ahora.

Task 9: Generar la configuración inicial

cr0x@server:~$ sudo nixos-generate-config --root /mnt
writing /mnt/etc/nixos/hardware-configuration.nix...
writing /mnt/etc/nixos/configuration.nix...

Qué significa: NixOS detectó hardware y escribió una configuración base.

Decisión: Trata hardware-configuration.nix como mayormente generado por la máquina y estable; mantén la intención humana en módulos separados cuando sea posible.

Task 10: Validar que los montajes ZFS están reflejados en la configuración de hardware

cr0x@server:~$ sudo sed -n '1,200p' /mnt/etc/nixos/hardware-configuration.nix
{ config, lib, pkgs, modulesPath, ... }:

{
  imports = [ (modulesPath + "/installer/scan/not-detected.nix") ];

  boot.supportedFilesystems = [ "zfs" ];

  fileSystems."/" = {
    device = "rpool/local/root";
    fsType = "zfs";
  };

  fileSystems."/nix" = {
    device = "rpool/local/nix";
    fsType = "zfs";
  };

  fileSystems."/persist" = {
    device = "rpool/safe/persist";
    fsType = "zfs";
  };

  fileSystems."/boot" = {
    device = "/dev/disk/by-uuid/ABCD-EF01";
    fsType = "vfat";
  };
}

Qué significa: El instalador codificó los montajes correctamente. La ESP usa UUID, ZFS usa nombres de dataset.

Decisión: Mantén los nombres de datasets ZFS estables. No dependas de /dev/nvme0n1p2 en configuraciones a largo plazo a menos que disfrutes sorpresas post-reinicio.

Task 11: Preverificar la compilación antes de la instalación (capturar errores obvios)

cr0x@server:~$ sudo nixos-install --root /mnt --no-root-passwd --dry-run
building the system configuration...
warning: Git tree '/mnt/etc/nixos' is dirty
dry-run succeeded

Qué significa: La configuración se evalúa y compila. “dirty” está bien por ahora.

Decisión: Si la compilación falla, arréglalo ahora mientras aún estás en el entorno ISO. No “instales y esperes”.

Task 12: Instalar y configurar el sistema

cr0x@server:~$ sudo nixos-install --root /mnt --no-root-passwd
building the system configuration...
installing the boot loader...
setting up /etc...
updating GRUB 2 menu...
installation finished!

Qué significa: NixOS se instaló y escribió entradas del cargador de arranque. (Dependiendo de la configuración, puede que veas systemd-boot en lugar de líneas de GRUB.)

Decisión: Si hay errores en la instalación del bootloader, no reinicies. Inspecciona /mnt/boot y los ajustes del cargador de arranque primero.

Task 13: Antes de reiniciar, verifica que existan entradas de arranque (ruta UEFI/systemd-boot)

cr0x@server:~$ sudo ls -la /mnt/boot/loader/entries | head
total 16
drwxr-xr-x 2 root root 4096 Jan  1 00:10 .
drwxr-xr-x 3 root root 4096 Jan  1 00:10 ..
-rwxr-xr-x 1 root root  420 Jan  1 00:10 nixos-generation-1.conf

Qué significa: Existen entradas de systemd-boot en la ESP.

Decisión: Si el directorio está vacío, tu ESP no se montó durante la instalación. móntala y reinstala el bootloader desde un chroot (o vuelve a ejecutar la instalación con cuidado).

Task 14: Después del reinicio, confirma que estás ejecutando la generación prevista

cr0x@server:~$ sudo nix-env -p /nix/var/nix/profiles/system --list-generations
   1   2026-02-05 00:12:33   (current)

Qué significa: Tienes al menos una generación y es la actual.

Decisión: Si faltan generaciones o cambiar falla después, probablemente tengas una instalación de bootloader rota o un problema de montajes de sistemas de archivos.

Task 15: Confirmar que el pool ZFS se importa correctamente al arrancar

cr0x@server:~$ sudo zpool status -x
all pools are healthy

Qué significa: No hay problemas conocidos en los pools.

Decisión: Si reporta pools degradados, dispositivos fallando o errores de checksum, trátalo como urgente. ZFS dice la verdad; la gente tiende a negociar con ella de todos modos.

Task 16: Validar servicios y rendimiento de arranque rápidamente

cr0x@server:~$ systemd-analyze time
Startup finished in 3.221s (kernel) + 6.483s (userspace) = 9.704s
graphical.target reached after 6.439s in userspace

Qué significa: Un vistazo rápido al tiempo de arranque dividido entre kernel/espacio de usuario.

Decisión: Si userspace es enorme, inspecciona qué unidades están bloqueando. Si el kernel tarda mucho, suele ser firmware, inicialización de almacenamiento o complejidad del initrd.

cr0x@server:~$ systemd-analyze blame | head
3.812s zfs-import-cache.service
2.104s network-online.target
1.442s nix-daemon.service
1.015s systemd-journald.service

Qué significa: Qué unidades tomaron más tiempo.

Decisión: No “optimices” hasta saber si importa. Si network-online.target es lento, revisa DHCP o elimina dependencias innecesarias.

Task 17: Confirmar que SSH es accesible y las llaves están en su lugar

cr0x@server:~$ sudo systemctl status sshd --no-pager
● sshd.service - OpenSSH Daemon
     Loaded: loaded (/etc/systemd/system/sshd.service; enabled; preset: enabled)
     Active: active (running) since Thu 2026-02-05 00:20:14 UTC; 1min ago
       Docs: man:sshd(8)
             man:sshd_config(5)

Qué significa: sshd está habilitado y en ejecución.

Decisión: Si no está activo, no sigas con planes de administración remota. Arregla red/sshd ahora mientras todavía tienes acceso por consola.

Task 18: Comprobar tamaño del cierre y crecimiento del store (planificación de capacidad)

cr0x@server:~$ nix path-info -Sh /run/current-system
/nix/store/2h3...-nixos-system-server-25.11.20260201  1.8G

Qué significa: Tamaño aproximado del cierre del sistema actual.

Decisión: Si tienes restricción de disco, planifica recolección de basura y mantén /nix en su propio dataset o partición.

Una estructura sensata de configuración para NixOS 25.11

Mantén tu configuración legible. El tú del futuro estará cansado y ligeramente molesto. No lo empeores.

Layout mínimo de flake que escala más allá de un host

Estructura recomendada:

  • flake.nix y flake.lock en la raíz del repo
  • hosts/server/configuration.nix para la intención del host
  • modules/ para componentes reutilizables (ssh, monitorización, zfs, usuarios)
  • secrets/ para material encriptado (no almacenado en el store de Nix como texto plano)

Ejemplo de flake (recortado a lo esencial):

cr0x@server:~$ cat /etc/nixos/flake.nix
{
  description = "NixOS fleet";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
  };

  outputs = { self, nixpkgs, ... }:
  let
    system = "x86_64-linux";
  in {
    nixosConfigurations.server = nixpkgs.lib.nixosSystem {
      inherit system;
      modules = [
        ./hosts/server/configuration.nix
      ];
    };
  };
}

Luego la configuración del host importa módulos y fija la intención:

cr0x@server:~$ sed -n '1,220p' /etc/nixos/hosts/server/configuration.nix
{ config, pkgs, ... }:

{
  imports = [
    ../../hardware-configuration.nix
    ../../modules/base.nix
    ../../modules/ssh.nix
    ../../modules/zfs.nix
  ];

  networking.hostName = "server";
  time.timeZone = "UTC";

  users.users.cr0x = {
    isNormalUser = true;
    extraGroups = [ "wheel" ];
    openssh.authorizedKeys.keys = [
      "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... cr0x@laptop"
    ];
  };

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

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

  system.stateVersion = "25.11";
}

Qué evitar: un enorme configuration.nix monolítico con todo desde nginx hasta tipografías y flags experimentales del kernel. Divídelo. Depurarás más rápido.

Permanencia: decide qué sobrevive a las reconstrucciones

Si creaste /persist, úsalo. Almacena el estado que te importa: claves host SSH, estrategia de machine-id, datos de servicios, logs (tal vez) y estado de aplicaciones (definitivamente). El cableado exacto varía por preferencia, pero el principio es estable: separa “reconstruible” de “debe sobrevivir”.

Un enfoque práctico es montar por enlaces (bind-mount) directorios seleccionados en /persist (o usar patrones tipo impermanence). El beneficio es claridad durante la respuesta a incidentes: puedes eliminar el dataset root y mantener el estado intacto.

Configuraciones de Nix que evitan dolor autoinfligido

  • Habilita el daemon (por defecto) y usa builds en sandbox cuando sea posible.
  • Prefiere entradas nixpkgs fijadas y revisa las actualizaciones.
  • Planifica la recolección de basura y la retención de generaciones explícitamente.
cr0x@server:~$ sudo nixos-option nix.gc.automatic
nix.gc.automatic = false

Qué significa: GC no está habilitado por defecto en muchas instalaciones.

Decisión: Para servidores con discos pequeños, habilita GC con una política de retención que no borre tu última generación conocida buena justo antes de necesitarla.

Actualizaciones, reversiones y control de deriva en el mundo real

NixOS te da muchas barandillas de seguridad. Aún así necesitas conducir como si transportaras cristal.

Cómo funciona realmente “cero deriva de configuración”

La deriva ocurre cuando la realidad cambia sin que cambie la fuente de la verdad. NixOS combate esto de dos maneras:

  • La reconstrucción sobrescribe archivos gestionados por la configuración. Eso bloquea que ediciones casuales en /etc persistan.
  • El sistema es un artefacto de build. No “ajustas el estado en vivo”, cambias a una nueva generación.

Pero la deriva puede seguir ocurriendo en directorios de estado (bases de datos, caches, logs), en cambios manuales del firewall, en cargas de módulos del kernel “temporales” y en cambios fuera de banda como ajustes de firmware. Tu trabajo es minimizar las rutas fuera de banda y documentar las pocas que queden.

El patrón de actualización seguro

Este es el patrón que quiero en producción:

  1. Actualizar entradas (flake.lock) intencionalmente.
  2. Compilar el sistema.
  3. Cambiar a él.
  4. Validar servicios críticos.
  5. Mantener la reversión disponible y no ejecutar GC inmediatamente.
cr0x@server:~$ sudo nixos-rebuild build --flake /etc/nixos#server
building the system configuration...
/nix/store/8m7...-nixos-system-server-25.11.20260205

Qué significa: Construiste un nuevo cierre del sistema sin activarlo.

Decisión: Si la compilación falla, no tocaste el sistema en ejecución. Arréglalo antes de cambiar.

cr0x@server:~$ sudo nixos-rebuild switch --flake /etc/nixos#server
building the system configuration...
activating the configuration...
setting up /etc...
reloading user units for cr0x...

Qué significa: La nueva generación está activa. Los servicios pueden haberse reiniciado.

Decisión: Valida las cosas específicas que importan a tu negocio (puertos, salud de API, montajes de almacenamiento). No confíes solo en “switch succeeded”.

Reversión que puedes hacer bajo presión

Hay dos vías de reversión:

  • En el arranque: elige una generación anterior desde el menú del bootloader.
  • Desde el sistema en ejecución: cambia a una generación anterior in situ.
cr0x@server:~$ sudo nix-env -p /nix/var/nix/profiles/system --list-generations
   1   2026-02-05 00:12:33
   2   2026-02-05 01:05:10   (current)

Qué significa: Tienes al menos dos generaciones para elegir.

Decisión: Si solo mantienes una generación, has convertido NixOS en un gestor de paquetes elegante. Conserva varias.

cr0x@server:~$ sudo /nix/var/nix/profiles/system-1-link/bin/switch-to-configuration switch
setting up /etc...
reloading user units for cr0x...

Qué significa: Cambiaste de nuevo a la generación 1.

Decisión: Si el sistema está en mal estado, prefiere la reversión en tiempo de arranque. Restablece más supuestos.

Recolección de basura sin dispararte en el pie

La presión de espacio es real, especialmente en discos root pequeños. Pero el GC agresivo es cómo borras tu único kernel funcional justo antes de un reinicio.

cr0x@server:~$ sudo nix-collect-garbage -d
finding garbage collector roots...
deleting old generations of profile /nix/var/nix/profiles/system
deleting unused links...
0 store paths deleted, 0.00 MiB freed

Qué significa: En este ejemplo, no se liberó nada (probablemente porque las generaciones están fijadas).

Decisión: Antes de habilitar GC automatizado, establece una política sobre cuántas generaciones guardas y por cuánto tiempo. El disco es barato; el tiempo de inactividad no.

Broma #2: “Habilitemos GC diario” es como descubres que tu plan de reversión era, de hecho, una danza interpretativa.

Notas de ingeniería de almacenamiento: ZFS, Btrfs, ext4 y la realidad

El almacenamiento es donde “funcionó en staging” va a morir. La buena noticia: NixOS hace reproducible la configuración de almacenamiento, lo que significa que realmente puedes razonar sobre ella.

Root en ZFS: lo que ganas y lo que pagas

Ganancias: checksums de extremo a extremo, snapshots, propiedades de dataset por subárbol, flujos de replicación y observabilidad excelente (scrubs, contadores de errores, señales claras de fallo).

Costes: Debes planificar uso de memoria, scrubs, monitorización y comportamiento de importación. ZFS es estable, pero también honesto: te dirá que un disco está muriendo antes que tu controlador RAID.

Diseño de datasets: no lo compliques, hazlo útil

El diseño de datasets anterior (rpool/local/root, rpool/local/nix, rpool/safe/persist) está pensado para control operativa:

  • Diferentes cadencias de snapshot: frecuentes para persist, menos frecuentes para nix, quizás ninguna para root.
  • Diferentes reglas de replicación: persist se replica fuera del host; nix es reconstruible y opcional.
  • Radio de blast claro: puedes revertir persist sin revertir el SO y viceversa.

ARC y memoria: qué vigilar

ZFS usará memoria para el caché ARC. Esto suele ser bueno. También puede ser incómodo en VMs con poca memoria o cargas mixtas donde page cache y ARC compiten por espacio.

cr0x@server:~$ cat /proc/meminfo | head
MemTotal:       16342132 kB
MemFree:         2145320 kB
MemAvailable:    8021444 kB
Buffers:          212344 kB
Cached:          4219072 kB

Qué significa: “MemAvailable” es tu margen real. “Cached” incluye caches de sistema de archivos.

Decisión: Si andas justo de RAM y ves presión OOM bajo carga, considera restringir el ARC de ZFS. Hazlo intencionalmente, con medición, no por superstición.

Scrubs: prográmalos como backups—porque son una clase de backup

Los scrubs leen todos los datos y verifican checksums. No reemplazan backups, pero convierten la corrupción silenciosa en alertas ruidosas.

cr0x@server:~$ sudo zpool scrub rpool
cr0x@server:~$ sudo zpool status
  pool: rpool
 state: ONLINE
  scan: scrub in progress since Thu Feb  5 02:11:33 2026
        32.1G scanned at 1.20G/s, 1.02G issued at 39.1M/s, 120G total
        0B repaired, 0.85% done, 0:01:39 to go

Qué significa: El scrub está en curso; el progreso y el throughput son visibles.

Decisión: Si los scrubs muestran bytes reparados o errores de checksum, trátalo como una señal para investigar discos y cableado. No solo borres el error y sigas.

Diferencias Ext4/Btrfs (si no usas ZFS)

Si eliges ext4, tu “control máximo” se desplaza hacia capas superiores: dependerás más de backups, snapshots LVM (tal vez) y actualizaciones cuidadosas.

Si eliges Btrfs, obtienes snapshots y send/receive. La disciplina operativa es distinta: vigila el comportamiento del espacio libre, operaciones de balance y elige un layout de subvolúmenes sensato.

La parte de NixOS es consistente: el estado del SO es declarativo, existen reversiones y la deriva se minimiza. Tu plan de almacenamiento completa el resto de la historia de fiabilidad.

Guía de diagnóstico rápido

Este es el orden de triaje “está lento / no arranca / está inestable” que ahorra tiempo. El objetivo no es exhaustividad; es velocidad y señal.

Primero: confirma qué cambió

  • ¿Cambiaron generaciones? ¿Los inputs cambiaron?
  • ¿Es el sistema en ejecución el que crees que es?
cr0x@server:~$ readlink /run/current-system
/nix/store/8m7...-nixos-system-server-25.11.20260205

Decisión: Si la ruta del store no es la esperada, deja de adivinar. Alinea al equipo en “qué está corriendo”.

Segundo: ¿es arranque, almacenamiento, red o servicio?

Elige la categoría rápido. La mayoría de los fallos son aburridos.

Problemas de arranque e initrd

cr0x@server:~$ journalctl -b -p err --no-pager | tail -n 20
Feb 05 02:20:31 server kernel: zfs: module license 'CDDL' taints kernel.
Feb 05 02:20:33 server systemd[1]: Failed to mount /persist.
Feb 05 02:20:33 server systemd[1]: Dependency failed for Local File Systems.

Decisión: Si fallan montajes, tienes un problema de sistema de archivos/dataset/orden de montaje. No persigas todavía los logs de la aplicación.

Salud del almacenamiento

cr0x@server:~$ sudo zpool status
  pool: rpool
 state: DEGRADED
status: One or more devices could not be opened.
action: Attach the missing device and online it using 'zpool online'.
config:

        NAME           STATE     READ WRITE CKSUM
        rpool          DEGRADED     0     0     0
          nvme0n1p2    ONLINE       0     0     0
          nvme1n1p2    UNAVAIL      0     0     0  cannot open

Decisión: Si ZFS está degradado, tu prioridad es la disponibilidad de dispositivos y la seguridad de los datos. El debugging de rendimiento puede esperar.

Alcance de red

cr0x@server:~$ ip -br a
lo               UNKNOWN        127.0.0.1/8 ::1/128
enp1s0           UP             10.20.30.40/24 fe80::a00:27ff:fe4e:66a1/64

Decisión: Si la interfaz no está UP o no tiene dirección, arregla la configuración de red antes de culpar a DNS, TLS o aplicaciones.

Salud de servicio

cr0x@server:~$ systemctl --failed --no-pager
  UNIT                     LOAD   ACTIVE SUB    DESCRIPTION
● nginx.service             loaded failed failed Nginx Web Server

1 loaded units listed.

Decisión: Las unidades fallidas te dicen dónde concentrarte. Lee los logs de la unidad; no reinicies a ciegas.

Tercero: encuentra el cuello de botella (CPU, IO, memoria o contención de locks)

cr0x@server:~$ top -b -n 1 | head -n 12
top - 02:31:10 up 12 min,  1 user,  load average: 7.12, 6.80, 4.21
Tasks: 168 total,   2 running, 166 sleeping,   0 stopped,   0 zombie
%Cpu(s): 12.4 us,  3.1 sy,  0.0 ni, 82.9 id,  1.4 wa,  0.0 hi,  0.2 si,  0.0 st
MiB Mem :  15959.1 total,   2210.5 free,   5300.8 used,   8447.8 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.   8658.3 avail Mem

Decisión: Si la espera IO es alta, mira el disco. Si la memoria es baja y no hay swap, puedes dirigirte a OOM. Si la CPU está al límite, perfila el servicio o escala.

Errores comunes: síntoma → causa raíz → solución

1) El sistema no arranca después de una instalación “exitosa”

Síntoma: El arranque cae al menú del firmware o “no hay dispositivo arrancable”.

Causa raíz: La ESP no se montó en /mnt/boot durante la instalación, así que las entradas del bootloader se escribieron en el lugar equivocado (o no se escribieron).

Solución: Arranca el ISO, monta root en /mnt, monta la ESP en /mnt/boot y reinstala el bootloader vía nixos-enter o vuelve a ejecutar los pasos de instalación con cuidado.

2) El pool ZFS no se importa al arrancar

Síntoma: Shell de emergencia; errores sobre montar root o pool faltante.

Causa raíz: Falta boot.supportedFilesystems = [ "zfs" ];, nombres de dataset incorrectos o initrd sin los componentes ZFS.

Solución: Confirma que la configuración de hardware incluye soporte ZFS; recompila; si ya está roto, arranca una generación anterior o usa el ISO para importar el pool y arreglar la configuración.

3) “Funcionó, luego tras reiniciar SSH está muerto”

Síntoma: No puedes SSH después de una reconstrucción; la consola muestra que el sistema está arriba.

Causa raíz: Deriva de la configuración de red (DHCP vs estática), reglas de firewall cambiadas de forma declarativa o ajustes de sshd endurecidos sin llaves en su lugar.

Solución: En consola, revisa ip -br a, systemctl status sshd y journalctl -u sshd. Revierte la generación si es necesario, luego corrige la configuración con llaves autorizadas y un despliegue por etapas.

4) El disco se llena inesperadamente en NixOS

Síntoma: /nix crece hasta que el sistema empieza a fallar de maneras extrañas.

Causa raíz: Generaciones antiguas y rutas del store se acumulan; GC no configurado; artefactos de build retenidos.

Solución: Establece una política de GC y retención de generaciones; mueve /nix a su propio dataset/partición; monitorea espacio libre.

5) “nixos-rebuild switch” tarda una eternidad

Síntoma: Las reconstrucciones son lentas; CPU y disco en churn.

Causa raíz: Builds frecuentes desde código fuente, falta de acceso a caches binarios o una configuración inflada que tira demasiadas dependencias.

Solución: Confirma que usas caches binarios, prefiere paquetes estables y evita compilar el mundo en nodos de producción.

6) Secretos filtrados en el Nix store

Síntoma: Un token aparece en /nix/store o puede ser leído por usuarios no previstos.

Causa raíz: Colocaste secretos directamente en expresiones Nix o creaste archivos en tiempo de build que terminan en el store.

Solución: Mueve secretos al aprovisionamiento en tiempo de ejecución (tmpfiles, gestor de secretos externo, archivos encriptados descifrados en activación) y rota credenciales comprometidas.

Tres microhistorias corporativas (dolor, humildad, resultados)

Incidente causado por una suposición errónea: “El nombre de la interfaz será eth0”

En una empresa mediana, un equipo migró una pequeña flota desde la gestión de configuración tradicional a NixOS. Hicieron lo correcto: todo era declarativo, las compilaciones estaban fijadas, las reversiones funcionaban. Limpio.

Luego compraron hardware nuevo. La NIC cambió. Los nombres de interfaz predecibles hicieron su trabajo y la interfaz pasó a llamarse algo como enp129s0f0, no eth0. Su configuración NixOS tenía una sección de red estática referenciando el nombre antiguo. En el banco de pruebas, alguien lo probó con un host y lo arregló manualmente “solo para ponerlo online”. Ese arreglo manual nunca llegó a git.

Durante la ventana de despliegue, la mitad de los servidores arrancaron sin red. El sistema de monitorización mostró una caída parcial. El ingeniero on-call no pudo SSH, que era el fallo completo. Existía acceso por consola, pero no rápido—eran sitios remotos.

La causa raíz no fue NixOS; fue la suposición de que el nombre de interfaz era estable entre hardware. El pecado operativo real fue el arreglo manual en banco que evitó la fuente de verdad. Así es como la deriva se cuela: por urgencia y buenas intenciones.

La solución fue simple y aburrida: emparejar interfaces por MAC en la configuración y dejar de escribir configs que dependan de nombres de dispositivo. La acción de postmortem no fue “probar más”. Fue “no hacer ediciones locales sin commit” y “la configuración de red debe identificarse por identificadores estables”.

Optimización que salió mal: “La recolección de basura agresiva mantendrá los discos limpios”

Otra organización ejecutaba NixOS en nodos de build y algunos servicios internos. Alguien notó que /nix comía espacio en disco. Bien. Habilitaron GC automático diario con una política agresiva de eliminación. El gráfico de espacio libre se veía bien.

Semanas después, una actualización de seguridad requirió reiniciar algunos nodos. Un nodo no volvió bien—un problema de hardware desaceleró la enumeración de dispositivos y la generación más nueva tenía una carrera en tiempo de arranque involucrando una dependencia de red. La solución era arrancar la generación anterior. Excepto que la clausura de la generación anterior había sido recolectada.

Ahora tenían un arranque roto y sin camino de reversión. La ironía fue lo suficientemente aguda como para cortar vidrio: el SO diseñado para reversiones no tenía ninguna porque optimizaron el almacenamiento como si fuera un disco temporal.

La recuperación implicó arrancar media de rescate, restaurar lo suficiente del store desde backups para poder arrancar y luego avanzar con una configuración corregida. Después, cambiaron la política: mantener múltiples generaciones durante una ventana temporal y solo recolectar basura tras una prueba de reinicio exitosa. El uso de disco subió. Los incidentes bajaron.

Práctica aburrida pero correcta que salvó el día: “Compilar, luego cambiar, luego verificar”

Un equipo fintech usaba NixOS para un conjunto de APIs internas. No lo anunciaban; simplemente les gustaba que producción pudiera reconstruirse desde git y que los despliegues fueran previsibles. Su runbook tenía una regla aburrida: siempre ejecutar nixos-rebuild build primero, luego switch, y siempre ejecutar una pequeña suite de verificación después. A nadie le gustaba, pero se mantuvo.

Un día, un ingeniero actualizó entradas fijadas para incluir un arreglo de biblioteca. La compilación tuvo éxito, pero el paso de activación falló porque una unidad systemd cambió el nombre de una opción upstream. Ese fallo ocurrió durante el switch, no tras un reinicio, y fue visible inmediatamente en consola y en logs de despliegue.

Porque habían compilado primero, ya sabían que no era un problema de compilación. Porque cambiaron en una ventana controlada, el radio de blast fue limitado. Porque tenían pasos de verificación, no enviaron accidentalmente un estado medio funcional donde algunos servicios usaban bibliotecas viejas y otros nuevas.

La reversión fue rápida: volvieron a la generación anterior, corrigieron la unidad en código, reconstruyeron, cambiaron, verificaron y siguieron. Sin drama. Nadie fue celebrado. Ese es el punto.

FAQ

1) ¿NixOS es realmente “cero deriva”?

Para las partes gestionadas de forma declarativa, sí: reconstruir aplica el estado declarado. La deriva puede seguir existiendo en estado persistente de aplicaciones, ajustes de firmware y cualquier cosa que cambies fuera de banda.

2) ¿Debo usar flakes en servidores?

Sí. Fijar y reproducir es todo el juego. Flakes dificulta actualizar accidentalmente el mundo porque se movió un canal.

3) ¿Puedo gestionar múltiples máquinas desde un repo?

Ese es uno de los mejores usos de NixOS. Mantén módulos por-host y módulos compartidos. Evita copiar y pegar factorizando roles comunes limpiamente.

4) ¿Necesito ZFS para que NixOS sea reproducible?

No. La reproducibilidad de NixOS trata la configuración del sistema y el Nix store. ZFS añade semánticas fuertes de almacenamiento y snapshots, que son excelentes, pero es opcional.

5) ¿Cuál es la opción de bootloader más simple y segura?

UEFI + systemd-boot es sencillo en muchos sistemas. Si tienes requisitos multi-boot complejos o legacy, GRUB puede ser apropiado. Elige uno, prueba entradas de reversión y documenta.

6) ¿Cómo evito que secretos acaben en el Nix store?

No incluyas secretos en derivaciones Nix. Provisiónalos en tiempo de ejecución (scripts de activación, tmpfiles) o recupéralos desde un gestor de secretos. Rota cualquier secreto que sospeches haya aterrizado en el store.

7) ¿Cuál es la forma correcta de hacer rollbacks en producción?

Mantén múltiples generaciones, asegúrate de que existan entradas de bootloader y prueba arrancar una generación anterior tras un cambio significativo. La reversión debe ser memoria muscular, no teoría.

8) ¿Cómo depuro un servicio fallido después de cambiar de generación?

Empieza con systemctl --failed, luego journalctl -u service. Si la falla es por configuración, revierte inmediatamente y corrige en el código.

9) ¿Puedo hacer despliegues remotos?

Sí, y vale la pena hacerlo cuando confíes en tu canal. La clave es mantener acceso por consola para fallos y evitar cambiar generaciones sin un paso de verificación.

10) ¿Cuál es la monitorización mínima que debería tener?

Espacio en disco (especialmente /nix), presión de memoria, carga, salud de servicios y, si aplica, estado de pools ZFS y resultados de scrubs. Si no lo puedes ver, te harán un page por ello más tarde.

Próximos pasos que deberías hacer realmente

Has instalado NixOS 25.11. Eso fue la parte fácil. Ahora conviértelo en operativa.

  1. Commit en tu repositorio de configuración (incluyendo flake.lock) y trátalo como la fuente de la verdad.
  2. Escribe un runbook de reversión con los comandos exactos y los pasos del menú de arranque para tu hardware.
  3. Establece retención de generaciones y política de GC que preserve la seguridad, no solo el espacio libre.
  4. Decide e implementa el manejo de secretos antes de añadir más servicios.
  5. Añade monitorización básica: disco, memoria, salud de servicios y estado ZFS si aplica.
  6. Prueba una reconstrucción completa en una VM o nodo de repuesto. Si no puedes reconstruir, no posees el sistema.

La reproducibilidad no es una vibra. Es un hábito respaldado por herramientas. NixOS te da la herramienta. El hábito depende de ti.

← Anterior
Pánico por la clave de recuperación de BitLocker: cómo volver a entrar sin dejar Windows inservible
Siguiente →
Revertir un controlador cuando Windows no arranca: el método seguro

Deja un comentario