- Si en tu entorno ya se usan imágenes Docker como unidad de despliegue, Nix se convierte en un punto de entrada para experimentar con builds deterministas y fijación de dependencias sin cambiar mucho el workflow
- Un
docker build común suele depender del estado de la Internet pública, como repositorios de Ubuntu o descargas externas, por lo que es difícil reproducir la misma imagen después de un tiempo
- Nix conoce las dependencias necesarias a nivel de paquete, así que con
dockerTools.buildLayeredImage es posible configurar que se vuelvan a subir solo las capas modificadas
- El ejemplo en Go
douglas-adams-quotes muestra que se puede crear el binario con pkgs.buildGoModule y cargarlo para desplegarlo como una imagen Docker normal con nix build.#docker, docker load <./result
- Si varios servicios de un monorepo comparten capas y la caché de Nix, se pueden reducir tiempos y costos de build al reproducir imágenes de commits antiguos y en despliegues repetidos
Por qué sumar Nix al build de imágenes Docker
- Nix combina tres facetas: gestor de paquetes, lenguaje y sistema operativo
- Con el lenguaje Nix se escriben instrucciones para compilar paquetes
- El gestor de paquetes Nix puede crear software, herramientas, imágenes de NixOS, imágenes de contenedores y más a partir de esas instrucciones
- Introducir Nix en un pipeline CI/CD existente no es fácil
- Nix es distinto de la forma a la que muchos desarrolladores están acostumbrados
- Si no estás en un entorno donde puedas empezar todo de cero, como una startup nueva o un homelab, la barrera de adopción es alta
- Las organizaciones que ya usan Docker pueden aplicar Nix empezando por el build de imágenes de contenedores y experimentar sin romper demasiado el flujo de despliegue existente
Dónde se tambalea el build de Docker
- Docker y la contenerización se adoptaron ampliamente, y las imágenes Docker se usan en Internet casi como un formato universal de paquetes
- Plataformas como Fly.io, Railway y Render usan imágenes Docker en lugar de imágenes arbitrarias de VM o programas Linux en forma de tarball
- Los builds de Docker no siempre son deterministas
- La mayoría de los Dockerfile que se ven comúnmente en Internet compilan bien, pero un pequeño porcentaje de fallas puede convertirse en problemas operativos
- Una de las mayores debilidades es que el build de Docker accede a la Internet pública
- Es necesario para descargar paquetes desde los repositorios de Ubuntu
- Si más adelante hay que recrear la misma imagen, es difícil volver exactamente al estado que tenían los repositorios de Ubuntu en ese momento
- Ubuntu 18.04 se acerca al fin de soporte en ese año, y puede que recién después de una falla descubras qué dependía de esa versión
- Agregar paquetes a una imagen Docker de forma simple puede generar espacio desperdiciado
- Si ejecutas
apt-get upgrade al inicio del build, pueden reemplazarse archivos dentro de la imagen del contenedor
- Los archivos anteriores al reemplazo pueden quedar en capas y acumularse como copias en sombra
Cómo Nix maneja las capas de Docker
- Nix conoce de antemano las dependencias necesarias y puede dividirlas en la menor cantidad posible de capas Docker
- Cambiar una línea de código no obliga a que
apt o npm reinstalen dependencias
- Solo el contenido mínimo que realmente cambió puede reflejarse en la actualización de la imagen
dockerTools es un conjunto de herramientas para incluir paquetes de Nix y sus dependencias en imágenes Docker
- Las imágenes Docker creadas con Nix se dividen principalmente en dos enfoques
- Imagen sin capas: coloca el programa, sus dependencias y elementos adicionales como certificados raíz TLS en una sola carpeta, y la expone como una imagen de una única capa
- Imagen con capas: ubica cada dependencia en una capa de imagen separada para poder subir solo las partes que realmente cambiaron
- Docker también tiene un almacén basado en contenido internamente, pero es difícil aprovecharlo bien solo con
docker build
- Las imágenes con capas de Nix usan capas por paquete, así que dependencias como
glibc solo deben subirse una vez
- Eso sí, si esa biblioteca necesita una modificación, esa capa debe volver a subirse
Ejemplo de servicio en Go: Douglas Adams Quotes
- El servicio de ejemplo es un programa en Go que ofrece citas de Douglas Adams
- Un proyecto de módulos Go define el paquete Nix con
pkgs.buildGoModule
bin = pkgs.buildGoModule {
pname = "douglas-adams-quotes";
inherit version;
src = ./.;
vendorHash = null;
};
- Esta definición usa la plantilla de módulos Go para configurar el compilador de Go, el compilador de C para CGo y la descarga de dependencias externas
pname es el nombre del paquete
version se genera automáticamente a partir del commit de Git del servicio
src = ./.; usa el código fuente del directorio de trabajo actual
- Si no hay dependencias fuera de la biblioteca estándar, se puede poner
vendorHash = null
- Si hay dependencias externas, se puede especificar el hash de todas las dependencias o usar
gomod2nix
- El paquete se compila con el siguiente comando
nix build .#bin
- La imagen Docker con capas puede crearse con
dockerTools.buildLayeredImage
docker = pkgs.dockerTools.buildLayeredImage {
name = "registry.fly.io/douglas-adams-quotes";
tag = "latest";
config.Cmd = "${bin}/bin/douglas-adams-quotes";
};
- Al indicar el binario del servidor compilado en
config.Cmd, también se copian los elementos necesarios
- También se incluye
glibc y lo necesario para la ejecución
- Los paquetes adicionales, como certificados raíz CA, pueden ponerse en
contents
docker = pkgs.dockerTools.buildLayeredImage {
name = "registry.fly.io/douglas-adams-quotes";
tag = "latest";
contents = with pkgs; [ cacert ];
config.Cmd = "${bin}/bin/douglas-adams-quotes";
};
- La imagen se crea y se carga en el daemon de Docker con estos comandos
nix build .#docker
docker load < ./result
- Al abrirla con
dive, se ve que cada capa agrega un paquete distinto de nixpkgs y que, al final, los elementos se enlazan simbólicamente a la raíz de la imagen
El efecto de compartir capas en un monorepo
- Si hay varios servicios en el mismo repositorio, las imágenes Docker creadas con Nix pueden compartir capas
- Se comparten sin configuración adicional
- Para lograrlo solo con Docker, habría que crear varias imágenes base comunes, que podrían incluir herramientas y contenido innecesario que algunos servicios no usan
- Por ejemplo, el repositorio
Xe/x es un monorepo que reúne 10 años de proyectos paralelos, experimentos y herramientas
- Varios proyectos están desplegados en unas tres plataformas
- Se ha venido trabajando en colocarlos en imágenes Docker para converger en una base común de despliegue
- Al pushear la actualización de un servicio, también se pushea parte de las actualizaciones compartidas con la mayoría de los demás servicios
- Este enfoque ahorra tiempo y costos en varios proyectos
Lo que es difícil igualar solo con la caché de Docker
- Usando el caching de Docker, en teoría también se podrían compilar imágenes con la misma eficiencia que con Nix
- Pero para lograr el mismo efecto con el enfoque de Docker, las etapas de build pueden volverse difíciles de mantener
- Hay que instalar las bibliotecas compartidas en capas separadas
- Hay que volver a activar la pila de red durante el build, lo que debilita la reproducibilidad
- Hay que reajustar rutas de búsqueda, flags del compilador y configuraciones relacionadas con CGo
dockerTools.buildLayeredImage de Nix se encarga de los detalles de meter paquetes en el contenedor, simplificando la configuración
Reproducción de momentos pasados y caché de Nix
- Una de las grandes ventajas de Nix es que permite volver a compilar exactamente software de un momento pasado
- Si más adelante hay que reproducir un bug en el entorno de un cliente específico, se puede recrear la misma imagen Docker
- El build de paquetes sirve para fijar el estado completo del software y sus dependencias en un punto en el tiempo
XeDN es un proyecto en el que se ha trabajado durante varios años, y la versión de hace 14 meses puede compilarse con el siguiente comando
nix build github:Xe/x/567fdc2#xedn-docker
- Este comando compila el target
xedn-docker desde un commit específico del repo de GitHub Xe/x
- Si se carga en el daemon de Docker, se crea una imagen con los mismos bytes que entonces
- El ejemplo también incluye Go 1.19
- Lograr la misma reproducción usando solo builds Docker normales podría requerir mucho costo de almacenamiento
- La caché de Nix guarda la salida de los comandos Nix para no tener que recompilarla más adelante
- No hace falta que cada laptop de desarrollador compile de nuevo paquetes como
nokogiri
- Puede recibir resultados ya compilados en la nube
- Garnix se usa para el CI de todos los proyectos flakes y reporta el estado de build en cada commit
- También se compilan con Garnix las configuraciones de máquinas del homelab, de modo que al actualizarlas cada noche se trae la configuración más reciente desde la caché de Garnix en lugar de compilarla localmente
Conclusión como artefacto de despliegue
- Puede decirse que Nix, como constructor de imágenes Docker, es mejor que el constructor de imágenes de Docker
- Nix hace que especifiques el resultado, sin tener que enumerar directamente los pasos detallados para llegar a él
- En entornos que ya usan Docker, aplicar Nix empezando por el build de imágenes Docker es una ruta de adopción realista
- Las imágenes Docker creadas con Nix pueden compartir capas entre distintas partes de un monorepo
- Gracias a la caché binaria, no hace falta recompilar código que ya se compiló en el pasado
- El artefacto final es una imagen de contenedor normal que se puede desplegar en plataformas como AWS, Google Cloud y Fly.io
1 comentarios
Opiniones de Hacker News
Intenté varias veces que me gustara Nix, pero ahora siento que debería rendirme.
Tengo 2 sistemas corriendo Nix y me da miedo tocarlos; antes ambos se rompieron y tuve que reinstalarlos desde cero. En teoría, Nix es idempotente y determinista, pero en la práctica hay que entender respecto de qué es determinista, y si no conoces a fondo el comportamiento de todas las partes de las que depende, te encuentras con resultados raros y errores extraños que no ayudan, o con ninguna retroalimentación en absoluto.
La documentación, aunque sea completa y técnicamente correcta, es terriblemente críptica, y los tutoriales solo te llevan hasta el 80% del camino. Si sales de ahí, tienes que construirlo tú mismo, y eso exige años de conocimiento por experiencia y frustración, algo que ya no quiero soportar. La ventaja de Docker, más bien, es el caos mismo: con saber más o menos shell y el gestor de paquetes de la distribución, puedes construir casi cualquier cosa, y cuando hay problemas es mucho más fácil diagnosticarlos y corregirlos gracias a herramientas con décadas de antigüedad.
Nix, como Emacs, puede hacer cualquier cosa si tienes paciencia y un conocimiento profundo y visionario, pero parece que no hay más opción que meterse de lleno o mirarlo desde lejos.
Si haces dos builds seguidas en la misma máquina, obtienes el mismo resultado, pero con el tiempo, cuando se actualizan el gestor de paquetes y las etiquetas de las imágenes base, una rebuild un mes después puede dar un resultado totalmente distinto. Cuando el equipo administra unos 40 contenedores, arreglar contenedores se vuelve una parte importante del trabajo.
Por eso, en teoría Nix debería ser perfecto, pero es tan distinto que las herramientas de los proveedores no corren en Nix y, cuando aparece un error, es difícil encontrar rápidamente una solución en la web. Esa frustración me llevó a crear https://www.stablebuild.com, que ofrece builds deterministas basadas en Docker sobre contenedores Ubuntu, Debian y Alpine. Está compuesto por un caché pull-through inmutable de Docker Hub, copias completas diarias de repositorios de paquetes de Ubuntu/Debian/Alpine, copias diarias de PPA populares y del índice de PyPI, y un caché inmutable de archivos/URL arbitrarios.
En la práctica, es fácil de usar y depurar, y tiene amplia compatibilidad de software; en los contenedores migrados a StableBuild hubo 0 problemas causados por no determinismo.
Una vez pasé tiempo tratando de entender por qué
nix developno se comporta como dice la documentación y cómo hacer que se comporte así. La documentación dice que, por defecto, entras al entorno de tiempo de build, pero el valor predeterminado real no es hermético, y los nombres de las opciones de línea de comandos también son confusos, así que tuve que extraer el conocimiento del código fuente.Me gustaría que los casos borde que rompen la reproducibilidad se trataran de forma más explícita. El código de punto flotante es sensible al orden de las operaciones y el estado también puede filtrarse por la planificación con desalojo del sistema operativo; si no se explicita incluso lo obvio, la gente terminará disparándose en el pie.
Si usas la configuración “Erase your darlings”, puedes eliminar la mayor parte del estado no reproducible fuera de las cuentas de usuario. Es algo engorroso, pero uno se pregunta qué cosa no es engorrosa en NixOS.
https://grahamc.com/blog/erase-your-darlings/
No me preocupo por lo que hay dentro de las cuentas de usuario, y no me gusta Home Manager.
Docker está bien, pero en Mac el rendimiento no es muy bueno; Nix me gusta porque hace que instalar y hacer funcionar las cosas de la misma manera en varias máquinas sea trivial. La documentación de Nix es pésima, pero ChatGPT-4 fue excelente para resolver problemas de Nix.
Siento que el 90% de los problemas con Nix surgieron por intentar hacer cosas que no seguían “la forma Nix”.
He usado casi todos los gestores de paquetes de distribuciones y, sin hacer nada especial, de alguna manera logré romperlos todos. Fedora Kinoite se ha mantenido firme incluso agregando/eliminando capas, actualizando a diario y hasta haciendo rebase desde Silverblue. Personalmente, creo que rpm-ostree terminará reemplazando a Nix.
Veo a Nix y NixOS en un estado parecido al de git antes de GitHub.
La idea básica está basada en ciencias de la computación más serias que los enfoques tradicionales como SVN o Docker, y aunque la plomería interna todavía tiene problemas, no es peor; pero las herramientas y la documentación de cara al usuario no están al nivel de adopción masiva.
Puede que esto haya cambiado con el lanzamiento de flox: https://flox.dev. Es casi fluido, y viniendo de gente de DE Shaw, tampoco sorprende.
Nix no tiene mucho sentido sin flakes y
nix-command, pero ambos están documentados como experimentales y además vienen desactivados por defecto. La documentación está mejorando, pero todavía no alcanza, y nixlang es genial si lo aprendes bien, pero no puede aceptarse como barrera de entrada para el público general.nix-env -iA fooen la práctica casi nunca hace lo que uno quiere, así que Nix es menos un gestor de paquetes como se lo promociona y más un arma secreta para empresas que pueden permitirse desarrollar expertise interno.flox baja la barrera de “probarlo una vez y obtener de inmediato una mejor experiencia”. Nix/NixOS, o algo parecido, tarde o temprano va a relegar a Docker como Subversion, pero no ocurrirá hasta que llegue un momento tipo GitHub, y entonces ocurrirá de golpe.
La mayoría de las quejas sobre Nix en este hilo son técnicamente incorrectas, pero totalmente entendibles y, más importante, no son culpa del usuario. Sé que cada vez queda menos gente que conoció un mundo sin git/GitHub, pero sería bueno escuchar a Linus explicando, frente a personas que pasaron los criterios de contratación iniciales de Google, por qué deberían interesarse por una herramienta que les parece compleja.
https://youtu.be/MjIPv8a0hU8?si=QC0UnHXRdMpp2tI4
Llevamos un tiempo construyendo esto, y nos encantaría escuchar si hay algo que deberíamos priorizar para mejorarlo.
git cumple con eso, pero Nix todavía no, así que no estoy tan seguro de que un momento tipo GitHub vaya a ayudar.
A este post le falta explicar por qué son útiles las capas Docker compartidas.
La razón es el caché: mientras más imágenes compartan las mismas capas, más cosas se pueden cachear y más rápido arrancan los contenedores.
Docker es débil en esto porque, para obtener los beneficios del caché, al construir una imagen las capas resultantes deben ser lo más iguales posible a las capas existentes. Por ejemplo, si hoy ejecutas
apt-get install python3y no hay actualizaciones nuevas, debería producir exactamente la misma capa que ayer, pero como las capas de Docker se cachean por hash de archivos, todos los archivos tienen que ser exactamente iguales, incluso metadatos como la hora de creación.Nix ya guarda las dependencias por hash, así que si son la misma versión y la misma configuración, la capa siempre será la misma.
Las dependencias normalmente forman un grafo, no un árbol, así que esto se vuelve molesto muy rápido. Herramientas alternativas como Nix, y quizá Bazel, no tienen esta restricción y pueden mapear el grafo de dependencias a capas Docker para lograr un caché granular. Es algo que no se puede expresar con Dockerfile.
apt-getno se invalida automáticamente.Solo cambia si usas
--no-cacheo si hay algún cambio en una capa superior.Durante los últimos 2 o 3 días estuve batallando para compilar imágenes Docker en Darwin, y este artículo se siente como si el universo se estuviera burlando de mí
Nix es claramente la mejor herramienta para el objetivo que uno quiere lograr, pero tiene rincones oscuros y abandonados que te chupan el alma. Me gusta, pero a veces me siento como Morty arrastrado a la aventura de Rick en Compilerland
Docker es, en esencia, algo más cercano a una cárcel que contiene binarios de Linux. En Linux, compilas el binario, lo metes en el contenedor y listo; el código de Nix también es simple. Si puedes compilar el código, crear el contenedor es solo un paso más
Pero Docker requiere binarios de Linux, y en Mac Docker Desktop levanta una VM de Linux, hace todo el trabajo ahí dentro y luego oculta ese hecho. Nix no hace eso, así que hay dos opciones
La primera es hacer compilación cruzada, pero eso exige que incluso glibc pueda compilarse de forma cruzada; y aunque la mayoría de las dependencias de la comunidad puedan hacerlo, algunos paquetes quizá no fueron escritos pensando en compilación cruzada. Además, Hydra, que llena el caché estándar de Nix, no hace builds de compilación cruzada, así que podrías pasarte un buen rato compilando para luego fallar
La segunda es conectar un builder de Linux a la Mac y enviarle los trabajos de build
x86_64-linux. Puede ser una máquina física, una VM o incluso un contenedor Docker con NixOSLa opción 1 parece la forma correcta, pero la 2 es más práctica. Es muy probable que los problemas que estás viendo sean porque intentaste la 1, y eso requiere mucha experiencia no solo con Nix, sino también con compilación cruzada. Sería bueno que Hydra también compilara y cacheara compilaciones cruzadas de Darwin a Linux para evitar roturas, pero eso también aumentaría los costos. Parece mejor probar la solución 2
Creo que había una solución oficial: https://ryantm.github.io/nixpkgs/builders/special/darwin-bui...
En algunos stacks la compilación cruzada no es muy robusta, así que compilamos de forma cruzada con Docker para
{aarch64,amd64} x {linux,darwin}. Estamos corriendo varios Docker sobre Darwin aarch64 para compilar todo, y la experiencia ha sido buenahttps://github.com/gytis-ivaskevicius/high-quality-nix-conte...
Tiene uno que otro bug, pero parecen estar sobre todo relacionados con volúmenes, y maneja bastante bien la compilación de imágenes Docker. La parte más áspera es la velocidad: la combinación del hardware y el hecho de que Docker tenga que emular una VM de Linux pega duro
La experiencia de compilar una imagen Docker para una aplicación Java con Nix no fue muy buena
Después de que
gradle2nixquedara abandonado, no parece haber una alternativa clara para aplicaciones Java basadas en Gradle. Hace tiempo le propuse a un amigo intentar crear una imagen Docker lo más pequeña posible para una aplicación Spring Boot sencilla, y el resultado hecho con Nix era el doble de grande que la imagen hecha sin NixEl código se puede ver aquí: https://github.com/jossephus/Docker_challenge/blob/main/flak...
Se incluyen tanto zulu como el JDK que gradle trae mediante el argumento
jdk. Si mirasgradleGenen nixpkgs, se entiende a qué me refiero. Perdón porgradle2nix; estoy trabajando en una mejora menos hackyEra más o menos así: https://gist.github.com/takeda/17b6b645ad4758d5aaf472b84447b...
Enlacé todo con musl, compilé Python desactivando todos los paquetes que la aplicación no usaba y eliminé de boto3/botocore las partes no utilizadas. Solo boto3/botocore ya superan los 100 MB
Los paquetes de Nix apuntan por defecto al sistema operativo NixOS, por lo que en situaciones normales, donde hay suficiente espacio en disco, se espera que todas las funcionalidades estén activadas. Por eso arrastran muchas dependencias innecesarias. En cambio, las imágenes Alpine están diseñadas para Docker y su objetivo es desactivar funciones adicionales en los paquetes, así que el resultado es más pequeño
Para crear imágenes pequeñas hay que desactivar lo innecesario con
override. Por ejemplo, zulu incluye cosas como alsa, fontconfig, freetype, xorg, cups, gtk, cairo y ffmpeg. Tu amigo extrajo cuidadosamente solo los archivos necesarios y los metió en el contenedor, mientras que del lado de Nix se terminó empaquetando todo el paquete zulu con todas sus dependenciasPrimero habría que corregirlo para incluir un solo JDK y luego, con los métodos anteriores, reducir aún más el tamaño del JDK. Usar
openjdk_headlesspodría ser más sencillohttps://github.com/NixOS/nixpkgs/blob/master/pkgs/developmen...
https://github.com/GoogleContainerTools/jib/tree/master/jib-...
openjdk_headlessomite las dependencias de GTK y X que Spring no necesitaEn lugar de Zulu, lo cambié para basarse en la compilación headless de OpenJDK de Nixpkgs, eliminando dependencias de bibliotecas GUI, y usé
pkgs.jre_minimalyjlinkpara crear un JRE mínimo a medidaEl tamaño de la imagen es de 161 MB, un poco más grande que la imagen
demo_jlink. En la práctica, como incluye todos los módulos necesarios para ejecutar la aplicación, el JRE queda en unos 90 MB. La llamada ajdepsenDockerfile_jlinkno detectaba todos los módulos y estaba creando un JRE solo conjava.base. Si mi JRE mínimo también incluyera solojava.base, el JRE quedaría en unos 50 MB, y la imagen de contenedor rota sería de 117 MB según PodmanTambién eliminé el
copyToRootincorrecto en la llamada adockerTools.buildImage. Solo con el contexto de cadena deconfig.Cmdla app ya entra en la imagen, pero el código anterior la estaba copiando una vez más. Además, lo cambié adockerTools.buildLayeredImagepara poner cada store path en una capa de imagen separada; esto ayuda a la escalabilidad del espacio cuando se comparten dependencias entre varias imágenes de contenedor, pero no afecta a un experimento con una sola imagenLo que queda es principalmente optimizar el tamaño del JRE.
glibces la siguiente dependencia más grande, con unos 30 MB, aparentemente porque Nixpkgs compila glibc con soporte para muchos locales y codificaciones de caracteres. No sé si sería posible, ni práctico, separarlo en una derivation u output aparte para poder elegirlo. Si se crean varias imágenes condockerTools.buildLayeredImage, siempre que usen el mismo commit de Nixpkgs, glibc y el resto de las dependencias se compartenhttps://github.com/max-privatevoid/hackernews-docker-challen...
Si ya adoptaste Nix, excelente; y ojalá se difunda más la gestión declarativa de paquetes como Nix o Guix.
Si ya usas Docker pero quieres adoptar Nix de forma gradual, también existe el enfoque de esta charla: https://youtu.be/l17oRkhgqHE
En vez de migrar de inmediato toda la configuración y la construcción de contenedores a Nix, puedes mantener el Dockerfile y hacer que construya una configuración de Nix. La mayor desventaja es que no puedes aprovechar las capas en absoluto, y la ventaja es que puedes reutilizar la infraestructura o automatización existente de Docker mientras vas cambiando el Dockerfile gradualmente.
[1] https://mitchellh.com/writing/nix-with-dockerfiles
Pero me parece que una buena parte de esa falta de reproducibilidad se debe a que no hay garantía de que una de las capas de Docker siga estando disponible. Entonces me pregunto si Nix garantiza que las versiones de los paquetes permanecerán en el repositorio para siempre.
Cambiando un poco de tema, la ilustradora Annie Ruygt, que hizo las imágenes de estas diapositivas, también creó los logos y el arte de marca de dos startups de YC: RethinkDB(rethinkdb.com) y Pachyderm(pachyderm.io).
Trabajó en Pachyderm, y Pachyderm fue fundada por exingenieros de RethinkDB. Su trabajo es realmente bueno.
Hace relativamente poco, por recomendación del equipo de infraestructura, pasé más o menos medio día intentando construir una imagen base de CI con Nix, pero la imagen era enorme y algunas partes no funcionaban por problemas de linking.
Lo que me molestó especialmente fue que, al crear imágenes multiarquitectura, intentaba ejecutar realmente algo de otra arquitectura y solo soportaba virtualización de hardware con qemu. Las máquinas de build y las estaciones de trabajo son VMs, así que no tienen eso, aunque sí tienen
binfmt-misc. Si hubiera hecho fork/exec demkdirarm64 para crear/tmp, habría funcionado; pero una capa de Docker no es más que un archivo tar, así que se puede crear un directorio así:echo "tmp uid=0 gid=0 time=0 mode=0755 type=dir" | bsdtar -cf - @-Es muy probable que esta capa exacta ya exista en algún lado, así que quizá el usuario ni siquiera tendría que descargarla.
Cada vez que pruebo Nix, me queda la sensación de que, con unos meses más, podría usarlo regularmente. nixpkgs tiene casi todos los paquetes que quiero y se instala bien en la workstation. Pero un requisito como “necesito bash, python, build-essential y Bazel” no parece ser el objetivo de un builder de imágenes Docker. Si lo que quieres es meter un único binario de Go en una imagen Docker, no necesitas Nix. Tomas distroless y pones la aplicación en un archivo tar, y eso ya es un contenedor. Personalmente uso
rules_ocide Bazel; internamente hace más o menos eso, con apenas un poco más de inteligencia para construir binarios para varias arquitecturas, crear el YAML del índice de imágenes y subirlo al registry.Claro, los archivos de build del paquete tienen que soportarlo. Tampoco parece correcto decir que qemu solo soporta virtualización de hardware. qemu también se puede usar en arquitecturas donde solo es posible la emulación por software.
Aquí se cubre un ejemplo mínimo: https://discourse.nixos.org/t/how-do-i-get-a-shell-nix-with-...
No diría que debería ser tan simple como usar
pkgCross, pero me da curiosidad qué problema concreto encontraste en el proceso normal.https://nix.dev/tutorials/cross-compilation.html
La hemos usado durante años en varios proyectos, pero si el tamaño es un problema, con el método mencionado en el artículo se pueden crear imágenes muy pequeñas que solo incluyan lo que especifiques.
[1] https://hub.docker.com/r/nixos/nix/tags
Incluso usé musl; construía en CI las imágenes más pequeñas que he logrado con cualquier herramienta, de forma rápida y consistente, y el caché también funcionaba bien. No entiendo bien qué fue lo que se ejecutó.
buildFHSEnvpara soportar algún binario raro de terceros.Parece que Nix necesita más artículos que expliquen cómo hacer una transición suave, pieza por pieza, desde sistemas existentes.
Como ingeniero de plataformas, me gustaría que me gustara Nix, pero no es fácil para todos
Creo que la experiencia de desarrollo todavía es bastante mala. Por ejemplo, prefiero la experiencia de desarrollo de devbox porque permite agregar paquetes con algo como
devbox add python@3.11Al ver un
flake.nixde 120 líneas, es difícil decir que sea exactamente “más fácil”https://github.com/Xe/douglas-adams-quotes/blob/main/flake.n...
El flake enlazado define cómo compilar un binario de Go, define una imagen Docker que usa ese binario como punto de entrada, y también define un módulo de NixOS que crea un servicio systemd para ejecutar el binario de Go. Además, incluye una prueba de NixOS para verificar que ese módulo de NixOS cree el servicio systemd como se espera
El framework de pruebas de NixOS es bastante impresionante, y las pruebas se ejecutan dentro de una VM de QEMU que corre NixOS. Lo relevante para el artículo enlazado es solo la definición del binario de Go y de la imagen Docker, además de parte del boilerplate alrededor
Además, muchas líneas corresponden 1:1 con una descripción inline de un servicio systemd, así que no desaparecerían sin importar qué sistema se use. También incluye una forma elegante de declarar varios sistemas mediante overrides
Este ejemplo se parece más a “hagamos una cosa trivial como se haría en un proyecto grande y serio”, y si se tratara como algo puntual, creo que podría reducirse a unas 40 líneas
Ese binario y ese archivo de configuración son lo más parecido a un “flat pack” que se puede obtener en Linux, y este ejemplo muestra bien qué nos estamos perdiendo entre el bosque y los árboles
Guix también tiene una opción para Docker fácil y bastante buena llamada
guix pack -f dockerGuix además tiene la ventaja de usar un lenguaje ya existente, Guile/Scheme, en vez de un lenguaje propio dedicado
[1] https://guix.gnu.org/manual/en/html_node/Invoking-guix-pack....