El proyecto Docker, con software homónimo, se ha es­ta­ble­ci­do como el estándar de la vi­r­tua­li­za­ción de co­n­te­ne­do­res. Un concepto central en la uti­li­za­ción de la pla­ta­fo­r­ma Docker es la Docker Image. En este artículo, te ex­pli­ca­mos cómo se construye una imagen de Docker y cómo funciona.

¿Qué es una Docker Image?

Conocerás el concepto “image” po­si­ble­me­n­te en relación con las máquinas virtuales (VM). Las imágenes de máquinas virtuales suelen ser una copia de un sistema operativo. Una imagen VM también puede contener otros co­m­po­ne­n­tes in­s­ta­la­dos, como bases de datos o se­r­vi­do­res web. Este término procede de una época en la que el software se compartía en soportes de datos ópticos, como los CD-ROM o los DVD. Para crear una copia local del soporte de datos, tenías que crear una imagen con un software especial.

La vi­r­tua­li­za­ción basada en co­n­te­ne­do­res es lo que sigue a la vi­r­tua­li­za­ción con VM. En vez de vi­r­tua­li­zar un ordenador virtual (máquina) con su propio sistema operativo, una imagen de Docker suele contener solo una apli­ca­ción. Puede tratarse de un solo archivo binario o de una agru­pa­ción de diversos co­m­po­ne­n­tes de software.

Para ejecutar la apli­ca­ción, se creará un co­n­te­ne­dor a partir de la imagen. Todos los co­n­te­ne­do­res de un Docker Host suelen recurrir al mismo núcleo del sistema operativo, por eso, los co­n­te­ne­do­res y las imágenes de Docker suelen pesar bastante menos que el equi­va­le­n­te en máquinas virtuales.

Los conceptos Docker Container y Docker Image están es­tre­cha­me­n­te vi­n­cu­la­dos. No solo puede crearse un co­n­te­ne­dor de Docker a partir de una imagen Docker, también puede crearse una nueva imagen desde un co­n­te­ne­dor en ejecución, así que entre co­n­te­ne­dor e imagen Docker hay una relación similar a la que hay entre gallina y huevo:

Comando Docker Si­g­ni­fi­ca­do Analogía huevo-gallina
docker run <image-id> Crear un Docker Container a partir de una Image La gallina nace del huevo
docker commit <container-id> Crear una Docker Image a partir del Container La gallina pone un huevo

En el sistema biológico de la gallina y el huevo, un polluelo nace de un huevo. En el proceso, el huevo se pierde. En cambio, a partir de una Docker Image pueden crearse tantos Co­n­tai­ne­rs similares como se quiera. Esta re­pro­du­ci­bi­li­dad hace que Docker sea adecuado como pla­ta­fo­r­ma de apli­ca­cio­nes y servicios es­ca­la­bles.

Una imagen de Docker es una plantilla inmutable a partir de la cual se crean co­n­te­ne­do­res de Docker. La imagen contiene toda la in­fo­r­ma­ción y las de­pe­n­de­n­cias para ejecutar un co­n­te­ne­dor. Esto incluye bi­blio­te­cas de programas básicos e in­te­r­fa­ces de usuario. En pa­r­ti­cu­lar, suele haber un entorno de línea de comandos (“Shell”) y una im­ple­me­n­ta­ción de la bi­blio­te­ca estándar C. A co­n­ti­nua­ción, te pre­se­n­ta­mos la imagen oficial de “Alpine Linux”:

Núcleo Linux Bi­blio­te­ca estándar C Comandos de Unix
del Host musl libc BusyBox

Además de estos co­m­po­ne­n­tes fu­n­da­me­n­ta­les que co­m­ple­me­n­tan el núcleo o kernel de Linux, una imagen Docker suele contener software adicional. Aquí te damos algunos ejemplos de co­m­po­ne­n­tes de software para distintos usos. Ten en cuenta que una única imagen Docker suele contener una pequeña selección de los co­m­po­ne­n­tes que mostramos:

