Has descargado imágenes, las has listado y las has borrado, pero todavía no sabes qué son por dentro. Y ese "por dentro" explica casi todo lo que te sorprenderá de Docker en los próximos módulos: por qué descargar la segunda imagen es más rápida que la primera, por qué la suma de tamaños de docker images no cuadra con el disco realmente ocupado, por qué los datos que escribes dentro de un contenedor desaparecen al borrarlo, y por qué la etiqueta latest es una trampa. En esta lección vas a diseccionar una imagen: su naturaleza inmutable, su modelo de capas, el mecanismo de copy-on-write que separa la imagen del contenedor, la nomenclatura completa con registros y digests, las familias de imágenes base, y una exploración práctica con docker image history e inspect sobre node:22-alpine, que será la base de la API de Aurora Libros.
Contenido
- Qué es exactamente una imagen
- El modelo de capas
- La capa de escritura del contenedor y el copy-on-write
- La consecuencia clave: los datos se pierden
- Nomenclatura completa: registro, repositorio, etiqueta y digest
- Por qué
latestno significa "la última" - Imágenes base y variantes oficiales
- Exploración práctica con
node:22-alpine - Comprobando que dos imágenes comparten capas
- Qué es exactamente una imagen
Una imagen de Docker es dos cosas empaquetadas juntas:
- Un sistema de archivos: el conjunto completo de directorios y ficheros que verá la aplicación cuando se ejecute. Binarios, librerías, ficheros de configuración, tu código.
- Unos metadatos de arranque: cómo hay que ejecutarla. Qué comando lanzar, con qué variables de entorno, qué usuario, en qué directorio de trabajo, qué puertos declara.
Y tiene tres propiedades que definen su comportamiento:
- Es inmutable. Una vez construida, no cambia jamás. Si necesitas una versión distinta, construyes otra imagen.
- Es de solo lectura. Ningún contenedor puede escribir en ella.
- Es identificable de forma criptográfica. Su contenido produce un hash SHA-256 que la identifica sin ambigüedad.
Esas tres propiedades juntas son las que hacen posible la reproducibilidad: si dos máquinas ejecutan la imagen con el mismo digest, están ejecutando exactamente los mismos bytes.
Lo que una imagen no contiene, y conviene tener claro desde el principio:
| Sí contiene | No contiene |
|---|---|
| Binarios y librerías de espacio de usuario | Un kernel (usa el del anfitrión) |
| Ficheros de configuración | Datos de tu base de datos en producción |
| Tu código de aplicación | Secretos que deban variar por entorno |
| Metadatos: comando, variables, usuario, puertos | Estado de ejecución |
- El modelo de capas
Aquí está la idea central. Una imagen no es un bloque monolítico: es una pila de capas, cada una de las cuales es un diff, un conjunto de cambios respecto a la capa anterior.
Imagina cómo se construye la imagen de la API de Aurora Libros:
- Capa 1: el sistema de archivos base de Alpine Linux (unos 8 MB).
- Capa 2: encima, se instala Node.js 22 (se añaden ficheros nuevos).
- Capa 3: encima, se copia el
package.jsondel proyecto. - Capa 4: encima, se instalan las dependencias de npm (
node_modules). - Capa 5: encima, se copia el código de la API.
graph TB
subgraph IMG["Imagen aurora-api:1.0.0 (solo lectura)"]
L5["Capa 5 · código de la API · 120 KB"]
L4["Capa 4 · node_modules · 48 MB"]
L3["Capa 3 · package.json · 2 KB"]
L2["Capa 2 · Node.js 22 · 42 MB"]
L1["Capa 1 · Alpine Linux base · 8 MB"]
end
L1 --- L2 --- L3 --- L4 --- L5
Cuando el contenedor arranca, todas esas capas se fusionan en una sola vista unificada del sistema de archivos gracias a un union filesystem (en Linux moderno, overlay2, el driver que viste en docker info). Desde dentro del contenedor no percibes capas: ves un / normal con /usr, /etc, /app, etc.
Las reglas de la fusión son simples:
- Un fichero que solo existe en una capa, aparece.
- Si un fichero existe en varias capas, gana el de la capa superior.
- Si una capa borra un fichero de una inferior, se marca con un fichero especial whiteout y deja de verse (pero sigue ocupando espacio en la capa inferior; esta consecuencia es importante para el tamaño de las imágenes y se explota en la lección 05-04).
Y ahora la propiedad que lo hace todo eficiente: las capas se comparten entre imágenes. Si tienes cinco imágenes distintas construidas sobre node:22-alpine, las capas de Alpine y de Node se almacenan una sola vez en el disco y las cinco imágenes las referencian.
graph TB
BASE["Capa: Alpine 3.20"] --> NODE["Capa: Node.js 22"]
NODE --> API1["aurora-api:1.0.0<br/>+ 2 capas propias"]
NODE --> API2["aurora-api:1.1.0<br/>+ 2 capas propias"]
NODE --> WORKER["aurora-worker:1.0.0<br/>+ 3 capas propias"]
De ahí salen tres beneficios muy concretos:
- Ahorro de disco: no multiplicas la base por cada imagen.
- Descargas rápidas: al hacer
pullde la segunda imagen, el daemon solo baja las capas que le faltan. Por eso a veces vesAlready existsen lugar dePull complete. - Subidas rápidas: al publicar una versión nueva de tu API en la que solo cambió el código, solo se sube esa capa.
Este último punto tendrá consecuencias enormes cuando escribas tu primer Dockerfile en el módulo 2: el orden de las instrucciones determina qué capas se reaprovechan.
- La capa de escritura del contenedor y el copy-on-write
Si la imagen es de solo lectura, ¿cómo puede un contenedor escribir ficheros? La respuesta es la pieza que une los dos conceptos:
Cuando creas un contenedor, Docker apila una capa fina de lectura y escritura encima de las capas de solo lectura de la imagen. Esa capa pertenece solo a ese contenedor.
graph TB
subgraph C1["Contenedor A"]
WA["Capa de escritura A<br/>(lectura/escritura)"]
end
subgraph C2["Contenedor B"]
WB["Capa de escritura B<br/>(lectura/escritura)"]
end
subgraph IMG["Imagen nginx:alpine (solo lectura, COMPARTIDA)"]
I3["Capa 3"]
I2["Capa 2"]
I1["Capa 1"]
end
WA --> I3
WB --> I3
I3 --- I2 --- I1
Dos contenedores de la misma imagen comparten todas las capas de la imagen y solo tienen propia su capa superior. Por eso arrancar el décimo contenedor de una imagen no cuesta prácticamente disco.
El mecanismo que hace esto posible se llama copy-on-write (copiar al escribir), y funciona así:
| Operación dentro del contenedor | Qué ocurre realmente |
|---|---|
| Leer un fichero de la imagen | Se lee directamente de la capa de la imagen. Coste cero |
| Crear un fichero nuevo | Se crea en la capa de escritura del contenedor |
| Modificar un fichero de la imagen | Se copia entero a la capa de escritura y se modifica ahí. La imagen no se toca |
| Borrar un fichero de la imagen | Se marca como borrado en la capa de escritura (whiteout). Sigue existiendo en la imagen |
Consecuencias prácticas que notarás:
- Modificar un fichero grande por primera vez es lento, porque hay que copiarlo entero antes. Las siguientes veces ya es rápido.
- Las aplicaciones con escritura intensiva (bases de datos, precisamente) rinden peor si escriben en la capa del contenedor. La solución es usar volúmenes, y por eso
aurora-dbnecesitará uno.
Puedes ver el tamaño de esa capa de escritura:
Cómo se lee: 1.09kB es lo que ha escrito este contenedor en su capa propia; virtual 52.5MB es el total incluyendo las capas de la imagen, que son compartidas. Si arrancas cinco contenedores de nginx:alpine, el disco no crece 5 × 52,5 MB, sino 52,5 MB más cinco capas de escritura minúsculas.
- La consecuencia clave: los datos se pierden
Esta es la implicación práctica más importante de todo el apartado anterior, y merece su propio bloque porque es la causa de la mayoría de los sustos de los principiantes:
La capa de escritura vive y muere con el contenedor. Cuando borras un contenedor, su capa de escritura se borra con él. Todo lo que la aplicación hubiera escrito ahí desaparece para siempre.
Compruébalo tú mismo, paso a paso:
docker run --name prueba-datos alpine:3.20 \
sh -c "echo 'catálogo de Aurora Libros' > /datos.txt && cat /datos.txt"El contenedor ha escrito el fichero en su capa de escritura y lo ha mostrado. Ahora reanuda ese mismo contenedor con docker start -a (la -a es --attach, para ver su salida):
Sigue funcionando: la capa de escritura del contenedor persiste mientras el contenedor exista, aunque esté parado. Y ahora la parte reveladora:
Qué acaba de pasar: al borrar el contenedor desapareció su capa de escritura y con ella /datos.txt. El nuevo contenedor nace de la imagen, que nunca contuvo ese fichero. La imagen es inmutable: escribir dentro de un contenedor no modifica la imagen.
Traducido a Aurora Libros: si metes PostgreSQL en un contenedor sin más y borras ese contenedor, pierdes el catálogo de libros entero. En un entorno de desarrollo es molesto; en producción es un incidente grave.
La solución se llama volúmenes: almacenamiento que vive fuera del ciclo de vida del contenedor. Es el tema completo de la lección 03-06, y en la lección 01-06 harás un primer uso muy introductorio de -v para servir una página de Aurora Libros con Nginx. De momento, quédate con la regla:
Todo dato que deba sobrevivir al contenedor tiene que estar en un volumen.
- Nomenclatura completa: registro, repositorio, etiqueta y digest
Cuando escribes nginx:alpine, estás usando una forma abreviada. El nombre completo de una imagen es:
Ejemplos, de más abreviado a más explícito:
| Lo que escribes | Cómo lo resuelve Docker |
|---|---|
nginx |
docker.io/library/nginx:latest |
nginx:alpine |
docker.io/library/nginx:alpine |
bitnami/postgresql:16 |
docker.io/bitnami/postgresql:16 |
ghcr.io/aurora-libros/aurora-api:1.2.0 |
Tal cual: registro GitHub, organización, repositorio, etiqueta |
registry.auroralibros.local:5000/aurora-api:1.2.0 |
Registro privado en el puerto 5000 |
Las reglas de completado automático son:
- Si no indicas registro, se asume
docker.io(Docker Hub). - Si no indicas usuario u organización y el registro es Docker Hub, se asume
library/, el espacio reservado a las imágenes oficiales. Por esonginxes oficial ybitnami/postgresqles de un tercero. - Si no indicas etiqueta, se asume
:latest.
El digest
Además de por nombre y etiqueta, toda imagen tiene un digest: el hash SHA-256 de su manifiesto.
REPOSITORY TAG DIGEST IMAGE ID SIZE
nginx alpine sha256:c15da6c91de8d2f436196f3a768483ad32c258ed4e1beb3d367a27ed67253e66 3f8a4339aadd 52.5MBLa diferencia entre los tres identificadores:
| Identificador | Ejemplo | Naturaleza |
|---|---|---|
| Etiqueta | nginx:alpine |
Mutable: puede reasignarse a otra imagen en cualquier momento |
| Image ID | 3f8a4339aadd |
Hash local de la configuración; inmutable, pero solo tiene sentido en tu máquina |
| Digest | sha256:c15da6c9... |
Inmutable y global: identifica exactamente los mismos bytes en cualquier registro y máquina |
Y por eso puedes anclar una imagen sin ninguna ambigüedad:
Esto descarga esa imagen exacta, pase lo que pase con las etiquetas. Es la práctica recomendada en producción y en pipelines de CI, donde la reproducibilidad importa más que la comodidad. Lo retomaremos en las lecciones 02-06 y 06-01.
- Por qué
latest no significa "la última"
latest no significa "la última"Este es uno de los malentendidos más caros de Docker, así que vamos a ser tajantes:
latestno es una función que devuelva la versión más reciente. Es simplemente el nombre de etiqueta que Docker usa por defecto cuando no especificas ninguna.
Nada obliga a que latest apunte a la última versión. Es una convención, y una convención que se incumple con frecuencia:
- Un mantenedor puede publicar
2.0.0sin actualizarlatest, que se queda en la 1.x. - Algunos proyectos usan
latestpara la última versión estable y publican las nuevas mayores solo con su número. - Y al revés:
latestpuede apuntar a una versión de desarrollo inestable.
Los problemas concretos que causa usarla:
| Problema | Explicación |
|---|---|
| No reproducible | docker pull miapp:latest hoy y mañana pueden traer imágenes distintas. Tu CI puede pasar hoy y fallar mañana sin que nadie haya tocado el código |
| Actualizaciones silenciosas | Un cambio mayor con ruptura de compatibilidad puede entrar en tu despliegue sin que lo decidas |
| Caché engañosa | Si ya tienes latest en local, Docker no vuelve a comprobar el registro salvo que fuerces el pull. Puedes estar ejecutando una versión de hace meses creyendo que es la última |
| Diagnóstico imposible | "¿Qué versión hay en producción?" — "latest". Eso no es una respuesta |
La práctica correcta:
# Mal: no sabes qué estás ejecutando
docker pull node
# Mejor: versión mayor y variante fijadas
docker pull node:22-alpine
# Aún mejor para producción: versión concreta
docker pull node:22.14.0-alpine3.21
# Máximo rigor: anclado por digest
docker pull node:22-alpine@sha256:9d2b8c0...Regla práctica: en desarrollo, node:22-alpine es un equilibrio razonable entre estabilidad y comodidad. En producción y CI, versión completa o digest. Durante el curso usaremos etiquetas explícitas siempre: node:22-alpine, postgres:16-alpine, redis:7-alpine, nginx:alpine.
- Imágenes base y variantes oficiales
Una imagen base es el punto de partida sobre el que construyes. Estas son las familias que encontrarás:
| Imagen base | Tamaño aprox. | Gestor de paquetes | Libc | Cuándo usarla |
|---|---|---|---|---|
scratch |
0 bytes | Ninguno | Ninguna | Binarios estáticos (Go, Rust). Máxima seguridad y mínimo tamaño |
alpine |
~8 MB | apk |
musl | Por defecto cuando el tamaño importa |
debian:bookworm-slim |
~75 MB | apt |
glibc | Compatibilidad amplia con tamaño contenido |
debian:bookworm |
~120 MB | apt |
glibc | Cuando necesitas herramientas del sistema |
ubuntu:24.04 |
~78 MB | apt |
glibc | Familiaridad, paquetes de Ubuntu |
| Imágenes distroless | ~20 MB | Ninguno | glibc | Producción endurecida, sin shell |
Un aviso importante sobre Alpine: usa musl como librería de C en lugar de la glibc habitual. El 95 % de las veces da igual, pero puede causar problemas con paquetes de Python o de Node que traigan binarios compilados contra glibc, y en casos concretos hay diferencias de rendimiento en la resolución de nombres DNS. Si algo se comporta de forma extraña en Alpine y no en Debian, esa suele ser la causa.
Variantes de las imágenes oficiales de lenguajes
Las imágenes oficiales publican varias variantes de cada versión, y elegir bien es importante. Tomemos Node.js 22, que es lo que necesitará aurora-api:
| Etiqueta | Base | Tamaño aprox. | Comentario |
|---|---|---|---|
node:22 |
Debian completo | ~1,1 GB | Trae compiladores y herramientas. Cómoda, enorme |
node:22-slim |
debian:slim |
~230 MB | Debian sin extras. Buen equilibrio |
node:22-alpine |
Alpine | ~140 MB | La más pequeña con gestor de paquetes. La que usaremos |
node:22-bookworm |
Debian 12 | ~1,1 GB | Igual que node:22 pero fijando la versión de Debian |
Las bases de datos siguen el mismo patrón: postgres:16 (Debian) frente a postgres:16-alpine, y redis:7 frente a redis:7-alpine.
Y una recomendación de seguridad que cobrará todo su sentido en la lección 05-03: prefiere siempre imágenes oficiales o verificadas. Cualquiera puede publicar en Docker Hub una imagen llamada postgresql-rapido con lo que le apetezca dentro.
- Exploración práctica con
node:22-alpine
node:22-alpineVamos a diseccionar la imagen que será la base de aurora-api.
Descargarla
22-alpine: Pulling from library/node
f18232174bc9: Already exists
9c3b5e8d1a2f: Pull complete
7d4a2c9e0f31: Pull complete
b1e6f8a4c507: Pull complete
Digest: sha256:9d2b8c0f4e1a7b3d5c6e8f0a2b4d6e8f0a1c3e5d7f9b1d3f5a7c9e1b3d5f7a9c
Status: Downloaded newer image for node:22-alpine
docker.io/library/node:22-alpineFíjate en la primera línea de capas: Already exists. Esa capa (f18232174bc9) es el sistema base de Alpine, y ya la tenías del docker pull alpine:3.20 de la lección anterior. Docker no la ha vuelto a descargar. Acabas de ver el compartido de capas en funcionamiento.
Listarla
Ver su historial de capas
IMAGE CREATED CREATED BY SIZE COMMENT
c4b8e2f1a9d3 5 days ago CMD ["node"] 0B
<missing> 5 days ago ENTRYPOINT ["docker-entrypoint.sh"] 0B
<missing> 5 days ago COPY docker-entrypoint.sh /usr/local/bin/ #… 388B
<missing> 5 days ago RUN /bin/sh -c apk add --no-cache --virtual … 7.66MB
<missing> 5 days ago ENV YARN_VERSION=1.22.22 0B
<missing> 5 days ago RUN /bin/sh -c addgroup -g 1000 node && addu… 125MB
<missing> 5 days ago ENV NODE_VERSION=22.14.0 0B
<missing> 3 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 3 weeks ago /bin/sh -c #(nop) ADD file:8f4a3d2b1c… in / 8.83MBSe lee de abajo arriba, en orden de construcción. Vamos con las claves:
- La última línea (
ADD file:... in /, 8,83 MB) es la capa base de Alpine. Coincide exactamente con el tamaño dealpine:3.20que viste antes: es literalmente la misma capa. RUN ... addgroup ... 125MBes la instalación de Node.js: la capa más pesada con diferencia. Ahí está el grueso de los 142 MB.- Las capas de 0B (
ENV,CMD,ENTRYPOINT) no añaden ficheros: solo modifican los metadatos de arranque. Ocupan cero porque no cambian el sistema de archivos. <missing>en la columna IMAGE no es un error: significa que esa capa intermedia no tiene un ID de imagen propio en tu máquina, porque descargaste la imagen en lugar de construirla. Es completamente normal.
Para ver la salida sin truncar:
Inspeccionar sus metadatos
Devuelve un JSON largo. Extraigamos lo importante:
{
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"NODE_VERSION=22.14.0",
"YARN_VERSION=1.22.22"
],
"Cmd": ["node"],
"Entrypoint": ["docker-entrypoint.sh"],
"WorkingDir": "",
"Labels": null
}Estos son los metadatos de arranque del apartado 1:
Env: variables de entorno que tendrá todo contenedor de esta imagen.Cmd: el comando por defecto. Si hacesdocker run node:22-alpinesin más, ejecutanodey te abre su consola interactiva.Entrypoint: un script que se ejecuta antes y que recibeCmdcomo argumento.WorkingDir: el directorio de trabajo inicial.
La distinción entre Cmd y Entrypoint es sutil e importante, y se trata a fondo en la lección 02-04.
Otros campos útiles:
docker image inspect node:22-alpine --format 'Arquitectura: {{.Architecture}} / SO: {{.Os}}'
docker image inspect node:22-alpine --format 'Capas: {{len .RootFS.Layers}}'
docker image inspect node:22-alpine --format '{{range .RootFS.Layers}}{{println .}}{{end}}'Arquitectura: amd64 / SO: linux
Capas: 4
sha256:f18232174bc91741fdf3da96d85011092101a032a93a388b79e99e69c2d5c870
sha256:9c3b5e8d1a2f...
sha256:7d4a2c9e0f31...
sha256:b1e6f8a4c507...El último comando lista los hashes de las capas de la imagen. Guárdate el primero de esa lista: lo vas a comparar en el apartado siguiente.
- Comprobando que dos imágenes comparten capas
Vamos a demostrar el compartido de forma inequívoca. Primero, asegúrate de tener las dos imágenes:
Ahora compara sus listas de capas:
sha256:f18232174bc91741fdf3da96d85011092101a032a93a388b79e99e69c2d5c870
sha256:9c3b5e8d1a2f...
sha256:7d4a2c9e0f31...
sha256:b1e6f8a4c507...La primera capa es idéntica en ambas. Mismo hash, mismos bytes, almacenados una sola vez en /var/lib/docker/overlay2. La imagen de Node no contiene su propia copia de Alpine: la referencia.
Puedes automatizar la comparación:
comm -12 \
<(docker image inspect alpine:3.20 --format '{{range .RootFS.Layers}}{{println .}}{{end}}' | sort) \
<(docker image inspect node:22-alpine --format '{{range .RootFS.Layers}}{{println .}}{{end}}' | sort)Qué hace: comm -12 compara dos listas ordenadas y muestra solo las líneas comunes (suprime las exclusivas de la primera con -1 y las de la segunda con -2). Los <(...) son sustituciones de proceso de bash: pasan la salida de cada comando como si fuera un fichero. El resultado es la lista de capas compartidas.
Y la confirmación en cifras:
REPOSITORY TAG IMAGE ID SIZE
node 22-alpine c4b8e2f1a9d3 142MB
nginx alpine 3f8a4339aadd 52.5MB
alpine 3.20 a8560b36e8b8 8.83MBSuma las tres imágenes: 142 + 52,5 + 8,83 = 203,3 MB. Pero docker system df reporta 194,5 MB. La diferencia de casi 9 MB es exactamente la capa base de Alpine, contada tres veces en docker image ls y una sola vez en el disco.
Ahí tienes la respuesta a una de las preguntas del principio de la lección: la columna SIZE de docker image ls muestra el tamaño total de cada imagen, sin descontar lo compartido. Para saber el espacio real, usa docker system df.
Errores Comunes y Consejos
- Creer que se puede modificar una imagen. No se puede: es inmutable. Lo que escribes dentro de un contenedor va a su capa de escritura, no a la imagen. Para "cambiar" una imagen se construye otra (módulo 2).
- Perder datos por no usar volúmenes. Es el error clásico. Si tu contenedor de PostgreSQL guarda el catálogo de Aurora Libros en su capa de escritura, un
docker rmlo borra todo. Solución: volúmenes (lección 03-06). - Usar
latesten cualquier sitio serio. No significa "la última", no es reproducible y hace imposible responder a "qué versión hay desplegada". Fija etiquetas explícitas, y digests en producción. - Sumar la columna SIZE de
docker image ls. Da un número mayor que el real por las capas compartidas. Usadocker system df. - Pensar que borrar un fichero en una capa reduce el tamaño de la imagen. No: la capa inferior sigue conteniéndolo, solo se oculta. Es la razón de que una imagen mal construida pese de más (lección 05-04).
- Confundir Image ID y digest. El Image ID es local; el digest es global y es el que debes usar para anclar una versión exacta.
- Consejo:
docker image historyantes que cualquier optimización. Te dice de un vistazo qué capa está engordando la imagen. - Consejo: elige la variante correcta.
node:22pesa unas ocho veces más quenode:22-alpine. En un pipeline que construye veinte veces al día, esa diferencia se nota en tiempo y en factura.
Ejercicios
Ejercicio 1: la aritmética de las capas
Descarga estas tres imágenes y responde:
- ¿Cuánto suman los tamaños que muestra
docker image ls? - ¿Cuánto ocupan realmente en disco según
docker system df? - Explica la diferencia identificando qué capa concreta (por hash) es la responsable.
- En la salida de los
pull, ¿en cuáles aparecieron líneasAlready existsy por qué?
Ejercicio 2: la lección de la persistencia
Ejecuta esta secuencia y explica el resultado de cada paso:
docker run --name libros alpine:3.20 sh -c "echo 'Rayuela, Julio Cortázar' > /catalogo.txt && cat /catalogo.txt"
docker start -a libros
docker rm libros
docker run --name libros alpine:3.20 cat /catalogo.txtDespués responde: (a) ¿por qué el segundo comando sí encuentra el fichero y el cuarto no?, (b) ¿en qué momento exacto se perdió el dato?, (c) ¿qué habría pasado si en lugar de docker rm hubieras hecho solo docker stop?
Ejercicio 3: identifica una imagen sin ambigüedad
Para la imagen node:22-alpine de tu máquina, obtén: (a) su Image ID, (b) su digest, (c) el número de capas que tiene, (d) su comando por defecto y sus variables de entorno, y (e) el tamaño de la capa más grande y qué instrucción la generó. Después, escribe el comando docker pull que descargaría exactamente esa misma imagen dentro de dos años, aunque la etiqueta 22-alpine haya cambiado de contenido.
Soluciones
Solución al ejercicio 1
- La suma de la columna SIZE rondará los 200 MB (aproximadamente 8,8 + 142 + 41 según versiones).
docker system dfreportará una cifra menor, unos 9 MB por debajo de la suma.- La responsable es la capa base de Alpine. Verifícalo así:
for img in alpine:3.20 node:22-alpine redis:7-alpine; do
echo "$img:"
docker image inspect $img --format '{{index .RootFS.Layers 0}}'
doneEste bucle imprime la primera capa de cada imagen (index .RootFS.Layers 0 toma el elemento 0 del array). Las tres muestran el mismo sha256:f1823217...: es la misma capa física almacenada una vez y referenciada tres veces. docker image ls la cuenta en cada imagen; docker system df la cuenta una vez, y de ahí la diferencia.
Already existsaparece en el segundo y el tercerpullpara esa capa base. En el primero (alpine:3.20) tuvo que descargarse; a partir de ahí, el daemon comprueba el hash, ve que ya la tiene y se la salta. Esa es la razón práctica de que las descargas sucesivas de imágenes de la misma familia sean mucho más rápidas.
Solución al ejercicio 2
Paso a paso:
docker run --name libros ...crea un contenedor nuevo, escribe/catalogo.txten su capa de escritura y lo muestra. Salida:Rayuela, Julio Cortázar. El contenedor termina y queda enExited (0).docker start -a librosreanuda el mismo contenedor (no crea otro). Vuelve a ejecutar su comando, que reescribe y muestra el fichero. La capa de escritura seguía intacta.docker rm libroselimina el contenedor y su capa de escritura con él./catalogo.txtdeja de existir.docker run --name libros alpine:3.20 cat /catalogo.txtcrea un contenedor nuevo a partir de la imagen. Salida:
Respuestas:
(a) El segundo comando encuentra el fichero porque opera sobre el mismo contenedor, cuya capa de escritura persiste mientras el contenedor exista. El cuarto no lo encuentra porque opera sobre un contenedor distinto, nacido de la imagen alpine:3.20, que nunca contuvo ese fichero. Escribir dentro de un contenedor no modifica la imagen: la imagen es inmutable.
(b) El dato se perdió exactamente en el docker rm. Ese es el momento en que la capa de escritura se elimina del disco.
(c) Con docker stop el fichero seguiría ahí. Parar un contenedor detiene su proceso pero conserva su capa de escritura; un docker start -a libros posterior habría vuelto a mostrar el contenido. Solo rm destruye los datos. Esta distinción entre "parado" y "eliminado" es central en el ciclo de vida que verás en la lección 03-02.
Solución al ejercicio 3
# (a) Image ID
docker image inspect node:22-alpine --format '{{.Id}}'
# (b) Digest
docker image inspect node:22-alpine --format '{{index .RepoDigests 0}}'
# (c) Número de capas
docker image inspect node:22-alpine --format '{{len .RootFS.Layers}}'
# (d) Comando por defecto y variables de entorno
docker image inspect node:22-alpine --format 'Cmd: {{.Config.Cmd}}{{println}}Env: {{.Config.Env}}'
# (e) Capa más grande y su instrucción
docker image history node:22-alpine --no-trunc --format "{{.Size}}\t{{.CreatedBy}}" | sort -h -r | head -1Comentarios:
.Iddevuelve el Image ID completo con prefijosha256:; los 12 primeros caracteres son los que ves endocker image ls..RepoDigestses un array porque una imagen puede estar publicada en varios repositorios;index ... 0toma el primero. El formato esnode@sha256:....len .RootFS.Layerscuenta los elementos del array de capas: normalmente 4 para esta imagen.- La capa más grande será la del
RUNque instala Node.js, alrededor de 125 MB. Esa única línea explica la mayor parte del tamaño de la imagen.
El comando que descarga exactamente esa imagen dentro de dos años:
Sustituyendo el digest por el que te haya devuelto el apartado (b). La clave del razonamiento: la etiqueta 22-alpine es un puntero mutable que el mantenedor puede reasignar a una imagen distinta cada vez que publique un parche de Node 22. El digest, en cambio, es el hash del contenido: identifica esos bytes concretos y nada más. Mientras la imagen siga existiendo en el registro, ese pull traerá siempre lo mismo. Es la práctica estándar para reproducibilidad en CI y producción.
Conclusión
Una imagen es un sistema de archivos más unos metadatos de arranque, inmutable, de solo lectura e identificada criptográficamente. Internamente es una pila de capas, cada una un diff de la anterior, que se fusionan en una vista única y —esto es lo importante— se comparten entre imágenes, de donde salen las descargas incrementales y el ahorro de disco que has verificado con tus propias manos comparando los hashes de alpine:3.20 y node:22-alpine.
Al crear un contenedor, Docker añade encima una capa de escritura propia y aplica copy-on-write: leer es gratis, modificar implica copiar. Y de ahí la regla que más disgustos evita: esa capa muere con el contenedor, así que todo dato que deba sobrevivir tiene que ir a un volumen (lección 03-06).
También sabes ya nombrar imágenes con precisión —registro/usuario/repositorio:etiqueta@digest—, por qué latest es un nombre y no una promesa, y qué familias de imágenes base existen, incluida node:22-alpine, que será la base de la API de Aurora Libros.
Con la teoría de imágenes resuelta, ya no hay nada que te impida ensuciarte las manos. En la siguiente lección, Creando tu Primer Contenedor Docker, harás una práctica guiada completa: un contenedor en primer plano, un servidor Nginx en segundo plano con su puerto publicado, un shell interactivo dentro de Alpine, y tu primera página de Aurora Libros servida desde un contenedor.
Docker: De Principiante a Avanzado
Módulo 1: Introducción a Docker
- ¿Qué es Docker?
- Instalando Docker
- Arquitectura de Docker
- Comandos Básicos de Docker
- Entendiendo las Imágenes de Docker
- Creando tu Primer Contenedor Docker
- El Proyecto del Curso: la Plataforma Aurora Libros
Módulo 2: Trabajando con Imágenes Docker
- Docker Hub y Repositorios
- Construyendo Imágenes Docker
- Conceptos Básicos de Dockerfile
- Instrucciones Avanzadas del Dockerfile
- Gestionando Imágenes Docker
- Etiquetado y Publicación de Imágenes
Módulo 3: Contenedores Docker
- Ejecutando Contenedores
- Ciclo de Vida del Contenedor
- Gestionando Contenedores
- Inspección y Depuración de Contenedores
- Redes en Docker
- Persistencia de Datos con Volúmenes
- Límites de Recursos y Políticas de Reinicio
Módulo 4: Docker Compose
- Introducción a Docker Compose
- Definiendo Servicios en Docker Compose
- Comandos de Docker Compose
- Aplicaciones Multi-Contenedor
- Variables de Entorno en Docker Compose
- Perfiles, Overrides y Múltiples Entornos
- Desarrollo Local con Docker Compose
Módulo 5: Conceptos Avanzados de Docker
- Profundización en Redes Docker
- Opciones de Almacenamiento Docker
- Mejores Prácticas de Seguridad en Docker
- Optimizando Imágenes Docker
- Builds Avanzadas con BuildKit y Buildx
- Registro y Monitoreo en Docker
- El Runtime por Dentro: Namespaces, Cgroups y Capas
Módulo 6: Docker en Producción
- Preparar una Imagen para Producción
- CI/CD con Docker
- Orquestando Contenedores con Docker Swarm
- Introducción a Kubernetes
- Desplegando Contenedores Docker en Kubernetes
- Escalado y Balanceo de Carga
- Estrategias de Despliegue y Rollback
