Buildah vs Dockerfile. La guía definitiva para construir imágenes como un PRO Vistas: 2

A estas alturas, ya te puedo decir que en mi equipo personal ya no hay ni rastro de Docker. Sin embargo, me quedaba una asignatura pendiente por contarte, y era la construcción de imágenes utilizando Podman. Pero en concreto quería centrarme en buildah. En su momento, hice alguna imagen con Podman, pero utilizando Dockerfile, ahora quería explorar esto de hacerlo con scritps que es la solución que aporta Buildah y es básicamente donde se centra la gran diferencia entre ambos. Lo cierto es que me ha resultado realmente interesante y aunque seguiré utilizando Dockerfile en algunos proyectos, no dudo que en otros me centre en la experiencia de los scripts, que por otro lado tanto me gustan a mi.

0:00 / 0:00

Buildah vs Dockerfile. La guía definitiva para construir imágenes como un PRO

Una curiosidad

En general me gusta averiguar el porqué de las cosas, y en este caso, me ha resultado curioso que Buildah se centre en la construcción de imágenes utilizando scripts, mientras que Podman se centre en la construcción de imágenes utilizando Dockerfile. No es que sea algo malo, pero me ha resultado curioso. De hecho, lo que he hecho ha sido construir una imagen utilizando ambos métodos para ver las diferencias entre ambos y la verdad es que me ha resultado realmente interesante. En concreto he creado una imagen de nginx pero como webdav.

Pero lo que quería era conocer el porqué del nombre. Y es que Buildah viene de la pronunciación de Builder, constructor con un marcado acento de Boston donde la r final se omite o se transforma en un sonido de tipo ah. El porqué del nombre,

  • Funcionalidad. Buildah se llama así porque su única misión es construir imágenes.
  • Humor del desarrollador. El equipo orginal de Red Hat que lo creó buscaba algo que sonara cercano y desenfadado. En lugar de un nombre técnico o formal, optaron por algo que reflejara la idea de construcción de una manera divertida y memorable. La pronunciación de «Builder» con un acento de Boston les pareció una opción perfecta para transmitir esa sensación de cercanía y humor.
  • Identidad propia. Al separarse de Docker, era necesario un nombre que reflejara que esta herrramienta se espcializa solo en una parte del proceso, delegando la ejecución en otros.

El ejemplo de nginx como webdav

Como te comentaba, para comparar una solución con otra, he implementado la misma imagen utilizando ambos métodos, y la verdad es que me ha resultado realmente interesante. En concreto he creado una imagen de nginx pero como webdav. Para ello, he utilizado el siguiente Dockerfile,

# 1. Base ligera de Alpine
FROM alpine:3.23

# 2. Argumentos y variables de entorno
ARG USER=webdav
ARG UID=1000
ARG GID=1000

# 3. Instalación de paquetes (Nginx + Módulo WebDAV Extendido)
RUN apk add --no-cache nginx nginx-mod-http-dav-ext

# 4. Crear usuario sin privilegios
RUN adduser -D -u ${UID} ${USER}

# 5. Preparar estructura de directorios necesaria
# Creamos todas las rutas que Nginx necesita para escribir sin ser root
RUN mkdir -p /data \
             /var/log/nginx \
             /var/lib/nginx/tmp \
             /run/nginx \
             /tmp/nginx_upload && \
    # Ajustamos la propiedad al usuario 1000
    chown -R ${UID}:${GID} /data /var/log/nginx /var/lib/nginx /run/nginx /tmp/nginx_upload && \
    # Redirigir logs a la salida estándar para que 'podman logs' funcione
    ln -sf /dev/stdout /var/log/nginx/access.log && \
    ln -sf /dev/stderr /var/log/nginx/error.log

# 6. Copiar configuración personalizada
COPY nginx.conf /etc/nginx/nginx.conf

# 7. Configuración de ejecución
USER ${USER}
EXPOSE 8080
VOLUME ["/data"]

# Comando de arranque
CMD ["nginx", "-g", "daemon off;"]

Y por otro lado utilizando Buildah con un script de construcción,

#!/usr/bin/fish

set user webdav
set name "nginx-webdav"
set image_base "alpine:3.23"

echo "--- Construyendo imagen NO-ROOT de $name ---"

set container (buildah from $image_base)

# 1. Instalar paquetes
buildah run $container -- apk add --no-cache nginx nginx-mod-http-dav-ext