Ámbito de apli­ca­ción Co­m­po­ne­n­tes software
Lenguajes de pro­gra­ma­ción PHP, Python, Ruby, Java, Ja­va­S­cri­pt
He­rra­mie­n­tas de de­sa­rro­llo node/npm, React, Laravel
Sistemas de bases de datos MySQL, Postgres, MongoDB, Redis
Se­r­vi­do­res web Apache, nginx, lighttpd
Caches y Proxys Varnish, Squid
Sistemas de gestión de contenido WordPress, Magento, Ruby on Rails

¿Qué di­fe­re­n­cia hay entre una Docker Image y un Docker Container?

Como ya hemos visto, existe una estrecha in­ter­ac­ción entre una Docker Image y un Container. ¿Qué ca­ra­c­te­rí­s­ti­cas di­fe­re­n­cian estos conceptos?

En primer lugar, ob­se­r­va­mos que una imagen Docker es inerte. Solo ocupa algo de espacio en la memoria, pero por lo demás no consume ningún recurso del sistema. Además, una Docker Image es inmutable una vez se crea, por lo que es un medio de “solo lectura” o protegido contra la escritura. Una pequeña nota al respecto: sí, es posible añadir cambios a una imagen Docker existente. Lo que ocurre es que esto crea una nueva imagen; la imagen original sigue exi­s­tie­n­do en la versión no mo­di­fi­ca­da.

Como hemos me­n­cio­na­do an­te­rio­r­me­n­te, es posible crear tantos co­n­te­ne­do­res similares como queramos a partir de una imagen Docker. Entonces, ¿qué di­fe­re­n­cia exac­ta­me­n­te a un co­n­te­ne­dor Docker de una imagen? Un co­n­te­ne­dor Docker es una instancia en fu­n­cio­na­mie­n­to (de­be­ría­mos decir, en ejecución) de una imagen Docker. Como cualquier software que se ejecuta en un ordenador, un co­n­te­ne­dor Docker en fu­n­cio­na­mie­n­to consume recursos del sistema como memoria y ciclos de CPU. Además, el estado de un co­n­te­ne­dor cambia a lo largo de su vida.

Si esta de­s­cri­p­ción te parece demasiado abstracta, quizá te sea de ayuda un ejemplo sencillo del día a día. Imagínate que una Docker Image es un DVD. El DVD como tal es inerte; está en su carcasa y no hace nada. Sin embargo, ocupa pe­r­ma­ne­n­te­me­n­te todo y al mismo tiempo un espacio limitado. Solo cuando se reproduce en un entorno especial (un re­pro­du­c­tor de DVD), el contenido “cobra vida”.

Al igual que la película creada para re­pro­du­ci­r­se en DVD, un co­n­te­ne­dor Docker en ejecución tiene un estado. En el caso de una película, son el tiempo de re­pro­du­c­ción actual, el idioma se­le­c­cio­na­do, los su­b­tí­tu­los, etc. Este estado cambia con el tiempo, y una película en re­pro­du­c­ción consume co­n­s­ta­n­te­me­n­te ele­c­tri­ci­dad en el proceso. Al igual que los co­n­te­ne­do­res similares pueden iniciarse desde una imagen Docker tantas veces como se desee, la película de un DVD puede re­pro­du­ci­r­se una y otra vez. Además, la película en ejecución puede detenerse y re­ini­ciar­se, algo que también es posible con un co­n­te­ne­dor Docker.

Concepto Docker Analogía Modo Estado Consumo de recursos
Docker Image DVD inerte Read only / in­va­ria­ble Fijo
Docker Container Re­pro­du­c­ción Película que se reproduce Cambia con el tiempo Varía según el uso

¿Cómo y dónde se utilizan las imágenes Docker?

Docker se utiliza ac­tua­l­me­n­te en todas las fases del ciclo vital del software: de­sa­rro­llo, testeo y uso. El concepto central en el eco­si­s­te­ma Docker es el co­n­te­ne­dor, y para crearlo siempre ne­ce­si­ta­mos una imagen. Las Docker Images son útiles siempre que se utilice Docker. Echemos un vistazo a un par de ejemplos:

Docker Images en entornos de de­sa­rro­llo locales

