Fundamentos del Sistema de Archivos en Linux
Para comprender cómo Docker gestiona las imágenes y contenedores a nivel de almacenamiento, es esencial revisar primero los componetnes básicos del sistema de archivos de Linux. Un sistema operativo Linux estándar depende de dos sistemas de archivos principales para su arranque y operación:
1. Boot File System (bootfs)
El bootfs alberga el cargador de arranque (Boot Loader) y el kernel. Los usuarios no pueden modificar estos archivos directamente. Una vez que el kernel se carga completamente en la memoria RAM durante el proceso de inicio, el bootfs se desmonta para liberar recursos. Dado que los contenedores Docker comparten el kernel del sistema anfitrión, el bootfs es idéntico para todas las distribuciones de Linux que se ejecuten en contenedores sobre el mismo host, lo que demuestra la dependencia de Docker del kernel subyacente.
2. Root File System (rootfs)
A diferencia del bootfs, el rootfs varía según la distribución de Linux (Ubuntu, Alpine, Debian, etc.). Contiene los binarios, bibliotecas y configuraciones específicas del sistema operativo. Cada contenedor posee su propio rootfs, derivado de la imagen base utilizada. Esto determina qué comandos y utilidades estarán disponibles dentro del entorno aislado del contenedor. Directorios típicos incluyen /bin, /etc, /lib, y /usr.
Principios de Almacenamiento con OverlayFS
Docker utiliza controladores de almacenamiento para gestionar las capas de las imágenes. El controlador predeterminado y más recomendado en la actualidad es overlay2, que implementa el sistema de archivos union OverlayFS. Esta arquitectura se compone de tres elementos fundamentales:
- LowerDir (Capa de solo lectura): Representa las capas de la imagen base (
rootfs). Al construir una imagen mediante un Dockerfile, cada instrucción genera una nueva capa de solo lectura. Un contenedor puede apilar múltiples directorioslowerdir. - UpperDir (Capa de lectura/escritura): Se crea dinámicamente cuando se inicia un contenedor. Cualquier modificación, creación o eliminación de archivos dentro del contenedor se registra exclusivamente en esta capa. Aquí es donde se almacenan los logs de la aplicación o archivos temporales generados en tiempo de ejecución.
- MergedDir (Punto de montaje unificado): Es la vista unificada que se presenta al usuario y a los procesos dentro del contenedor. Fusiona transparentemente el contenido de
LowerDiryUpperDir, dando la ilusión de un único sistema de archivos cohesivo.
Análisis de la Estructura de Almacenamiento de Imágenes
Inspección de Rutas Físicas
Podemos obtener las rutas exactas de las capas en el sistema de archivos del host inspeccionando los metadatos de la imagen:
docker-host:~$ docker image inspect node:18-alpine
...
"GraphDriver": {
"Data": {
"LowerDir": "/var/lib/docker/overlay2/a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8/diff:/var/lib/docker/overlay2/z9y8x7w6v5u4t3s2r1q0p9o8n7m6l5k4j3i2h1g0f9e8d7c6b5a4z9y8/diff",
"MergedDir": "/var/lib/docker/overlay2/m1n2o3p4q5r6s7t8u9v0w1x2y3z4a5b6c7d8e9f0g1h2i3j4k5l6m7n8/merged",
"UpperDir": "/var/lib/docker/overlay2/m1n2o3p4q5r6s7t8u9v0w1x2y3z4a5b6c7d8e9f0g1h2i3j4k5l6m7n8/diff",
"WorkDir": "/var/lib/docker/overlay2/m1n2o3p4q5r6s7t8u9v0w1x2y3z4a5b6c7d8e9f0g1h2i3j4k5l6m7n8/work"
},
"Name": "overlay2"
},
...
Comportamiento de las Capas Lower y Upper
El directorio LowerDir es estrictamente de solo lectura. Si intentamos crear un archivo directamente en el host dentro de esta ruta, el sistema lo permitirá a nivel de SO, pero Docker no lo reflejará en la imagen, ya que las capas inferiores son inmutables por diseño. Durante la construcción de imágenes, si dos imágenes comparten capas idénticas, Docker reutiliza los directorios lowerdir existentes para optimizar el espacio.
# Intento de modificación directa en el host (no recomendado)
docker-host:~$ touch /var/lib/docker/overlay2/a1b2c3d4.../diff/host_file.txt
Por otro lado, la capa UpperDir captura todas las altreaciones. Cualquier archivo creado dentro del contenedor activo residirá físicamente en este directorio del host.
Estructura de Almacenamiento en Contenedores en Ejecución
Al iniciar un contenedor en primer plano, Docker configura los puntos de montaje necesarios:
docker-host:~$ docker run -it --name app-container python:3.9-slim bash
root@8f3e2a1b9c4d:/#
Si inspeccionamos el contenedor en ejecución, observaremos que además de las capas de la imagen, Docker inyecta una capa especial llamada init:
docker-host:~$ docker inspect 8f3e2a1b9c4d
...
"Data": {
"LowerDir": "/var/lib/docker/overlay2/k8s-init-layer-hash123/diff:/var/lib/docker/overlay2/base-image-layer-hash456/diff",
"MergedDir": "/var/lib/docker/overlay2/container-runtime-hash789/merged",
"UpperDir": "/var/lib/docker/overlay2/container-runtime-hash789/diff",
"WorkDir": "/var/lib/docker/overlay2/container-runtime-hash789/work"
}
...
Desglose de Directorios y la Capa Init
1. UpperDir y MergedDir en Acción
Si generamos un archivo dentro del contenedor, este aparecerá inmediatamente en el MergedDir del host, pero físicamente se almacenará en el UpperDir:
# Dentro del contenedor
root@8f3e2a1b9c4d:/# echo "data_payload" > /app/runtime_config.json
root@8f3e2a1b9c4d:/# cat /app/runtime_config.json
data_payload
# En el host
docker-host:~$ cat /var/lib/docker/overlay2/container-runtime-hash789/diff/app/runtime_config.json
data_payload
2. El Directorio Work
El directorio WorkDir es un espacio de trabajo temporal requerido por el kernel de Linux para realizar operaciones atómicas en OverlayFS. Su contenido no es visible para el usuario ni para el contenedor, y se limpia automáticamente durante las operaciones de montaje.
3. La Capa Init y su Propósito
La capa init (identificada por un sufijo -init en su hash) se interpone entre las capas de solo lectura de la imagen (LowerDir) y la capa de lectura/escritura del contenedor (UpperDir). Su función principal es albergar archivos de configuración de red y hostname que deben ser específicos para cada contenedor, como /etc/hosts, /etc/hostname, y /etc/resolv.conf.
docker-host:~$ ls -l /var/lib/docker/overlay2/k8s-init-layer-hash123/diff/etc
total 0
-rwxr-xr-x 1 root root 0 Oct 24 10:00 hostname
-rwxr-xr-x 1 root root 0 Oct 24 10:00 hosts
-rwxr-xr-x 1 root root 0 Oct 24 10:00 resolv.conf
Estos archivos se crean vacíos y son poblados por el daemon de Docker en el momento exacto del arranque del contenedor. La capa init es crucial porque permite modificar configuraciones de red y nombre de host sin alterar las capas inmutables de la imagen base. Además, al ejecutar docker commit para crear una nueva imagen a partir de un contenedor, los cambios en la capa init se descartan intencionalmente, evitando que configuraciones de red efímeras se persistan en la nueva imagen.
Persistencia y Ciclo de Vida de los Datos
Los archivos escritos en la capa UpperDir persisten en el sistema de archivos del host mientras el contenedor exista, incluso si se detiene. Al reiniciar el mismo contenedor (usando su ID o nombre), los datos permanecen intactos. Sin embargo, si el contenedor se elimina (docker rm), su capa de lectura/escritura se destruye permanentemente junto con todos los datos no persistidos en volúmenes externos.
Cuando se genera una nueva imagen a partir de un contenedor mediante docker commit, la capa UpperDir del contenedor se congela y se convierte en una nueva capa LowerDir de solo lectura para la imagen resultante. Este mecanismo de copia por escritura (Copy-on-Write) es la base de la eficiencia de almacenamiento y la rapidez de despliegue en el ecosistema de contenedores.