# 2. Crear usuario '$user' (UID 1000 para que mapee con tu usuario de Arch)
buildah run $container -- adduser -D -u 1000 $user

# 3. Montar para configurar
set mountpoint (buildah mount $container)

# 4. Crear y asegurar directorios para el usuario '$user'
# Necesitamos que Nginx pueda escribir logs, pids y temporales sin ser root
mkdir -p $mountpoint/data
mkdir -p $mountpoint/var/log/nginx
mkdir -p $mountpoint/var/lib/nginx/tmp
mkdir -p $mountpoint/run/nginx
mkdir -p $mountpoint/tmp/nginx_upload

# CAMBIO CLAVE: Todo pertenece al usuario 1000
chown -R 1000:1000 $mountpoint/data \
                   $mountpoint/var/log/nginx \
                   $mountpoint/var/lib/nginx \
                   $mountpoint/run/nginx \
                   $mountpoint/tmp/nginx_upload

# Enlazamos logs a la salida del contenedor
ln -sf /dev/stdout $mountpoint/var/log/nginx/access.log
ln -sf /dev/stderr $mountpoint/var/log/nginx/error.log

# 5. Copiar configuración
cp nginx.conf $mountpoint/etc/nginx/nginx.conf

# 6. Metadatos: Cambiamos el usuario de ejecución
buildah config --user $user $container
buildah config --port 8080 $container
buildah config --cmd '["nginx", "-g", "daemon off;"]' $container

buildah unmount $container
buildah commit --rm --squash $container atareao/$name:latest

De cuialquier forma te recomiendo que visites el respoitorio de GitHub donde he subido ambos ejemplos, y donde puedes encontrar el código completo de ambos métodos, así como las instrucciones para probarlos y utilizarlos.

Comparando uno con otro

Ambos métodos, llegan al mismo destino (una imagen de Alpine 3.23 con WebDAV y seguridad no-root), pero lo hacen con filosofías de construcción distintas que vale la pena desglosar para tu audiencia.

Gestión de la construcción

  • Transparencia vs. Control. El Dockerfile es declarativo; cualquier usuario que lo lea entiende el estado final deseado. El script de buildah es imperativo y procedimental, lo que te permite manipular el sistema de archivos del contenedor directamente desde tu shell de Arch (mountpoint), algo que un Dockerfile no permite.
    _ Eficiencia de Capas. En el Dockerfile, agrupamos los comandos mkdir, chown y ln en un solo paso RUN para generar una única capa y ahorrar espacio. En el script de buildah, aunque ejecutes varios comandos, el commit --squash final logra un resultado similar de una sola capa de datos.

Configuración de Usuario y Permisos

  • Flexibilidad (Args vs. Hardcoded).
    • El Dockerfile usa ARG para el UID y GID (por defecto 1000), permitiendo que alguien cambie esos valores al construir la imagen sin editar el archivo.
    • El script tiene el UID 1000 grabado directamente en los comandos chown y adduser, lo que lo hace menos flexible si otro usuario con distinto UID quisiera usarlo.
  • Propiedad de Archivos. Ambos aseguran que las rutas críticas (/data, /var/log/nginx, etc.) pertenezcan al usuario sin privilegios para que Nginx pueda arrancar y escribir sin ser root.

Anatomía de las Instrucciones

CaracterísticaInstrucción en DockerfileEquivalencia en build-webdav.fish
Base
FROM alpine:3.23

buildah from alpine:3.23
Instalación
RUN apk add --no-cache ...

buildah run ... apk add --no-cache ...
Manipulación FS
RUN mkdir / chown

mkdir / chown sobre el $mountpoint
Metadatos
USER webdav

buildah config --user webdav
Puerto
EXPOSE 8080

buildah config --port 8080

La «Magia» de los Logs

Ambos métodos implementan una solución idéntica para que podman logs funcione correctamente: crean enlaces simbólicos (ln -sf) desde los archivos de log de Nginx hacia la salida estándar (/dev/stdout) y de error (/dev/stderr).

Las ventajas

