Una imagen Docker es un artefacto inmutable que describe el filesystem y la configuración necesaria para crear contenedores. No es un archivo monolítico ni una copia comprimida de una máquina completa. Está formada por objetos relacionados mediante hashes de contenido:
Texto
referencia humana
node:22-slim
↓
manifest o image index
↓
config object + layer blobs
↓
filesystem visible + metadata de ejecución
Cuando Docker crea un contenedor, no modifica la imagen. Monta sus capas de solo lectura y añade una capa writable específica para esa instancia. Esta separación permite compartir contenido entre múltiples imágenes y contenedores, verificar integridad y distribuir únicamente los blobs que faltan.
Sin un artefacto versionado, desplegar una aplicación suele significar repetir una secuencia de instalación sobre cada servidor:
instalar el sistema base;
descargar paquetes;
copiar el código;
configurar usuarios y permisos;
definir el comando de arranque;
confiar en que el resultado sea igual al anterior.
Ese procedimiento puede producir drift. Una actualización del package manager, una dependencia que cambió o un archivo residual genera resultados distintos aunque se utilice la misma documentación.
La imagen convierte el resultado del build en contenido identificable. Puede publicarse, descargarse y referenciarse mediante un digest concreto. La máquina de destino no necesita reconstruir la aplicación: necesita obtener el artefacto aprobado y suministrarle configuración de runtime.
Piensa en una imagen como una pila de cambios inmutables aplicada en orden:
Texto
Layer 4 COPY dist/ /app/dist
Layer 3 RUN npm ci --omit=dev
Layer 2 COPY package*.json /app/
Layer 1 filesystem de node:22-slim
---------------------------------
Filesystem visible de la imagen
Cada capa contiene diferencias respecto a la anterior: archivos añadidos, modificados o marcados como eliminados. El runtime presenta la combinación como un solo filesystem.
Al crear dos contenedores desde la misma imagen:
Texto
image layers compartidas y read-only
├── writable layer de api-1
└── writable layer de api-2
Los cambios de api-1 no aparecen en api-2. Tampoco cambian la imagen original.
La config no contiene la configuración concreta de un contenedor, como sus puertos publicados, secretos de runtime o límites de memoria. Esos valores se suministran al crear la instancia.
El cliente selecciona la variante compatible con la plataforma solicitada. Por eso dos equipos pueden hacer pull del mismo tag y obtener manifests específicos diferentes, aunque ambos partan del mismo índice.
Después de descargar un blob, el cliente calcula su hash y comprueba que coincide con el esperado. Un contenido alterado accidentalmente no debería aceptarse bajo el mismo digest.
Si el Engine ya posee un blob con ese digest, puede reutilizarlo. No importa de qué repository se obtuvo originalmente: el contenido idéntico tiene la misma identidad criptográfica dentro del modelo.
Content addressing no demuestra automáticamente que el contenido sea seguro o confiable. Solo identifica exactamente qué bytes se recibieron. La confianza requiere además controlar el origen, firmas, provenance, políticas y vulnerabilidades.
El propietario del repository puede mover un tag para que apunte a otro manifest. Incluso un tag semántico puede cambiar si la política permite sobrescribirlo.
Docker suele mostrar un ID basado en la config de la imagen. Sirve para identificar contenido en el store local, pero no reemplaza necesariamente el digest del manifest usado para distribuirlo.
Una imagen multi-platform hace todavía más importante la diferencia: el digest del índice y el digest del manifest de una arquitectura no son el mismo objeto.
RUN curl -o /tmp/archive.tar.gz https://example.com/archive.tar.gz
RUN tar -xzf /tmp/archive.tar.gz -C /app
RUN rm /tmp/archive.tar.gz
Sin embargo, la primera capa ya contiene el archivo comprimido. La tercera añade un marcador de eliminación, pero no reescribe la capa anterior. El blob sigue formando parte de la imagen distribuida.
Una alternativa es realizar descarga, extracción y limpieza en la misma instrucción:
docker
RUN curl -o /tmp/archive.tar.gz https://example.com/archive.tar.gz \
&& tar -xzf /tmp/archive.tar.gz -C /app \
&& rm /tmp/archive.tar.gz
Una solución aún mejor puede ser un multi-stage build donde el archivo temporal solo exista en una etapa que no se publica.
Cuando una capa elimina un archivo que existía antes, el formato necesita representar esa eliminación sin modificar el blob anterior. Para ello utiliza entradas especiales conocidas como whiteouts.
El filesystem combinado oculta el archivo, pero herramientas que inspeccionan las capas individuales todavía pueden encontrar el contenido original. Esta es una razón crítica para no copiar secretos y después borrarlos: el secreto puede permanecer en una capa publicada.
el archivo /message existe en la writable layer de demo.
Puedes crear una imagen nueva con docker commit, pero ese flujo no es una estrategia normal de construcción reproducible. El resultado depende de cambios manuales, puede omitir mounts y dificulta auditar cómo se produjo.
El enfoque correcto es modificar el Dockerfile o los artefactos de entrada y reconstruir.
base Debian
+ Node.js
+ node_modules
+ dist versión A
Al construir api:1.1, solo cambia dist:
Texto
base Debian reutilizada
Node.js reutilizada
node_modules reutilizada si lockfile no cambió
dist versión B capa nueva
El registry y el host descargan únicamente blobs faltantes. Esta eficiencia depende de que el Dockerfile mantenga estable el orden de instrucciones: copiar todo el source antes de instalar dependencias invalidaría esa capa con cada cambio de código.
El tamaño mostrado por una imagen no siempre equivale al espacio adicional real porque las capas pueden compartirse. Distingue tamaño lógico, blobs comprimidos en registry y uso físico local.
Dos hosts que ejecutan docker pull app:latest en momentos distintos pueden obtener artefactos diferentes. Para despliegues reproducibles utiliza digests o políticas de tags inmutables.
docker run app:1.0 puede usar el contenido local sin comprobar automáticamente si el registry movió el tag. Decide explícitamente cuándo hacer pull y qué política utiliza el despliegue.
El digest mostrado por un índice puede diferir del manifest ejecutado. Registra tanto la referencia promovida como la plataforma cuando la trazabilidad lo requiera.
Las capas conservan ownership y modos. Un COPY ejecutado como root puede dejar archivos inaccesibles para el usuario final. Usa COPY --chown o corrige permisos durante el build.
FROM alpine:3.22
RUN dd if=/dev/zero of=/large-file bs=1M count=20
RUN rm /large-file
CMD ["sh"]
Después:
Bash
docker build -t layer-demo .docker image ls layer-demo
dockerhistory layer-demo
Aunque /large-file no aparezca en el filesystem final, el tamaño permanece en una capa anterior.
Ahora combina creación y eliminación en el mismo RUN, reconstruye y compara. El ejercicio demuestra que las capas registran cambios, no una fotografía final compactada automáticamente.