Si de­sa­rro­lla­mos software en un di­s­po­si­ti­vo propio, querremos que el entorno de de­sa­rro­llo local se mantenga tan co­n­si­s­te­n­te como sea posible. En la mayoría de casos, ne­ce­si­ta­mos versiones exactas e idénticas en términos de lenguaje de pro­gra­ma­ción, bi­blio­te­cas y otros co­m­po­ne­n­tes de software. Desde que se modifique al menos uno de los muchos planos, surgirán rá­pi­da­me­n­te problemas de­so­la­do­res, como que el código fuente no se compile o que el servidor web no se inicie. En este caso, la in­va­ria­bi­li­dad de la Docker Image hace ma­ra­vi­llas: como de­sa­rro­lla­dor, puedes confiar en que el entorno que contiene la imagen siga siendo co­n­si­s­te­n­te.

Los grandes proyectos de de­sa­rro­llo son liderados por equipos. Aquí es requisito previo utilizar un entorno que se mantenga estable con el tiempo en pro de la co­m­pa­ra­bi­li­dad y re­pro­du­ci­bi­li­dad. Todos los de­sa­rro­lla­do­res de un mismo equipo recurren a la misma Image. Si llega un de­sa­rro­lla­dor nuevo al equipo, recibirá la imagen Docker adecuada y podrá empezar de inmediato. Para realizar cambios en el entorno de de­sa­rro­llo, se creará una nueva imagen Docker. Los de­sa­rro­lla­do­res entrarán a la nueva imagen y estarán al día in­me­dia­ta­me­n­te.

Docker Image en la ar­qui­te­c­tu­ra orientada a servicios (SOA)

Las Docker Images son la base de la ar­qui­te­c­tu­ra orientada a servicios moderna. En lugar de una apli­ca­ción única y mo­no­lí­ti­ca, se de­sa­rro­llan servicios in­di­vi­dua­les con in­te­r­fa­ces bien definidas. Cada servicio está em­pa­que­ta­do en su propia imagen. Los co­n­te­ne­do­res lanzados a partir de ella se comunican entre sí a través de la red y es­ta­ble­cen así la fu­n­cio­na­li­dad global de la apli­ca­ción. La en­ca­p­su­la­ción en imágenes Docker separadas permite de­sa­rro­llar y mantener los servicios de forma in­de­pe­n­die­n­te. Los servicios in­di­vi­dua­les pueden incluso estar escritos en di­fe­re­n­tes lenguajes de pro­gra­ma­ción.

Docker Image para pro­vee­do­res de alo­ja­mie­n­to/PaaS

Las imágenes Docker también se utilizan en el centro de datos. Cada servicio, como el equi­li­bra­dor de carga, el servidor web, el servidor de base de datos, etc., se define como Docker Image. Cada uno de los co­n­te­ne­do­res creados a partir de ellas puede manejar una de­te­r­mi­na­da carga. Un software or­que­s­ta­dor supervisa los co­n­te­ne­do­res, su carga y su estado. Si la carga aumenta, el or­que­s­ta­dor inicia co­n­te­ne­do­res adi­cio­na­les desde la imagen co­rre­s­po­n­die­n­te. Este enfoque permite que los servicios se amplíen rá­pi­da­me­n­te re­s­po­n­die­n­do a las co­n­di­cio­nes ca­m­bia­n­tes.

¿Cómo se construye una Docker Image?

A di­fe­re­n­cia de lo que sucede con las imágenes de máquinas virtuales, una Docker Image en estado normal no es un archivo in­di­vi­dual. Se trata más bien de una agru­pa­ción de distintos co­m­po­ne­n­tes. He aquí un resumen (más detalles a co­n­ti­nua­ción):

  • Capas de imágenes: contienen datos añadidos al operar en el sistema de archivos. Las layers o capas se su­pe­r­po­nen y reducen a un nivel coherente mediante un sistema de archivos de unión.
  • Imagen padre: facilita las funciones fu­n­da­me­n­ta­les de la imagen y ancla la imagen al árbol ge­nea­ló­gi­co del eco­si­s­te­ma Docker.
  • Ma­ni­fie­s­to de imagen: describe la agru­pa­ción e ide­n­ti­fi­ca las capas de imágenes que contiene.

