Entornos reproducibles con Nix + Docker en GNU/Linux

Uno de los grandes retos en desarrollo y operaciones es la fiabilidad. ¿Cuántas veces hemos oído aquello de “en mi máquina funciona”? Aunque Docker ayudó mucho a estandarizar entornos, todavía existen problemas: imágenes demasiado grandes, dependencias que cambian con el tiempo o builds que no son totalmente predecibles.

Aquí entra en juego Nix, un gestor de paquetes y sistema de construcción declarativo. Su propuesta: cada build es puramente funcional, es decir, depende sólo de lo que declares, no del estado del sistema. Si combinamos Nix con Docker obtenemos imágenes pequeñas, reproducibles y fáciles de mantener.

En esta entrada vamos a ver qué es Nix, por qué puede ser útil junto a Docker y algún ejemplo práctico.

Miniatura con los logs de Nix y Docker

Un poco de historia sobre Nix

Nix fue creado en 2003 por Eelco Dolstra, como parte de su investigación doctoral en la Universidad de Utrecht (Países Bajos).

Su tesis se tituló “The Purely Functional Software Deployment Model”, donde propuso aplicar principios funcionales al despliegue de software: cada paquete se construye de forma aislada y con todas sus dependencias explícitas.

Sobre su historia de desarollo, podemos decir que las primeras versiones aparecieron en el año 2004. Más adelante apareció una distribución completa GNU/Linux, llamada NixOS, basada en Nix. Además, esta distribución es similar a una de la que ya hemos hablado en la web, llamada Guix.

A nivel de licencia, es software libre, ya que utiliza una licencia Licencia LGPL 2.1

Actualmente, cómo esta montado Nix

  • Gestor de paquetes funcional: cada paquete se construye en un entorno aislado, sin depender del resto del sistema.
  • Declarativo: describes qué quieres en lugar de instalar manualmente.
  • Reproducible: si hoy construyes un paquete y mañana lo vuelve a hacer otra persona con el mismo nixpkgs, el resultado será idéntico.
  • Aislado: múltiples versiones de la misma librería pueden coexistir.

Como he explicado antes, Nix no sólo es un gestor de paquetes, también es la base de NixOS, una distribución entera declarativa. En cualquier caso, podemos usar Nix en cualquier GNU/Linux, sin cambiar de distro.

¿Por qué es buena idea usar Nix en Docker?

Docker te da portabilidad, pero:

  • Las imágenes pueden crecer hasta varios GB.
  • Los Dockerfile suelen incluir múltiples pasos manuales, con riesgo de drift.
  • Reproducibilidad no siempre garantizada: apt-get puede instalar versiones distintas con el tiempo.

Pero con Nix:

  • Construyes los paquetes de forma declarativa.
  • Generas imágenes Docker con capas mínimas.
  • Garantizas que hoy y dentro de 6 meses, el resultado será el mismo.

Instalación de Nix y algunos ejemplo

Antes de hacer la instalación, si estáis con poco sin espacio en disco, os recomiendo, yo lo he hecho así, montar un punto de montaje que tire de un NFS, en mi caso de una Synology, que tengo por casa. Ese recurso debe montar sobre /nix/store, que es donde guardará, en la instalación, todos los paquetes.

En cualquier GNU/Linux moderno debemos escribir:

sh <(curl -L https://nixos.org/nix/install) --daemon

La instalación puede tardar un rato, en el caso de mi RaspberryPi, casi media hora. Ya que debe bajar ficheros, crear usuarios, etcétera.

Reinicia la shell y comprueba:

nix --version

Si en algún momento queremos desinstalar, podemos seguir estas instrucciones: Uninstalling Nix

Es importante comentar, que debemos habilitar el servicio en el arranque y encenderlo:

sudo systemctl enable nix-daemon.service
sudo systemctl start nix-daemon.service

Un ejemplo: desplegar una imagen de Nginx

Creamos un archivo default.nix:

{ pkgs ? import <nixpkgs> {} }:

pkgs.dockerTools.buildImage {
  name = "nginx-nix";
  tag = "latest";
 
  copyToRoot = pkgs.buildEnv {
    name = "image-root";
    paths = [
      pkgs.nginx
      pkgs.busybox
      (pkgs.runCommand "extra-files" {} ''
        mkdir -p $out/etc $out/var/log/nginx $out/tmp
        chmod 1777 $out/tmp
        echo "nobody:x:65534:65534:nobody:/:" > $out/etc/passwd
        echo "nogroup:x:65534:" > $out/etc/group
        echo '#!/bin/sh' > $out/entrypoint.sh
        echo 'exec nginx -g "daemon off;"' >> $out/entrypoint.sh
        chmod +x $out/entrypoint.sh
      '')
    ];
  };
 
  config = {
    Entrypoint = [ "/entrypoint.sh" ];
    ExposedPorts = { "80/tcp" = {}; };
  };
}

Construimos la imagen:

nix-build default.nix

La salida indicará la ruta donde está la imagen (./result). Importamos a Docker:

docker load < $(readlink -f result)

Por cierto, la imagen generada es realmente pequeña, apenas 50 MB

Lanzamos el contenedor:

docker run -p 8083:80 nginx-nix:latest

Ahora si accedemos a http://localhost:8080 deberamos ver la página por defecto de Nginx.

Unos cuantos apuntes comparando Nix con Dockerfile

Cuando trabajamos con un Dockerfile clásico, solemos instalar paquetes usando apt-get o gestores similares. Esto implica que la construcción de la imagen depende del estado actual de los repositorios en el momento de la instalación. Con el tiempo, esos paquetes pueden actualizarse, desaparecer o cambiar de versión, lo que hace que la imagen generada no siempre sea idéntica y pueda variar entre compilaciones.

Con Nix + Docker, en cambio, se describe de forma declarativa, es decir, el contenido es exacto de la imagen. Cada dependencia se identifica mediante hashes y derivaciones inmutables, lo que garantiza que el resultado sea siempre el mismo, sin importar cuándo o dónde se construya. El resultado es más ligero, consistente y, sobre todo, totalmente reproducible. Además, esta aproximación permite construir imágenes multi-stage sin perder esa reproducibilidad, incluir aplicaciones complejas con todas sus dependencias fijadas, usar nix develop para asegurar que todo el equipo trabaje con entornos idénticos y, finalmente, integrarlo en pipelines de CI/CD para builds.

A modo de resumen

Combinar Nix y Docker abre la puerta a entornos realmente confiables y portables. Mientras Docker estandarizó el empaquetado, Nix asegura la reproducibilidad. Juntos, son una herramienta poderosa para desarrolladores y administradores de sistemas que buscan consistencia en sus despliegues.

¿Ya usas Docker en tu día a día? Quizás sea momento de darle una oportunidad a Nix y descubrir el siguiente nivel.

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.