Comprender las capas de imágenes
Explicación
Como aprendiste en ¿Qué es una imagen?, las imágenes de contenedor están compuestas por capas. Y cada una de estas capas, una vez creada, es inmutable. Pero, ¿qué significa eso realmente? ¿Y cómo se utilizan esas capas para crear el sistema de archivos que puede usar un contenedor?
Capas de la imagen
Cada capa de una imagen contiene un conjunto de cambios en el sistema de archivos: adiciones, eliminaciones o modificaciones. Veamos una imagen teórica:
- La primera capa añade comandos básicos y un gestor de paquetes, como apt.
- La segunda capa instala un entorno de ejecución de Python y pip para la gestión de dependencias.
- La tercera capa copia el archivo requirements.txt específico de la aplicación.
- La cuarta capa instala las dependencias específicas de esa aplicación.
- La quinta capa copia el código fuente real de la aplicación.
Este ejemplo podría verse así:

Esto es beneficioso porque permite reutilizar las capas entre diferentes imágenes. Por ejemplo, imagina que quieres crear otra aplicación de Python. Gracias a las capas, puedes aprovechar la misma base de Python. Esto hará que las compilaciones sean más rápidas y reducirá la cantidad de almacenamiento y ancho de banda necesarios para distribuir las imágenes. La superposición de capas de la imagen podría verse similar a lo siguiente:

