← Teoría

Práctica 39: NixOS — el sistema operativo declarativo

Bloque: Instalación de SO no habituales (sesión 3 de 5)
Duración estimada: 5 horas (1 sesión completa)
Modalidad: trabajo individual
Requisitos previos: haber hecho la 07 (Ubuntu) y la 11 (consola Ubuntu)
Herramientas: VirtualBox 7.x, ISO minimal de la última NixOS estable (gratis, ~1 GB), conexión a internet
De qué va esta práctica: todos los sistemas operativos que conoces son imperativos: tú vas dando órdenes ("instala esto", "configura aquello", "edita este archivo") y el sistema cambia poco a poco, paso a paso. NixOS es radicalmente distinto: tú escribes un único archivo de texto que describe cómo quieres que sea el sistema (qué paquetes, qué usuarios, qué servicios) y luego le dices "constrúyete así". Si te equivocas, puedes volver atrás en un segundo al estado anterior. Si pides al sistema que sea idéntico al de tu compañero, sale idéntico. Este paradigma se llama declarativo y es la idea más rompedora en sistemas operativos de la última década.
Aviso franco: NixOS no es fácil. La curva es empinada y el lenguaje Nix es raro. Pero el "wow" que vas a vivir cuando rompas el sistema a propósito y vuelvas atrás con un comando vale la sesión entera.
Red del aula: la VM va en adaptador puente contra 192.168.8.0/24. La IP la dará el DHCP del aula. NetworkManager se encarga al arrancar.

Objetivos de la práctica

  • Entender la diferencia entre configuración imperativa (Ubuntu, Windows) y declarativa (NixOS, Terraform, Kubernetes).
  • Conocer las tres ideas centrales de Nix: store inmutable, derivación reproducible, generaciones con rollback atómico.
  • Instalar NixOS desde la ISO minimal usando particionado manual y nixos-install.
  • Leer y editar /etc/nixos/configuration.nix para añadir paquetes, usuarios y servicios.
  • Aplicar cambios con nixos-rebuild switch y observar cómo el sistema entero se construye otra vez.
  • Romper la configuración a propósito y volver atrás con nixos-rebuild switch --rollback o desde GRUB.
  • Usar nix-shell para crear entornos efímeros con herramientas que no quieres instalar de forma permanente.
  • Comparar con Ubuntu: qué te cuesta más, qué te cuesta menos, qué problemas desaparecen.
  • Diagnosticar tres fallos típicos: error de sintaxis en configuration.nix, paquete que no existe, sin red durante el rebuild.
  • Documentar tu configuración final y entregarla como parte de la ficha técnica.

Parte 0 — Conceptos previos (60 min)

0.1 — El problema que NixOS quiere resolver

Imagina la siguiente escena, que pasa todos los días en oficinas de todo el mundo:

  • El servidor de producción lleva tres años funcionando.
  • Nadie recuerda exactamente qué cambios se le han hecho.
  • Hay que "replicarlo" en otro servidor.
  • Resultado: tres semanas de prueba/error, y el nuevo servidor casi es igual al viejo. Casi.

Este problema se llama configuration drift: el estado real del sistema se va alejando con el tiempo de cualquier documentación que tuvieras. Lo causan tres prácticas:

  1. Hacer cambios con apt install sin apuntarlo.
  2. Editar archivos a mano sin control de versiones.
  3. Aplicar parches manuales que el siguiente apt upgrade sobrescribe.

Las soluciones parcheadas al problema son Ansible, Puppet, Chef, Docker… Todas son capas encima de un SO imperativo. NixOS le da la vuelta: el SO entero es el resultado de un archivo declarativo. Si dos máquinas tienen el mismo configuration.nix y la misma versión de Nix, son bit a bit idénticas.

0.2 — Las tres ideas de Nix

1. Store inmutable

Todos los paquetes se instalan en /nix/store/ bajo una ruta que incluye un hash criptográfico. Por ejemplo:

/nix/store/abc123...-firefox-115.0.2/
/nix/store/def456...-firefox-116.0.0/

Los dos Firefox conviven sin pisarse. Nadie sobrescribe nada nunca. Para "actualizar" lo que se hace es cambiar a qué hash apunta el enlace /run/current-system.

2. Derivación reproducible

El hash de cada paquete se calcula a partir de todas sus entradas: fuente, compilador, flags, dependencias, sistema operativo. Si tú y otra persona compiláis el mismo paquete con los mismos parámetros, sale exactamente el mismo hash y los mismos bytes. Esto se llama build reproducible.

3. Generaciones con rollback

Cada vez que aplicas un cambio (nixos-rebuild switch) NixOS crea una nueva generación del sistema. La generación anterior sigue en disco. Si la nueva no arranca o tiene un fallo, eliges la anterior en el GRUB y estás otra vez en el estado de antes. El rollback es atómico: o estás en la generación nueva entera, o en la vieja entera, no en mitad de un upgrade roto.

0.3 — Imperativo vs declarativo, con un ejemplo

Ubuntu (imperativo)NixOS (declarativo)
apt install nginx
systemctl enable nginx
sed -i 's/.../.../g' /etc/nginx/nginx.conf
systemctl restart nginx
ufw allow 80
5 pasos en orden. Si te saltas uno, queda a medias.
services.nginx.enable = true;
services.nginx.virtualHosts."example" = {
  root = "/var/www";
};
networking.firewall.allowedTCPPorts = [ 80 ];
Describes el final. Nix se encarga de cómo.

0.4 — Vocabulario mínimo del mundo Nix

TérminoQué es
Nix (el lenguaje)Lenguaje funcional puro, parecido a JSON con funciones, diseñado solo para describir paquetes y configs.
Nix (el gestor)El programa que evalúa el lenguaje y construye paquetes. Puede instalarse en cualquier Linux/macOS sin NixOS.
NixOSUna distro Linux donde todo el sistema se describe en Nix.
nixpkgsEl repositorio gigante de paquetes (~100.000) escritos en Nix. Equivalente a "Debian main".
ChannelUna versión de nixpkgs (por ejemplo "nixos-24.05" o "nixos-unstable").
GenerationUna versión congelada del sistema, identificable por número.
FlakeFormato moderno (aún opcional) para hacer las builds reproducibles al 100% pinneando dependencias. No lo usaremos hoy.

Con tus palabras: ¿qué problema soluciona NixOS que apt/dnf/pacman no solucionan, y a qué precio?

Parte 1 — Preparar la VM (15 min)

1.1 — Descargar la ISO

Desde https://nixos.org/download, sección "NixOS: the Linux distribution" → ISO image → Minimal ISO image (64-bit Intel/AMD). Aproximadamente 1 GB.

Hay también una ISO gráfica con KDE/GNOME. Usaremos la minimal porque queremos manejar el sistema desde la consola y centrar el aprendizaje en la configuración declarativa.
Apunta la versión exacta que estás descargando (por ejemplo 25.11, 26.05, etc). La vas a necesitar más tarde para rellenar system.stateVersion en tu configuration.nix: ese campo debe coincidir con la versión que usaste al instalar.

1.2 — Crear la VM

ParámetroValor
NombreNixOS-Lab
TipoLinux
VersiónLinux 5.x or later (64-bit)
RAM2048 MB (Nix necesita memoria para evaluar)
CPUs2 vCPU
Disco20 GB VDI dinámico
SistemaHabilitar EFI
RedAdaptador puente (Intel)

Parte 2 — Instalar NixOS (60 min)

2.1 — Arrancar y entrar como root

Arranca la VM. Tras el menú, aparece un prompt como [nixos@nixos:~]$. Estás en un Live ISO. Pasa a root:

sudo -i

2.2 — Particionar (UEFI + GPT)

parted /dev/sda -- mklabel gpt
parted /dev/sda -- mkpart ESP fat32 1MB 512MB
parted /dev/sda -- set 1 esp on
parted /dev/sda -- mkpart primary 512MB 100%