¿Qué pasa cuando queremos trasladar una imagen Docker a un archivo in­di­vi­dual? Esto también es posible con el comando “docker save” de la línea de comandos. Se creará un archivo tar, que puede moverse entre sistemas con bastante no­r­ma­li­dad. Con el siguiente comando, se escribirá una Docker Image con el nombre “busybox” en un archivo “busybox.tar”:

docker save busybox > busybox.tar

Muchas veces, al accionar “docker save” en la línea de comandos se canaliza a Gzip, de manera que tras la salida los datos se comprimen en el archivo tar:

docker save myimage:latest | gzip > myimage_latest.tar.gz

Con “docker load”, se ingresará como imagen Docker en el host de Docker un archivo de imagen creado con “Docker save”:

docker load busybox.tar

Capas de imágenes

Una imagen Docker está compuesta de capas de solo lectura, en inglés “layers”. Cada capa describe cambios sucesivos en el sistema de archivos de la imagen. Por cada operación que provoque un cambio en el sistema de archivos de la imagen, se crea una nueva capa. El enfoque utilizado suele de­no­mi­nar­se “copy‑on‑write”. El acceso de escritura crea una copia mo­di­fi­ca­da en una nueva capa y los datos ori­gi­na­les no se alteran. Quizá este principio te suene de algo, y es que el software de control de versiones Git sigue el mismo patrón.

Podemos mostrar las capas de una imagen Docker. Para ello, uti­li­za­mos el comando “Docker image inspect” de la línea de comandos. Esta llamada devuelve un documento JSON que pro­ce­sa­mos con la he­rra­mie­n­ta estándar jq:

docker image inspect <image-id> | jq -r '.[].RootFS.Layers[]'

Para reunir los cambios co­n­te­ni­dos en las capas, se utiliza un sistema de archivos especial. Este sistema de archivos de unión se superpone a todas las capas para dar lugar a una es­tru­c­tu­ra de carpetas y archivos coherente en la su­pe­r­fi­cie. Hi­s­tó­ri­ca­me­n­te, se uti­li­za­ban varias te­c­no­lo­gías conocidas como co­n­tro­la­do­res de al­ma­ce­na­mie­n­to (o storage driver en inglés) para im­ple­me­n­tar el sistema de archivos de la unión. En la ac­tua­li­dad, para la mayoría de los casos se re­co­mie­n­da el co­n­tro­la­dor de al­ma­ce­na­mie­n­to overlay2:

Co­n­tro­la­dor de al­ma­ce­na­mie­n­to Co­me­n­ta­rio
overlay2 Re­co­me­n­da­do ac­tua­l­me­n­te
aufs, overlay Utilizado en versiones an­te­rio­res

Es posible vi­sua­li­zar el co­n­tro­la­dor de al­ma­ce­na­mie­n­to de una imagen de Docker. Para ello, uti­li­za­re­mos el comando “Docker image inspect” de la línea de comandos. Esta llamada devuelve un documento JSON que pro­ce­sa­mos con la he­rra­mie­n­ta estándar jg:

docker image inspect <image-id> | jq -r '.[].GraphDriver.Name'

Cada capa de la imagen se ide­n­ti­fi­ca con un hash único que se calcula a partir de los cambios que contiene. Si dos imágenes utilizan la misma capa, lo­ca­l­me­n­te solo se almacena una vez. Entonces, ambas imágenes acceden a la misma capa. Así se consigue un al­ma­ce­na­mie­n­to local eficaz y se reducen las ca­n­ti­da­des de tra­n­s­fe­re­n­cia al recuperar las imágenes.

Imagen padre

Una Docker Image suele basarse en una “Parent Image” (imagen padre). En la mayoría de los casos, la imagen padre se ha creado mediante una in­s­tru­c­ción FROM en el Do­c­ke­r­fi­le. La imagen padre define una base a partir de la cual se co­n­s­tru­yen las imágenes que derivan de ella. Las capas de imagen exi­s­te­n­tes se su­pe­r­po­nen mediante capas adi­cio­na­les.