Las capas te permiten extender las imágenes de otros al reutilizar sus capas base, lo que te permite añadir únicamente los datos que tu aplicación necesita.
Apilar las capas
La superposición de capas es posible gracias al almacenamiento direccionable por contenido y a los sistemas de archivos de unión (union filesystems). Aunque esto se volverá un poco técnico, así es como funciona:
- Después de descargar cada capa, esta se extrae en su propio directorio en el sistema de archivos del host.
- Cuando ejecutas un contenedor a partir de una imagen, se crea un sistema de archivos de unión donde las capas se apilan unas sobre otras, creando una vista nueva y unificada.
- Cuando el contenedor se inicia, su directorio raíz se establece en la ubicación de este directorio unificado, utilizando
chroot.
Cuando se crea el sistema de archivos de unión, además de las capas de la imagen, se crea un directorio específicamente para el contenedor en ejecución. Esto permite al contenedor realizar cambios en el sistema de archivos al tiempo que permite que las capas de la imagen original permanezcan intactas. Esto te permite ejecutar múltiples contenedores a partir de la misma imagen subyacente.
Pruébalo
En esta guía práctica, crearás nuevas capas de imagen manualmente utilizando el comando docker container commit. Ten en cuenta que rara vez crearás imágenes de esta manera, ya que normalmente usarás un Dockerfile. Sin embargo, esto hace que sea más fácil entender cómo funciona todo.
Crear una imagen base
En este primer paso, crearás tu propia imagen base que luego utilizarás para los siguientes pasos.
Descarga e instala Docker Desktop.
En una terminal, ejecuta el siguiente comando para iniciar un nuevo contenedor:
$ docker run --name=base-container -ti ubuntuUna vez descargada la imagen y iniciado el contenedor, deberías ver una nueva línea de comandos de la terminal. Esta se está ejecutando dentro de tu contenedor. Se verá similar a lo siguiente (el ID del contenedor variará):
root@d8c5ca119fcd:/#Dentro del contenedor, ejecuta el siguiente comando para instalar Node.js:
$ apt update && apt install -y nodejsCuando se ejecuta este comando, descarga e instala Node dentro del contenedor. En el contexto del sistema de archivos de unión, estos cambios en el sistema de archivos ocurren dentro del directorio exclusivo de este contenedor.
Valida si Node está instalado ejecutando el siguiente comando:
$ node -e 'console.log("Hello world!")'A continuación, deberías ver que aparece "Hello world!" en la consola.
Ahora que tienes instalado Node, estás listo para guardar los cambios que has realizado como una nueva capa de imagen, desde la cual puedes iniciar nuevos contenedores o compilar nuevas imágenes. Para hacerlo, utilizarás el comando
docker container commit. Ejecuta el siguiente comando en una nueva terminal:$ docker container commit -m "Add node" base-container node-baseVisualiza las capas de tu imagen utilizando el comando
docker image history:$ docker image history node-baseVerás una salida similar a la siguiente:
IMAGE CREATED CREATED BY SIZE COMMENT 9e274734bb25 10 seconds ago /bin/bash 157MB Add node cd1dba651b30 7 days ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B <missing> 7 days ago /bin/sh -c #(nop) ADD file:6089c6bede9eca8ec… 110MB <missing> 7 days ago /bin/sh -c #(nop) LABEL org.opencontainers.… 0B <missing> 7 days ago /bin/sh -c #(nop) LABEL org.opencontainers.… 0B <missing> 7 days ago /bin/sh -c #(nop) ARG LAUNCHPAD_BUILD_ARCH 0B <missing> 7 days ago /bin/sh -c #(nop) ARG RELEASE 0BTen en cuenta el comentario "Add node" en la línea superior. Esta capa contiene la instalación de Node.js que acabas de realizar.
Para demostrar que tu imagen tiene Node instalado, puedes iniciar un nuevo contenedor utilizando esta nueva imagen:
$ docker run node-base node -e "console.log('Hello again')"Con eso, deberías obtener una salida "Hello again" en la terminal, lo que demuestra que Node está instalado y funcionando.
Ahora que has terminado de crear tu imagen base, puedes eliminar ese contenedor:
$ docker rm -f base-container
Definición de imagen base
Una imagen base es la base para compilar otras imágenes. Es posible utilizar cualquier imagen como imagen base. Sin embargo, algunas imágenes se crean intencionalmente como bloques de construcción, proporcionando una base o punto de partida para una aplicación.
En este ejemplo, probablemente no desplegarás esta imagen
node-base, ya que en realidad no hace nada todavía. Pero es una base que puedes usar para otras compilaciones.
Compilar una imagen de aplicación
Ahora que tienes una imagen base, puedes extender esa imagen para compilar imágenes adicionales.
Inicia un nuevo contenedor utilizando la imagen node-base recién creada:
$ docker run --name=app-container -ti node-baseDentro de este contenedor, ejecuta el siguiente comando para crear un programa de Node:
$ echo 'console.log("Hello from an app")' > app.jsPara ejecutar este programa de Node, puedes usar el siguiente comando y ver el mensaje impreso en la pantalla:
$ node app.jsEn otra terminal, ejecuta el siguiente comando para guardar los cambios de este contenedor como una nueva imagen:
$ docker container commit -c "CMD node app.js" -m "Add app" app-container sample-appEste comando no solo crea una nueva imagen llamada
sample-app, sino que también añade configuración adicional a la imagen para establecer el comando predeterminado al iniciar un contenedor. En este caso, lo estás configurando para ejecutar automáticamentenode app.js.En una terminal fuera del contenedor, ejecuta el siguiente comando para ver las capas actualizadas:
$ docker image history sample-appA continuación, verás una salida que se asemeja a lo siguiente. Ten en cuenta que el comentario de la capa superior dice "Add app" y la siguiente capa tiene "Add node":
IMAGE CREATED CREATED BY SIZE COMMENT c1502e2ec875 About a minute ago /bin/bash 33B Add app 5310da79c50a 4 minutes ago /bin/bash 126MB Add node 2b7cc08dcdbb 5 weeks ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B <missing> 5 weeks ago /bin/sh -c #(nop) ADD file:07cdbabf782942af0… 69.2MB <missing> 5 weeks ago /bin/sh -c #(nop) LABEL org.opencontainers.… 0B <missing> 5 weeks ago /bin/sh -c #(nop) LABEL org.opencontainers.… 0B <missing> 5 weeks ago /bin/sh -c #(nop) ARG LAUNCHPAD_BUILD_ARCH 0B <missing> 5 weeks ago /bin/sh -c #(nop) ARG RELEASE 0BFinalmente, inicia un nuevo contenedor utilizando la nueva imagen. Dado que especificaste el comando predeterminado, puedes utilizar el siguiente comando:
$ docker run sample-appDeberías ver aparecer tu saludo en la terminal, proveniente de tu programa de Node.
Ahora que has terminado con tus contenedores, puedes eliminarlos utilizando el siguiente comando:
$ docker rm -f app-container
Recursos adicionales
Si deseas profundizar en los temas aprendidos, consulta los siguientes recursos:
Siguientes pasos
Como se indicó anteriormente, la mayoría de las compilaciones de imágenes no utilizan docker container commit. En su lugar, utilizarás un Dockerfile que automatiza estos pasos por ti.