Resultado: /dev/sda1 (EFI) y /dev/sda2 (root).

2.3 — Formatear y montar

mkfs.fat -F32 -n boot /dev/sda1
mkfs.ext4 -L nixos /dev/sda2

mount /dev/disk/by-label/nixos /mnt
mkdir -p /mnt/boot
mount /dev/disk/by-label/boot /mnt/boot

2.4 — Generar la configuración inicial

nixos-generate-config --root /mnt
ls /mnt/etc/nixos/
# verás: configuration.nix  hardware-configuration.nix

Nix ha detectado tu hardware y ha escrito un esqueleto. Vamos a tocarlo:

nano /mnt/etc/nixos/configuration.nix

2.5 — Editar configuration.nix mínimo

Descomenta o añade estas líneas (déjalas similares a esto, dentro del bloque grande):

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

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

  networking.hostName = "nixos-lab";
  networking.networkmanager.enable = true;

  time.timeZone = "Europe/Madrid";
  i18n.defaultLocale = "es_ES.UTF-8";
  console.keyMap = "es";

  users.users.alumno = {
    isNormalUser = true;
    extraGroups = [ "wheel" "networkmanager" ];
    initialPassword = "alumno123";
  };

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

  services.openssh.enable = true;

  # Pon aquí la versión que apuntaste al descargar la ISO.
  # Ejemplo: "25.11", "26.05"... Mírala con `nixos-version` antes de instalar.
  system.stateVersion = "25.11";
}
Cambia el valor de system.stateVersion por la versión que tú descargaste. Si la dejas en una versión distinta a la del ISO, no es un error fatal pero puede activar lógica de migración inadecuada.
Esta sintaxis es Nix language. Los { ... } son atajos, los = son asignaciones, y [ a b c ] son listas (separadas por espacios, no comas). Verlo asusta al principio; en 10 minutos te resulta cómodo.

2.6 — Instalar

nixos-install
# pedirá contraseña de root para el sistema instalado
reboot

(Antes de que arranque el sistema, retira la ISO del slot del DVD en VirtualBox.)

Parte 3 — Primer arranque y entender lo que has hecho (30 min)

Login con el usuario alumno y la contraseña alumno123. Ejecuta:

cat /etc/os-release
nixos-version
ls -l /run/current-system
# es un symlink a /nix/store/HASH-nixos-system-...

readlink /run/current-system
nix-store -q --references /run/current-system | head
ls /nix/store | wc -l

El último número probablemente sea ya 5.000+ entradas. Cada una es un paquete o una "derivación" en el store inmutable.

3.1 — Generaciones

sudo nix-env --list-generations -p /nix/var/nix/profiles/system

Solo deberías ver una, la 1. Vamos a hacer que aparezca la 2.

Parte 4 — Cambiar el sistema, declarativamente (60 min)

4.1 — Añadir paquetes

Edita /etc/nixos/configuration.nix y en environment.systemPackages añade tree, tmux y firefox:

environment.systemPackages = with pkgs; [
  vim
  git
  htop
  tree
  tmux
  firefox
];

Aplica:

sudo nixos-rebuild switch

Esto descarga paquetes, los une en una nueva generación y la activa. Comprueba:

which tree
sudo nix-env --list-generations -p /nix/var/nix/profiles/system
# ahora hay 2 generaciones

4.2 — Habilitar un servicio

Añade Nginx con una página de prueba. En configuration.nix:

services.nginx = {
  enable = true;
  virtualHosts."hello" = {
    default = true;
    root = "/var/www";
  };
};

networking.firewall.allowedTCPPorts = [ 80 ];

system.activationScripts.miweb = ''
  mkdir -p /var/www
  echo "<h1>Hola desde NixOS</h1>" > /var/www/index.html
'';

Reaplica:

sudo nixos-rebuild switch
curl http://localhost/
# debe responder "Hola desde NixOS"

4.3 — Romper algo a propósito