Al “heredar” de la imagen padre, la imagen de Docker se ubica en un árbol ge­nea­ló­gi­co que incluye todas las imágenes exi­s­te­n­tes. Quizá ya te estés pre­gu­n­ta­n­do dónde empieza el árbol ge­nea­ló­gi­co. Las raíces del árbol ge­nea­ló­gi­co están definidas por unas “imágenes base” es­pe­cia­les. En la mayoría de los casos, una imagen base se define con la in­s­tru­c­ción “FROM scratch” en el Do­c­ke­r­fi­le, aunque hay otras formas de crear una imagen base. Más in­fo­r­ma­ción sobre esto en la sección “¿De dónde vienen las imágenes Docker?”

Ma­ni­fie­s­to de imagen

Como hemos visto, una Docker Image consta de varias capas. Si uti­li­za­mos el comando “docker image pull” para obtener una imagen Docker de un registro en línea, no se descarga ningún archivo de imagen. En vez de eso, el Daemon de Docker local de­s­ca­r­ga­rá las capas in­di­vi­dua­les y las guardará. ¿De dónde procede la in­fo­r­ma­ción de las distintas capas?

La in­fo­r­ma­ción sobre qué capas de imagen componen una Docker Image viene en el llamado ma­ni­fie­s­to de la imagen. El ma­ni­fie­s­to de la imagen es un archivo JSON que describe to­ta­l­me­n­te una Docker Image. Entre otras cosas, el ma­ni­fie­s­to de imagen contiene:

  • In­fo­r­ma­ción sobre tamaño, diagrama y versión
  • Hashes cri­p­to­grá­fi­cos de las capas de imagen que se utilizan
  • In­fo­r­ma­ción sobre las ar­qui­te­c­tu­ras de los pro­ce­sa­do­res di­s­po­ni­bles

Para ide­n­ti­fi­car cla­ra­me­n­te una Docker Image, se calcula un hash cri­p­to­grá­fi­co del ma­ni­fie­s­to de la imagen. Al accionar “docker image pull”, primero se descarga el archivo de ma­ni­fie­s­to. El Daemon de Docker local extrae entonces las capas in­di­vi­dua­les de la imagen.

¿De dónde vienen las imágenes de Docker?

Tal y como hemos observado, las Docker Images son una parte im­po­r­ta­n­te del eco­si­s­te­ma Docker. Hay numerosas opciones para hacerse con una Docker Image. Di­s­ti­n­gui­mos dos formas básicas, cuyas ma­ni­fe­s­ta­cio­nes te mostramos más abajo:

  1. Extraer una Docker Image ya existente del Registry
  2. Crear una Docker Image nueva

Extraer una Docker Image de Registry

Muchas veces un proyecto Docker empieza con el paso de extraer una imagen Docker existente de un llamado Registry, que es una pla­ta­fo­r­ma accesible mediante la red que pro­po­r­cio­na imágenes Docker. El host Docker local se comunica con el Registry para de­s­ca­r­gar­se una Docker Image tras el comando “docker image pull”.

Por un lado, hay re­gi­s­tries online públicos y ac­ce­si­bles, que ofrecen una gran selección de Docker Images exi­s­te­n­tes. En el registry oficial de Docker “Docker Hub” hay, en el momento de redacción del artículo, más de ocho millones de Docker Images libres di­s­po­ni­bles en la lista. El Azure Container Registry de Microsoft contiene además de Docker Images otras imágenes de co­n­te­ne­do­res en distintos formatos. Además, la pla­ta­fo­r­ma permite a los usuarios crear sus propios re­gi­s­tries de co­n­te­ne­do­res no públicos.

Además de los re­gi­s­tries en línea me­n­cio­na­dos an­te­rio­r­me­n­te, también es posible alojar un registry local. Es algo que utilizan, por ejemplo, las grandes empresas para dar a sus propios equipos acceso protegido a las imágenes Docker que ellos mismos han creado. Para ello, la empresa Docker ofrece el “Docker Trusted Registry” (DTR), una solución local para pro­po­r­cio­nar un registro interno de la empresa en su propio centro de datos.

Crear una nueva Docker Image