Utilizar un script de Buildah (como tu build-webdav.fish) en lugar de un Dockerfile tradicional ofrece ventajas específicas,

  • Control Granular y Depuración en Vivo
    • Manipulación Directa: Al usar buildah mount, puedes entrar al sistema de archivos del contenedor desde tu propia terminal de Arch Linux y realizar cambios con tus herramientas locales antes de hacer el commit.
    • Pruebas Atómicas: Puedes ejecutar el script paso a paso. Si una instrucción de buildah run falla, el contenedor sigue ahí y puedes inspeccionar su estado exacto sin tener que reconstruir todo desde la última capa válida del Dockerfile.
  • Flexibilidad y Lógica de Programación
    • Uso de Lenguajes de Scripting: Al ser un archivo .fish (o .sh), puedes usar toda la potencia de tu shell: bucles for, condicionales complejos o llamadas a otros scripts locales que serían muy difíciles de implementar en un Dockerfile.
    • Variables Dinámicas: Puedes capturar la salida de comandos de tu sistema host y pasarlas directamente al contenedor durante la construcción de forma más natural que con los ARG de Docker.
  • Eficiencia en el Resultado Final
    • Squashing Nativo: El comando buildah commit --rm --squash aplasta automáticamente todas las operaciones en una sola capa de sistema de archivos, lo que garantiza una imagen final muy ligera (especialmente usando Alpine) sin preocuparte por el número de instrucciones ejecutadas.
    • Sin Demonios: Buildah no necesita un demonio corriendo (como Docker), lo que reduce la superficie de ataque y el consumo de recursos en tu máquina de desarrollo.
  • Gestión de Archivos y Permisos
    • Copiado Inteligente: Puedes usar el comando cp estándar de Linux hacia el $mountpoint, permitiéndote mantener permisos de archivos de forma más precisa que con la instrucción COPY de Docker.
    • Estructura a Medida: Te permite crear directorios complejos y asignar propietarios (chown) de manera imperativa y clara, asegurando que el entorno no-root funcione perfectamente desde el primer segundo.

Los inconvenientes

Aunque el uso de scripts de Buildah (como tu build-webdav.fish) es una delicia para quienes amamos el control total en Linux, no todo es «miel sobre hojuelas». Para ser justos, también hay que mencionar los puntos donde el Dockerfile le gana la partida.

  • Falta de Estandarización y Portabilidad
    • Dependencia del Host: Tu script depende de que el sistema donde se ejecuta tenga instalado fish, buildah y las utilidades de GNU/Linux. Un Dockerfile, en cambio, se puede construir en casi cualquier sitio (Windows, macOS, CI/CD) sin cambiar ni una línea.
    • Curva de Aprendizaje: Para la comunidad, leer un Dockerfile es el estándar de la industria. Un script imperativo requiere que quien lo lea entienda la lógica de tu shell y los comandos específicos de Buildah.
  • Gestión de la Caché
    • Reconstrucciones lentas: El Dockerfile brilla gracias a su sistema de capas cacheables. Si solo cambias el nginx.conf, Docker/Podman solo reconstruye desde esa línea.
    • Todo o Nada: En tu script, cada vez que lo ejecutas, se suele empezar desde el buildah from. Aunque Buildah tiene caché, no es tan automática ni granular como la del motor de Docker, lo que puede hacer que las reconstrucciones sean más pesadas si la imagen crece.
  • Mantenimiento y Fragilidad
    • Rutas de montaje: El uso de buildah mount es potente pero peligroso. Si el script falla a mitad y no ejecuta el unmount, puedes dejar puntos de montaje «zombis» en tu sistema Arch que requieren intervención manual.
    • Errores Silenciosos: En un Dockerfile, si un paso falla, el proceso se detiene inmediatamente. En un script, si no gestionas bien los errores (con set -e o lógica similar), el script podría seguir ejecutando pasos sobre un contenedor corrupto.
  • Complejidad en el CI/CD
    • Entornos Especiales: La mayoría de las plataformas de integración continua (GitHub Actions, GitLab CI) están optimizadas para docker build. Ejecutar un script que requiere privilegios para montar sistemas de archivos (mountpoint) suele requerir configuraciones adicionales o contenedores con «privilegios» dentro del pipeline.

Comparativa rápida: ¿Cuándo usar cada uno?

SituaciónGanadorRazón
Uso personal en ArchScriptTienes el control total y tus herramientas de siempre.
Colaboración/Open SourceDockerfileEs el «idioma universal» de los contenedores.
Integración ContinuaDockerfileAprovecha mejor la caché y la infraestructura estándar.
Depuración ProfundaScriptPuedes inspeccionar el interior sin capas intermedias.

Más información,

Deja una respuesta

Publicar comentario