Edita configuration.nix y cambia services.nginx.enable = true; por services.nginx.enable = "si"; (mal tipo).

sudo nixos-rebuild switch

Nix se queja con un error de tipo y no aplica nada. Tu sistema sigue funcionando con la generación anterior intacta. Esto es lo que distingue a un sistema declarativo: errores en la config no rompen el sistema en marcha.

4.4 — Rollback de verdad

Deja Nginx bien otra vez. Aplica. Ahora simula un cambio "malo": quita Nginx, aplica, y luego decide que querías volver atrás:

sudo nixos-rebuild switch --rollback
# vuelves a la generación anterior con un comando

Comprueba con nix-env --list-generations y con un curl localhost/: Nginx vuelve a estar.

¿Qué garantía te da el modelo de generaciones cuando aplicas una actualización del sistema en producción?

Parte 5 — nix-shell: entornos efímeros (45 min)

Una cosa muy potente de Nix es lanzar una shell con paquetes sin instalarlos en el sistema:

nix-shell -p cowsay
cowsay "Hola"
exit
# fuera de la shell, cowsay no existe

Útil para probar herramientas. Y se puede combinar:

nix-shell -p nodejs python3 sqlite
# tienes los tres juntos en una sub-shell

Cuando sales, no queda nada instalado. Cuando vuelves a entrar, sale de la caché.

5.1 — Reproducible: un shell.nix

Crea un archivo shell.nix en tu home:

{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
  buildInputs = [ pkgs.nodejs pkgs.python3 ];
}

Y desde el mismo directorio:

nix-shell

Cualquiera que clone esa carpeta y tenga Nix tendrá exactamente los mismos node y python. Esto es lo que en Python se intenta con venv, en JS con nvm… aquí es nativo.

Parte 6 — Diagnóstico (30 min)

ProblemaTu diagnóstico
nixos-rebuild switch dice "error: undefined variable 'firefoxx'".
El rebuild se queda colgado en "fetching ...".
Has reiniciado y el sistema arranca en una generación antigua sin querer.
El disco se llena de paquetes viejos en /nix/store.
Has añadido un servicio pero no escucha en el puerto.
Pistas:
  1. Has hecho un typo en el nombre del paquete. Búscalo con nix-env -qaP firefox (siempre funciona) o, si tienes flakes activos, nix search nixpkgs firefox.
  2. Sin red. Comprueba ip a y ping 1.1.1.1. Si NetworkManager no está activo, edita configuration.nix y rebuild.
  3. En GRUB elige siempre la generación más reciente (la más arriba). Si quieres marcarla como default, vuelves a hacer nixos-rebuild switch desde ella.
  4. sudo nix-collect-garbage --delete-older-than 7d.
  5. Mira journalctl -u nombre.service y revisa networking.firewall.allowedTCPPorts en la config.

Ficha técnica

Datos del sistema

Tu configuration.nix final (pega lo más relevante)

Reto opcional

Reto: instala el escritorio GNOME añadiendo services.xserver.enable = true; y services.xserver.desktopManager.gnome.enable = true; a tu configuration.nix. Reaplica con nixos-rebuild switch y reinicia. Al volver tendrás un sistema con escritorio gráfico completo. Tarda lo suyo en descargar — déjalo correr.

Autoevaluación

Marca lo conseguido

HitoHecho
VM con UEFI, NixOS minimal arrancado en live
Particionado manual con parted
configuration.nix inicial editado y nixos-install completado
Primer arranque del sistema instalado, login OK
Paquetes añadidos al systemPackages y aplicados
Nginx levantado con página propia en /var/www
He provocado un error de sintaxis y comprobado que NO rompe el sistema
Rollback con --rollback y comprobado vuelta al estado previo
nix-shell probado con paquetes ad hoc
shell.nix propio creado
Ficha técnica completa con tu configuration.nix
Reto: GNOME instalado declarativamente

¿En qué tipo de equipo o servidor te imaginas usando NixOS en serio? ¿Y dónde claramente lo descartarías?