A veces queremos crear una imagen Docker que se adapte es­pe­cia­l­me­n­te a un proyecto. Por lo general, usamos una Docker Image existente y la adaptamos según sea necesario. Pero recuerda que las Docker Images son in­mu­ta­bles. Si ma­n­te­ne­mos los cambios, el resultado será una nueva imagen Docker. Existen las si­guie­n­tes formas de crear una nueva Docker Image:

1. Co­n­s­trui­r­la desde la imagen padre con Do­c­ke­r­fi­le

2. Crearla desde un co­n­te­ne­dor en ejecución

3. Crear una nueva imagen base

Pro­ba­ble­me­n­te el enfoque más común para crear una nueva Docker Image es escribir un Do­c­ke­r­fi­le. El Do­c­ke­r­fi­le contiene in­s­tru­c­cio­nes es­pe­cia­les que es­pe­ci­fi­can la imagen padre así como todos los cambios ne­ce­sa­rios. Ac­cio­na­n­do “docker image build” se crea la nueva Docker Image desde el Do­c­ke­r­fi­le. He aquí un ejemplo sencillo:

# Crear Dockerfile desde la línea de comandos
cat <<EOF > ./Dockerfile
FROM busybox
RUN echo "hello world"
EOF
# Crear Docker Image desde Dockerfile
docker image build

Hi­s­tó­ri­ca­me­n­te, el concepto “Image” procede de la re­pro­du­c­ción de un soporte de datos (“to image” en inglés). En el contexto de las máquinas virtuales (VM), se crea una in­s­ta­n­tá­nea de una imagen de VM en ejecución. Con Docker es posible hacer algo parecido. Con el comando “Docker commit”, creamos una re­pro­du­c­ción de un co­n­te­ne­dor en ejecución como nueva Docker Image. Todas las mo­di­fi­ca­cio­nes efe­c­tua­das en el co­n­te­ne­dor serán al­ma­ce­na­das:

docker commit <container-id>

También existe la opción de pasar in­s­tru­c­cio­nes de Do­c­ke­r­fi­le al comando “docker commit”. Los cambios co­di­fi­ca­dos con las in­s­tru­c­cio­nes pasan a formar parte de la nueva Docker Image:

docker commit --change <dockerfile instructions> <container-id>

Para poder rastrear después qué cambios han llevado a una Docker Image, uti­li­za­mos el comando “docker image history”:

docker image history <image-id>

Como ya hemos visto, basamos una nueva Docker Image en una imagen padre o estado de un co­n­te­ne­dor en ejecución. ¿Pero qué pasa si quieres empezar desde cero con una nueva imagen Docker? Hay dos maneras de proceder. Puedes utilizar un Do­c­ke­r­fi­le con la in­s­tru­c­ción especial “FROM scratch”, tal y como expusimos an­te­rio­r­me­n­te. Como resultado, se crea una nueva y mínima imagen base.

Si incluso quieres pre­s­ci­n­dir de la imagen scratch de Docker, puedes utilizar una he­rra­mie­n­ta especial como de­boots­trap y preparar con ella una di­s­tri­bu­ción de Linux. A co­n­ti­nua­ción, se em­pa­que­ta­rá en un archivo “tarball” mediante el comando tar y se importará en el host local de Docker mediante “docker image import”.

Los comandos Docker Image más im­po­r­ta­n­tes

Comando Docker Image De­fi­ni­ción
docker image build Crear una Docker Image a partir de Do­c­ke­r­fi­le
docker image history Mostrar los pasos para crear una Docker Image
docker image import Crear una Docker Image a partir de un archivo Tarball
docker image inspect Mostrar in­fo­r­ma­ción detallada sobre una Docker Image
docker image load Cargar un archivo de imagen creado con “docker image save”
docker image ls / docker images Ver imágenes di­s­po­ni­bles en el host de Docker
docker image prune Eliminar Docker Images no uti­li­za­das del host de Docker
docker image pull Extraer Docker Image del Registry
docker image push Enviar Docker Image al Registry
docker image rm Eliminar Docker Image del host de Docker local
docker image save Crear un archivo de imagen
docker image tag Etiquetar Docker Image
Ir al menú principal