Erase Una Vez Docker
Erase Una Vez Docker
Erase Una Vez Docker
Manuel Morejón
Este libro está a la venta en http://leanpub.com/erase-una-vez-docker
Éste es un libro de Leanpub. Leanpub anima a los autores y publicadoras con el proceso de
publicación. Lean Publishing es el acto de publicar un libro en progreso usando herramientas
sencillas y muchas iteraciones para obtener retroalimentación del lector hasta conseguir el libro
adecuado.
Instalando Docker . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Arquitectura y componentes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Docker Desktop: definición, precios y suscripciones . . . . . . . . . . . . . . . . . . . . . . 15
Docker Desktop para Mac . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Docker Desktop para Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
Docker Desktop para Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
Docker Engine para Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Mi primer contenedor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Resumen del capítulo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Construcción de imágenes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
Las imágenes se estructuran por capas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
La importancia del fichero Dockerfile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
Crear una imagen desde cero . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
Comprendiendo el comando build . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
Ejemplo de imagen para un sitio web . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
Ejemplo de imagen para aplicación con dependencias . . . . . . . . . . . . . . . . . . . . . 99
Ganando velocidad utilizando la caché . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
Construir para múltiples plataformas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
Utilizar estructura multi-stage en los Dockerfiles . . . . . . . . . . . . . . . . . . . . . . . . 107
Ejercicios para practicar lo aprendido . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
Resumen del capítulo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
¿Cómo utilizarlo?
Si desea llevar a la práctica rápidamente los conocimientos aprendidos sobre Docker, le recomiendo
sentarse delante de su ordenador, descargar la versión digital del libro y abrir su Terminal preferido.
Acerca de este libro 2
Podrá leer las secciones de su interés e ir poniendo en práctica los conceptos aprendidos sin necesidad
de costo alguno en infraestructura.
Si solamente desea revisar algunos conceptos sobre los contenedores, puede descargar el libro en el
formato que desee y acceder a las secciones que le sean de interés. En los capítulos va a encontrar
enlaces a sistemas y herramientas que le permitirán aumentar los conocimientos en profundidad.
Código fuente
Las configuraciones y ejemplos utilizados en el libro se encuentran GitHub.
Repositorios
• https://github.com/mmorejon/erase-una-vez-docker
• https://github.com/mmorejon/erase-una-vez-1
• https://github.com/mmorejon/erase-una-vez-2
Usuarios en Windows
Se recomienda utilizar GitBash² en el momento de ejecutar los comandos en consola. Este sistema le
va a brindar un entorno de consola similar a Linux.
Comentarios y sugerencias
Su criterio es el más importante. En todo momento estaré al tanto de sus dudas e inquietudes para
ayudarlo a encontrar respuestas.
Puede escribirme a través de los siguientes canales para compartir sus sugerencias y comentarios.
Después de haber avanzado en la lectura de los capítulos, si considera que ha sido útil el contenido,
vendría genial una revisión en la plataforma donde ha comprado el libro para que sirva de referencia
al resto de lectores.
El libro todavía se encuentra en fase de desarrollo, por lo tanto, si desea sugerir capítulos o
modificaciones en secciones hágalo saber y con gusto será analizado.
¡Muchas gracias por adelantado!
⁵https://www.linkedin.com/in/manuelmorejon/
Introducción a los contenedores
En el capítulo conocerá los problemas que dieron origen a los contenedores. También aprenderá el
significado de Docker como herramienta, comunidad y ecosistema. Esta será la base del conocimiento
que va a necesitar para dar los primeros pasos en el mundo de los contenedores y en específico Docker.
Funciona en mi máquina
Seguramente la frase “el código funciona en mi máquina, pero no en el servidor” le resulte familiar.
Esta situación ha sucedido durante mucho tiempo en los equipos de desarrollo, de hecho, puede ser
que usted vea este problema con frecuencia.
Este problema tiene lugar cuando se pone en ejecución el código de la aplicación en un entorno
diferente al lugar donde se ha creado, por ejemplo, cuando el código pasa de la máquina del
desarrollador a los entornos de calidad o producción.
La clave a tener en cuenta en esta situación son los entornos, de aquí lo importante de hacer la siguiente
pregunta: ¿cuántas diferencias existen entre estos entornos?
Para responder esta pregunta lo primero será definir, ¿qué es un entorno para nosotros? Un entorno
es el lugar donde se ejecuta el código programado. Un entorno de producción puede ser la máquina
virtual creada en alguna de las conocidas plataformas públicas como AWS, GCloud, Azure, u otra
similar. El entorno de pruebas también puede estar compuesto por máquinas virtuales en plataformas
públicas, mientras que el entorno de desarrollo es el ordenador utilizado para programar el código
(nuestra máquina).
Cada uno de los entornos mencionados está compuesto por las siguientes capas o estructuras lógicas:
hardware/infraestructura, sistema operativo, bibliotecas/componentes y aplicaciones; siendo esta
Introducción a los contenedores 5
última (aplicaciones) el lugar donde se encuentra el código en ejecución. Tomando como referencia
estas capas puede identificar de forma rápida muchas de las diferencias que existen entre los entornos
de trabajo, principalmente entre los entornos de desarrollo y producción. Los entornos de desarrollo
(nuestras máquinas) tienen sistemas operativos con interfaz gráfica que apoya a un rápido desarrollo
de software, mientras que los servidores de producción utilizan distribuciones orientadas a sacar el
máximo rendimiento de los recursos, donde el acceso se realiza principalmente a través de la interfaz
de comandos.
Grandes diferencias pueden existir también entre las bibliotecas instaladas en ambas máquinas. De-
pendiendo del tipo de aplicación necesita instalar componentes asociados al lenguaje de programación
utilizado. Para el lenguaje Java se necesita Java Development Kit (JDK), en Golang el binario de la
versión en uso, y así sucesivamente con cada uno de los posibles lenguajes. Estará de acuerdo conmigo
que las versiones instaladas en las máquinas de desarrollo casi nunca son las mismas a las instaladas
en los servidores de producción. Entre las causas más repetidas están:
Aunque estos elementos deben ser suficientes para entender las diferencias entre los entornos,
agreguemos un elemento más, las configuraciones. Las configuraciones y parámetros establecidos en
los entornos de producción son completamente distintas a los entornos de desarrollo. En este punto
no hay forma de lograr que sean iguales. Tanto por requerimientos de seguridad, como por requisitos
de la plataforma, las configuraciones siempre son distintas.
Llegados a este punto, donde quedan claras las diferencias entre entornos, volvamos a hacer la
pregunta:
¿Por qué el código funciona bien en mi máquina, pero no en el servidor de producción?
Seguramente se imagina la respuesta, porque los entornos son distintos.
Introducción a los contenedores 6
Si se desea que este ejemplo funcione bien en cualquier entorno, será necesario reproducir exactamente
los cuatro puntos en el lugar donde se ejecute. Sería muy sencillo si se pudiese copiar/pegar los
componentes como si fueran ficheros en el ordenador, ¿cierto?
De las capas mencionadas, el único elemento no obligatorio es el hardware/infraestrucutra, porque
no es posible mover su ordenador hacia Amazon, pero para el resto de capas será interesante pensar
cómo ponerlas en un solo paquete y moverlas moverlas entre entornos.
• Ligeros: En un contenedor se incluyen solamente los ficheros que son necesarios para su
funcionamiento.
• Portables: Pueden ser empaquetados en un fichero y ser distribuidos de forma rápida y sencilla
hacia los entornos. Cuando llega a su destino no necesita dependencia adicionales para su
ejecución.
• Procesos aislados: Los procesos dentro de un contenedor se encuentran aislados del resto de
procesos de la máquina.
Ejemplos de contenedores
Estos ejemplos representan una pequeña muestra de los contenedores que verá en los próximos capítu-
los. Es importante mencionar que todos cumplen con las características mencionadas anteriormente.
Al ver la zona del sistema operativo se preguntará que tan ligero puede llegar a ser un Ubuntu Server. El
fichero original cuenta con 1.2GB, mientras que en el contenedor solamente son 73MB (en el momento
de escribir el libro). La diferencia se debe a que han sido eliminados de todos aquellos elementos no
son necesarios para ejecutar la aplicación.
la ausencia de un estado. Las imágenes no pueden estar iniciadas, detenidas o pausadas porque no
son procesos, solamente son ficheros.
Las imágenes están en nuestro ordenador como cualquier otro fichero, pueden ser movidas de una
máquina a otra, o transferidas hacia un almacenamiento en la nube, pero nunca en ejecución.
Sin embargo, los contenedores sí son procesos en ejecución, y siempre tienen un estado asociado.
Existe una estrecha relación entre las imágenes y los contenedores, con una imagen pueden ser
iniciados muchos contenedores. Si se busca un homólogo con elementos de cocina se puede decir
que la imagen es la receta y los contenedores son los platos que se hicieron siguiendo la receta. Otro
ejemplo, pero más cercano al mundo de la programación, puede ser que una imagen es una clase y
los contenedores son los objetos. De una clase pueden crearse muchos objetos. En resumen, la imagen
es el conjunto de ficheros (sistema operativo, dependencias y código de la aplicación) que utiliza el
contenedor para iniciarse como un proceso.
En la siguiente tabla se muestran las principales diferencias entre las imágenes y los contenedores.
Imagen Contenedor
conjunto de ficheros empaquetados es un proceso en ejecución
no tiene estado tiene estado, p.ej iniciado, detenido, pausado
principales comandos: build, push y pull principales comandos: run, stop, exec
Se nota fácilmente que cada aplicación tiene sus dependencias empaquetadas dentro del contenedor,
por lo tanto, su funcionamiento va a ser el mismo. Un segundo aspecto que resalta en la imagen es el
aislamiento entre aplicaciones, donde cada una es un contenedor independiente. Entre contenedores
no se comparten los procesos.
El tener resuelto el problema inicial es solamente el principio, todavía falta por responder nuevas
incógnitas, como por ejemplo:
¿Cuál herramienta permite gestionar las aplicaciones a través de contenedores?
Existen múltiples herramientas para gestionar las aplicaciones como contenedores, pero hay una que
ha sido capaz de revolucionar la industria del software, Docker.
¿Qué es Docker?
Docker⁷ es la plataforma por excelencia para gestionar contenedores, y es a su vez, una herramienta.
El principal objetivo de Docker es construir, distribuir y ejecutar aplicaciones en contenedores de
forma fácil y sencilla.
Logo de Docker
Los conceptos sobre contenedores han estado presentes en la industria desde hace mucho tiempo,
pero en todos los casos ha existido gran complejidad para su uso y comprensión. De igual forma la
industria carecían de un ecosistema apropiado para extender esta tecnología a gran escala. Esta fue la
gran virtud de Docker, facilitar el uso de los contenedores en todos los entornos y crear un ecosistema
de herramientas para su uso.
⁷https://www.docker.com/
Introducción a los contenedores 10
Docker en un ordenador
Docker se instala en el ordenador como cualquier otro programa. Una vez iniciado es un proceso más
en la máquina, pero con la responsabilidad de gestionar contenedores. El número de contenedores que
a gestionar en su ordenador va a ser proporcional a los recursos de CPU y RAM que posea el equipo.
Estos son algunos de los múltiples motivos que convierten a Docker la herramienta más popular para
gestionar contenedores, pero este es solo el comienzo todavía quedan más sorpresas.
Introducción a los contenedores 11
Hub
Docker Hub⁸ es la plataforma donde se almacenan las imágenes de los contenedores. En ella puede
crearse su usuario personal, corporativo o ambas. El objetivo de Docker Hub es brindar un registro
centralizado para todas sus aplicaciones.
Este patrón existía en la industria para distribuir la información. Casi todas las tecnologías cuentan
con un almacenamiento para sus artefactos digitales, p.ej registros para node, python, ruby, entre otros
muchos ejemplos.
Docker Hub es el principal almacenamiento de imágenes públicas en la industria. A través de su bus-
cador puede encontrar imágenes oficiales de todas las tecnologías, desde lenguajes de programación,
sistemas de bases de datos, monitorización y cualquier otra que pueda imaginarse.
Otro detalle importante es la privacidad de la información, si usted lo desea puede almacenar las
imágenes de forma privada, garantizando su acceso solamente a aquellas personas o grupos de su
interés.
A medida que avance en el libro recibirá indicaciones de cómo trabajar con esta plataforma y
aprenderá a sacar provecho de ella.
Compose
Registry
Docker Registry¹⁰ permite crear registros privados de imágenes de contenedores. Puede instalar este
sistema en su plataforma y administrar directamente el almacenamiento de sus imágenes. Este sistema
le brinda el control total del registro de imágenes.
⁸https://hub.docker.com/
⁹https://docs.docker.com/compose/
¹⁰https://docs.docker.com/registry/
Introducción a los contenedores 12
Trust
Docker Trust¹¹ tiene la responsabilidad de firmar digitalmente las imágenes de contenedores durante el
proceso de construcción. Esta funcionalidad brinda la garantía de saber que está utilizando su imagen
y que no ha sido interceptada o cambiada durante el proceso de distribución.
Recuerde que la seguridad de sus aplicaciones en la cadena de distribución es un tema muy importante.
Arquitectura y componentes
Si conoce la arquitectura de un sistema podrá obtener el máximo provecho de sus funcionalidades.
La arquitectura de Docker es sencilla, pero robusta. Utiliza un modelo cliente - servidor, donde el
cliente brinda la interfaz de comandos (docker), y el servidor (dockerd) es el proceso que realiza las
operaciones en la máquina.
Instalando Docker 15
Vista de la arquitectura
Ambos elementos pueden estar instalados en la misma máquina, que de hecho es lo que va a suceder
cuando llegue a la sección de instalación, pero también puede conectar un cliente local con un servidor
dockerd en una máquina remota. Entre el cliente y el servidor la comunicación se realiza utilizando
API REST, ya sea a través de sockets UNIX o de interfaces de redes.
la suya propia basado en sus necesidades. El uso de Extensiones en Docker Desktop abre las puertas
a todos los equipos para compartir ideas y herramientas basadas en contenedores.
Docker Desktop se encuentra disponible para Mac, Windows y Linux, pudiendo ser utilizado sin
restricciones por casi la totalidad de usuarios. La excepción aparece solamente para las grandes
empresas, donde deben pagar por su uso. Desde el punto de vista de Docker, una gran empresa es
aquella que tiene más de 250 empleados y recibe más de $10 millones de ingresos anuales. Solamente
las empresas que cumplan este criterio están obligadas a utilizar una suscripción de pago para
hacer uso de la aplicación Docker Desktop. Dicho de otra manera, si la empresa donde usted trabaja
tiene estas características, debe pagarle a usted una suscripción Pro, Team o Business si quiere que
usted utilice Docker Desktop. En este momento usted es un estudiante, por lo tanto no tiene que
preocuparse por las suscripciones durante el uso del libro.
Si desea conocer los detalles de las políticas aplicadas a las grandes empresas consulte esta página¹⁶
en el sitio de Docker.
Para entornos gráficos la recomendación es utilizar Docker Desktop, mientras que para servidores
y entornos sin interfaz gráfica la recomendación es utilizar directamente Docker Engine. En las
próximas secciones se va a mostrar cómo instalar Docker Desktop en Windows, Mac y Linux, así
como también Docker Engine en entornos Linux.
Precios y suscripciones
Es fundamental conocer los términos y condiciones de cada herramienta antes de instalarla. Recuerde
que en temas legales, el desconocimiento de las reglas no le exime de culpas. Es su responsabilidad
leer la licencia asociada al producto antes de hacer uso del mismo.
Docker tiene sus propios términos y condiciones, de ahí la importancia de prestar atención a esta
sección antes de seguir con la instalación del producto. La vigencia del contenido asociado a
términos y condiciones corresponde con el momento en que se ha escrito el libro.
En este momento Docker cuenta con cuatro tipos de suscripciones:
• Personal: Precio $0 (sin costes). Ideal para desarrolladores independientes, educación, comuni-
dades open source y negocios pequeños.
• Pro: Precio $5/mes/por persona. Incluye herramientas profesionales para desarrolladores inde-
pendientes que deseen acelerar su productividad.
• Team: Precio $7/mes/por persona. Ideal para equipos, incluye elementos para mejorar la
colaboración, la seguridad y la productividad.
• Business: Precio $21/mes/por persona. Ideal para medianas y grandes empresas donde se
necesita centralizar la gestión de procesos junto con una seguridad avanzada.
De las cuatro suscripciones, la opción que se ajusta a su caso de uso es la primera, donde no hay que
realizar ningún pago, porque son actividades educativas como desarrolladores independientes. La
suscripción utilizada va a ser Personal.
¹⁶https://www.docker.com/pricing/faq
Instalando Docker 17
Otro detalle a tener en cuenta, las suscripciones están relacionadas únicamente con el uso de
Docker Desktop, aplicación de interfaz gráfica desarrollada por Docker. El uso de Docker Engine¹⁷
no tiene ningún tipo de suscripción asociada, puede utilizarlo en cualquier circunstancia. Si desea
ampliar la información sobre los precios de las suscripciones utilice este enlace¹⁸.
• Tener instalado Rosetta 2 porque algunos componentes todavía están en Darwin/AMD64. Para
instalar Rosetta 2 utilice el siguiente comando:
$ softwareupdate --install-rosetta
Busque en el listado general de aplicaciones el nombre Docker y haga clic sobre ella para iniciarla.
Necesita aceptar los términos y condiciones para seguir adelante con la instalación, en este caso son
bastante cortos, así que le recomiendo leerlos y continuar si está de acuerdo. Recuerde que en su caso
puede utilizar Docker Desktop completamente gratis porque está como estudiante. Este mensaje debe
tenerse en cuenta si utiliza Docker Desktop en el ordenador de su empresa.
Con estos pasos ha quedado instalado Docker en su ordenador junto con otros componentes que serán
de gran ayuda. En la barra de estado ha sido incluido el icono de Docker que se utiliza para acceder
de forma rápida a las configuraciones.
Al iniciar el sistema aparece el Dashboard de la aplicación, puede que aparezcan múltiples mensajes
de recomendaciones sobre cómo utilizarla, si desea hacerlo no hay problema, tómese su tiempo
explorando las opciones que desee y luego regrese para hacer las primeras pruebas en el Terminal.
El primer comando a utilizar en el Terminal será para conocer la versión de Docker.
Instalando Docker 19
$ docker version
Client:
Cloud integration: v1.0.25
Version: 20.10.16
API version: 1.41
Go version: go1.17.10
Git commit: aa7e414
Built: Thu May 12 09:20:34 2022
OS/Arch: darwin/amd64
Context: default
Experimental: true
No pase por alto el detalle que Docker le brinda la versión de ambos componentes, el cliente y el
servidor. Ambos elementos fueron explicados en la sección Arquitectura y Componentes.
Si no desea conocer los procesos de instalación para otras distribuciones puede saltar directamente a
la sección Mi primer contenedor.
Estos cinco elementos son un resumen rápido de las principales ventajas de utilizar WSL 2 a la hora
de instalar Docker en Windows. De todas formas, ampliar los conocimientos nunca está de más, si
desea leer más al respecto le recomiendo consultar este artículo²⁶ en el blog oficial de Docker.
Tomando como base este análisis, la instalación a realizar en el libro será la descrita para WSL2 con
la distribución Ubuntu. Si de todas formas desea instalar la variante con Hyper-V, puede hacerlo a
través de este enlace²⁷.
Al iniciar el proceso de instalación Docker evalua si tiene configurado correctamente WSL 2 y le pide
instalar los componentes de Windows necesarios para utilizarlo. Garantice que la casilla de selección
esté marcada, luego dele al botón de aceptar. El proceso debe demorar menos de un minuto y al final
le muestra el mensaje “Instalation succeeded”.
Localice e inicie la aplicación Docker Desktop desde el menú de inicio. Es posible que aparezca
un mensaje diciendo que no tiene permisos para utilizar Docker. Si le sucede esto debe ir a la
“Administración de equipos > Usuarios y grupos locales > Usuarios”, seleccione su usuario y agregue
el grupo docker-users en la sección “Miembro de”. Reinicie el ordenador y vuelva a abrir la aplicación
Docker Desktop.
Una vez iniciado el sistema aparece la ventana de términos y condiciones definidos por Docker. Esta
ventana está relacionada con los aspectos vistos en la sección Precios y suscripciones. En su caso puede
aceptar sin problemas porque en su condición de estudiante no tiene que realizar ningún pago.
Al aceptar los términos y condiciones Docker inicia sus procesos, abre la ventana principal con el
Dashboard y le brinda la opción de conocer algunos tips y sugerencias. Si lo desea, tómese unos
minutos conociendo las últimas características incluidas en la aplicación, pero luego pase a revisar si
está activado WSL 2 en la sección Settings.
Instalando Docker 22
Ahora que está confirmado el uso de WSL 2 puede iniciarlo y ejecutar el primer comando de Docker
para conocer la versión que ha sido instalada.
$ docker version
GitCommit: v1.1.1-0-g52de29d
docker-init:
Version: 0.19.0
GitCommit: de40ad0
No pase por alto el siguiente detalle, Docker le brinda la versión de ambos componentes, el cliente y
el servidor, ambos elementos fueron explicados en la sección Arquitectura y Componentes.
Si no desea conocer los procesos de instalación para otras distribuciones puede saltar directamente a
la sección Mi primer contenedor.
Requisitos previos
Antes de instalar DD4L compruebe que cumple con los siguientes requerimientos:
* Kernel de 64 bit y habilitado para virtualización con KVM.
* Tener instalado QEMU 5.2 o una versión superior.
* Entorno gráfico Gnome o KDE.
* Tener mínimo 4GB de RAM.
DD4L funciona en una máquina virtual dentro de su ordenador, de aquí la necesidad de tener
habilitado la virtualización. Si desea conocer los motivos de este funcionamiento consulte el enlace
en el sitio oficial³².
Confirme que tiene habilitado correctamente los módulos de KVM antes de iniciar la instalación.
kvm_amd 167936 0
ccp 126976 1 kvm_amd
kvm 1089536 1 kvm_amd
irqbypass 16384 1 kvm
³¹https://docs.docker.com/desktop/linux/install/#supported-platforms
³²https://docs.docker.com/desktop/linux/install/#why-docker-desktop-for-linux-runs-a-vm
Instalando Docker 24
Instalación de DD4L
Descargue el fichero de instalación correspondiente con su distribución desde el sitio oficial³³. Luego
siga los pasos de la instalación según corresponda, enlace³⁴. Es muy posible que necesite instalar
alguna dependencia adicional durante el proceso, si es el caso identifique cuáles son a través de los
mensajes de su Terminal y complete la guía.
Después de instalar Docker Desktop solamente queda buscar la aplicación instalada e iniciar su
ejecución. La primera pantalla en mostrarse será la de términos y condiciones, la cual hace referencia
a las suscripciones analizadas en la secciones anteriores.
Una vez aceptados los términos y condiciones aparece el Dashboard o cuadro de mando de Docker.
Utilice unos minutos para explorar las opciones existentes o ajustar los recursos utilizados, luego
pasemos a ejecutar el primer comando con docker para conocer la versión que ha sido instalada.
Abra su Terminal y ejecute el siguiente comando.
$ docker version
No pase por alto el siguiente detalle, Docker le brinda la versión de ambos componentes, el cliente y
el servidor, ambos elementos fueron explicados en la sección Arquitectura y Componentes.
Si no desea conocer los procesos de instalación para otras distribuciones puede saltar directamente a
la sección Mi primer contenedor.
• Consulte los pasos necesarios para la instalación en la distribución que esté utilizando: CentOS³⁷,
³⁵https://docs.docker.com/engine/
³⁶https://github.com/docker/docker-install
³⁷https://docs.docker.com/engine/install/centos/
Instalando Docker 26
Le recomiendo ejecutar el script en modo DRY para conocer las configuraciones a realizar en su
ordenador.
Examine los comandos para detectar comportamientos no deseados. Si está de acuerdo con el
procedimiento repita el comando sin la variable DRY_RUN:
$ sudo sh ./get-docker.sh
(Opcional) Utilice los siguientes comandos para habilitar el inicio automático de Docker en aquellos
sistemas operativos que lo necesiten.
$ docker version
No pase por alto el detalle donde Docker le brinda la versión de ambos componentes, el cliente y el
servidor. Recuerde que ambos elementos fueron explicados en la sección Arquitectura y Componentes.
Instalando Docker 28
$ docker version
Para utilizar usuarios no-root debe crear un grupo Unix con el nombre docker y luego adicionar su
usuario a este grupo. Utilice los siguientes comandos para realizar esta configuración.
Crear grupo Unix. Puede darse el caso donde el grupo haya sido creado durante el proceso de
instalación. En ese caso puede seguir con el próximo comando.
Ahora debe salir y entrar del Terminal con su usuario para hacer efectivo el cambio. Después de esta
acción vuelva a utilizar el comando docker version y compruebe que el error ha desaparecido.
Instalando Docker 29
Mi primer contenedor
En casi todas las herramientas existe un ejemplo sencillo de hacer para mostrar el correcto funciona-
miento del sistema, el conocido ¡Hola Mundo!. Como no podía ser menos, en esta sección tiene lugar
el ejemplo ¡Hola Mundo! para Docker, y el comando a utilizar es el siguiente:
En este simple comando se pueden ver muchos de los conceptos asociados a los contenedores, disfrute
de este primer momento donde pone en funcionamiento su primer contenedor.
El mensaje mostrado en pantalla va a ser similar a:
To try something more ambitious, you can run an Ubuntu container with:
docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/
Si ha recibido este mensaje significa que todo está funcionando a las mil maravillas. ¡Enhorabuena
por su primer contenedor!
En los próximos capítulos se van a explicar los detalles de lo sucedido para lograr obtener este
resultado.
Instalando Docker 30
Vista de la arquitectura
Se explicaron las suscripciones existentes en Docker, así como también cuál debe ser utilizada según
el entorno. En este punto, lo más importante es recordar que al estar como estudiante no tiene que
realizar ningún pago por el uso de Docker. Puede seguir utilizándolo con toda confianza.
Se mostraron los pasos a seguir a la hora de instalar Docker en Windows, Mac y Linux. Cada sistema
operativo tiene sus características, por lo tanto siempre existen diferencias entre ellos, pero lo que
es común es el poder utilizar Docker Desktop como herramienta para el desarrollo de aplicaciones
y microservicios utilizando en contenedores. Por otro lado, para entornos sin interfaz gráfica se
recomienda instalar Docker Engine.
Después de la instalación de Docker se puso en funcionamiento el primer contenedor “Hola Mundo”.
Este viaje no ha hecho más que empezar, con Docker instalado en su ordenador no hay nada que le
impida avanzar con los contenedores. Tenga listo su Terminal y siga al próximo capítulo.
Primeros pasos con Docker
La instalación de Docker es la llave a un nuevo mundo de comandos, de repente tiene cientos de
opciones para gestionar contenedores. Es normal tener dudas por donde empezar, pero pasa eso
estamos aquí, para dar los primeros pasos poco a poco.
El punto de partida va ser comprender la estructura de comandos propuesta por Docker. Es imposible
aprenderse todos los comandos de memoria, pero si puede entender cómo están agrupados y a cuál
concepto dan solución.
Luego se pasa a explicar en detalle el ejemplo “Hola Mundo”. La explicación permite llevar a la práctica
los conceptos de imágenes y contenedores a través de las rutinas de Docker.
Conocerá también los registros de imágenes, cuáles son los más utilizados y cómo realizar la búsqueda
de imágenes. Son muchos los registros de imágenes que existen actualmente, pero en este capítulo
serán mencionados los más relevantes.
De igual forma aprenderá a interpretar la estructura del nombre de las imágenes. Con este conoci-
miento podrá saber de forma rápida dónde está almacenada la imagen, el usuario que la ha creado y
la versión del software que lleva dentro. Después de conocer la estructura del nombre de las imágenes
pasará a dominar los comandos básicos para gestionar imágenes y contenedores. Es este punto se hace
referencia a acciones muy repetidas como son listar, eliminar y descargar contenidos.
Por último, pero no menos importante, aprenderá que existen múltiples variantes para manipular la
información de salida de los comandos de Docker. A través del parámetro --format podrá dar rienda
suelta a su imaginación en el trabajo con los contenedores.
Sin más dilación, ¡pasemos al primer tema!
Estructura de comandos
Docker cuenta con cientos de comandos en el Terminal si se tienen en cuenta todas las posibles com-
binaciones (imágenes, contenedores, redes, volumes, etc). En un inicio esto puede parecer abrumador,
pero el objetivo de esta sección es lograr identificar el mecanismo que permita saber cómo llegar al
comando deseado sin tener que memorizarlos todos.
Para lograr este objetivo lo primero va a ser dominar la ayuda que brinda Docker a través del
parámetro --help. Con este parámetro puede conocer los principales subcomandos existentes. Va a ser
importante tener esta opción en la mente todo el tiempo, porque Docker se actualiza múltiples veces
en el año, y seguir al detalle todos sus cambios puede ser difícil. De ahí la necesidad de consultar la
ayuda siempre que necesite saber las opciones disponibles.
Primeros pasos con Docker 32
El comando | less le permite ver todos los comandos de Docker desde el inicio, pudiendo desplazarse
hacia arriba y hacia abajo según necesite. Veamos a continuación cómo interpretar el resultado de
utilizar este primer comando.
Options:
--config string Location of client config files ...
-c, --context string Name of the context to use to ...
-D, --debug Enable debug mode
-H, --host list Daemon socket(s) to connect to
-v, --version Print version information and quit
...
No se desespere si no sabe cómo llevarlos a la práctica en este momento, porque durante el transcurso
de los capítulos se van a utilizar de una u otra forma.
Comandos de gestión
Los comandos de gestión agrupan los conceptos existentes en Docker. Detrás de cada comando
de gestión existen nuevos comandos especializados. Entre los grupos más utilizados están image y
container.
Primeros pasos con Docker 33
Management Commands:
builder Manage builds
buildx* Docker Buildx (Docker Inc., v0.7.1)
compose* Docker Compose (Docker Inc., v2.2.3)
config Manage Docker configs
container Manage containers
context Manage contexts
image Manage images
...
Para conocer los comandos que se encuentran detrás de un grupo de gestión puede utilizar el
parámetro --help, pero esta vez después del comando de gestión. El ejemplo que se muestra a
continuación corresponde al comando de gestión image.
Manage images
Commands:
build Build an image from a Dockerfile
history Show the history of an image
import Import the contents from a tarball to create a filesystem image
inspect Display detailed information on one or more images
load Load an image from a tar archive or STDIN
ls List images
...
Como puede observar, las acciones mostradas corresponden con el concepto de imagen, como por
ejemplo: construir, obtener, listar o importar.
Comandos individuales
Después de los comandos de gestión viene la sección Commands. En este apartado va a encontrar
comandos que fueron incluidos en las primeras versiones de Docker, se puede imaginar que estamos
hablando de hace más de ocho años.
En las primeras versiones de Docker existían pocos comandos y estaban todos el mismo nivel, pero al
pasar el tiempo nuevas funcionalidades llegaron y fue necesario encontrar la forma de organizarlos, de
ahí que aparecieron los Comandos de gestión. En aras de mantener la compatibilidad con los sistemas
Primeros pasos con Docker 34
que estaban en funcionamiento Docker tomó la decisión de mantener los comandos iniciales, pero los
nuevos comandos fueron organizadas en grupos según su función.
Como resultado final puede ver funciones en Docker que tienen asociadas dos comandos con el mismo
resultado. Por ejemplo, si desea listar los contenedores puede:
Comando directo:
$ docker ps
$ docker container ls
Siguiendo la estructura de comandos puede imaginar lo sucedido. Se le indica a docker que inicie
un contenedor (container run) utilizando la imagen hello-world. Docker busca la imagen en su
ordenador, pero al no encontrarla sale a Internet para descargarla.
Al terminar la descarga docker muestra el código sha256 de la imagen, este código es único para cada
una.
Después de descargar la imagen, docker inicia un contenedor a partir de ella. El contenedor iniciado
imprime en pantalla el texto del saludo, cuando termina de imprimirlo el contenedor se detiene
automáticamente.
A grandes rasgos es un procedimiento sencillo y fácil de entender. Sin embargo, hay mucho trabajo
por detrás para garantizar que las cosas difíciles parezcan fáciles. Dominar este flujo es de vital
importancia para seguir avanzando en el mundo de los contenedores.
Primeros pasos con Docker 36
Registros de imágenes
De la sección anterior quedó un misterio sin resolver, ¿de dónde se ha descargado Docker la imagen
hello-world?
Todas las imágenes de contenedores se guardan en almacenes digitales llamados Registros. Uno de los
registros más utilizados es Docker Hub⁴⁵ y ha sido desde este lugar donde Docker ha descargado la
imagen hello-world.
En Docker Hub existen cientos de miles de imágenes. Piense en una herramienta o sistema que utilice
a diario y búsquela en Docker Hub, va a tener una gran probabilidad de que exista. La mayoría de
comunidades comparten sus productos como contenedores en esta gran plataforma.
Las imágenes en Docker Hub pueden ser buscadas de dos formas, a través de su sitio web o desde el
Terminal con líneas de comandos. Veamos a continuación ambos procedimientos.
Una vez identificada la imagen acceda a ella para ver los detalles de su funcionamiento, su última
fecha de actualización y los comentarios de los usuarios que la han utilizado. Dedique unos minutos
⁴⁵https://hub.docker.com/
Primeros pasos con Docker 37
en descubrir los elementos presentes en ella, note que no es necesario tener una cuenta en la plataforma
Docker Hub para realizar las búsquedas.
El equipo de Docker ha ido creando etiquetas para clasificar las imágenes almacenadas en el Hub.
Esta clasificación permite a los usuarios saber de forma rápida algunas características de la imagen
buscada. En este momento existen tres etiquetas:
• Official Image⁴⁶: la imagen está bajo el mantenimiento de la empresa Docker. Esta etiqueta
brinda garantía de calidad, buenas prácticas y actualización.
• Verified Publisher⁴⁷: la imagen es publicada y mantenida por la entidad comercial dueña del
producto. La imagen cuenta con garantía de seguridad y mantenimiento por parte del dueño.
• Open source program⁴⁸: identifica los proyectos alineados con la comunidad open source con
fines no comerciales. Docker les brinda apoyo a estos proyectos en la plataforma para que
puedan seguir creciendo y desarrollándose.
Las imágenes sin etiquetas también pueden ser utilizadas, no hay ningún problema en esto. La
diferencia radica que debe ser usted quien analice la calidad y mantenimiento del producto.
El comando search realiza la búsqueda solamente en el registro Docker Hub. Puede identificar en los
resultados algunos de los términos ya explicados en los párrafos anteriores, como por ejemplo Official.
⁴⁶https://docs.docker.com/docker-hub/official_images/
⁴⁷https://docs.docker.com/docker-hub/publish/
⁴⁸https://www.docker.com/community/open-source/application/
Primeros pasos con Docker 38
Al igual que con el resto de comandos, puede hacer uso del parámetro --help o de su referencia en el
sitio web⁴⁹ para ampliar la información sobre search.
Un ejemplo interesante puede ser filtrar la búsqueda y mostrar solamente las imágenes oficiales.
Utilice el comando que aparece a continuación para lograrlo.
Debe valorar todos los elementos de conjunto y tomar una decisión al respecto. Esta no es una decisión
crítica, en el momento que lo desee puede cambiar a otro registro si lo considera necesario.
Registro
El registro es donde está almacenada la imagen. La dirección web utilizada brinda una pista del lugar
o ubicación del registro, p.ej:
Existen muchos otros registros, pero usted podrá identificarlos poco a poco según lo necesite.
Nombre
El nombre de la imagen permite identificar de forma única el elemento dentro del registro. Muchas de
las plataformas públicas incluyen el identificador del usuario como prefijo al nombre de la imagen, de
esta manera permiten a diferentes usuarios tener el mismo nombre de imagen sin dejar de ser únicas
en la plataforma.
En la imagen anterior se puede observar el uso de mmorejon delante del nombre erase-una-vez-1,
teniendo como resultado de nombre mmorejon/erase-una-vez-1.
Repositorio
El repositorio es la unión del registro y el nombre. Este valor es el mostrado en la columna
REPOSITORY cada vez que se listen las imágenes con el comando docker.
Aunque esta sea la regla general, existen casos interesantes que no siguen este formato. El primero
corresponde con las imágenes oficiales en Docker Hub. Estas imágenes utilizan la palabra library en
vez del identificador del usuario, y no muestran el registro ni el prefijo en la columna REPOSITORY.
p.ej.
Primeros pasos con Docker 40
Etiqueta
Las imágenes de Docker, al igual que los sistemas de software, pueden tener múltiples versiones, y
cada versión es marcada con una etiqueta (tag).
La intención de etiquetar las imágenes corresponde con el deseo de asociar cada imagen con la versión
del producto que lleva dentro. Recuerde que el propósito principal de Docker, y en general de los
contenedores, es empaquetar, distribuir y ejecutar aplicaciones, por lo tanto es muy conveniente hacer
uso de las etiquetas para saber cuál versión de la aplicación se está ejecutando.
Ejemplos de etiquetas: v0.3.0, latest, alpine.
En el mundo de los contenedores la etiqueta latest está reservada para representar la ausencia
de etiquetas. En el momento que se incluya una imagen en el repositorio, si no se especifica una
etiqueta, Docker incluirá automáticamente latest como parte del nombre. De esta forma se garantiza
la existencia de una etiqueta en la estructura del identificador.
A la hora de descargar las imágenes se utiliza la misma filosofía. Si no se incluye una etiqueta se utiliza
latest por defecto.
$ docker image ls
Al ver el resultado le puede resultar familiar la columna REPOSITORY donde se muestra el identifi-
cador de la imagen (registro + nombre), así como también la columna TAG, haciendo referencia a la
versión de a la imagen.
El próximo comando a mostrar se utiliza para descargar imágenes. Es necesario conocer el identi-
ficador de la imagen antes de utilizarlo. En el ejemplo que se muestra a continuación se descargan
tres imágenes, dos versiones de Nginx en Docker Hub y la aplicación erase-una-vez-1 ubicada en el
registro de GitHub.
Liste nuevamente las imágenes cuando termine la descarga y compruebe que están todas las imágenes
mencionadas en el comando anterior. Docker también brinda la opción de filtrar el listado utilizando
el valor de la columna REPOSITORY. Si conoce el nombre del repositorio que está buscando agréguelo
después del comando list.
En este momento ya sabe buscar, descargar y listar las imágenes, pero para cerrar el ciclo de gestión
de imágenes solamente falta saber eliminarlas. Siguiendo la filosofía de comandos, no le va ser difícil
entender la siguiente instrucción para borrar imágenes:
Untagged: nginx:1.20.2
Untagged: nginx@sha256:02923d65cde08a49380ab3f3dd2f8f90aa51fa2bd358bd85f89345848f6e6623
Deleted: sha256:e08034998ac81e1cf75ce5bd3fc94630787e21d246cd5717792792d1bc84f552
Deleted: sha256:1c56e3672ee621273351c6a99d25cddae716b92d37d34d82794bde5f9ced57e2
Deleted: sha256:e4ea413e433348a39aae1661bd2088d381809247b2c0f04f45b6d0b2d8e55290
Deleted: sha256:9ea9d1dae7621e94766874a9cebad149a8ba331f40c427a46fd3e132cf0698b7
Deleted: sha256:8d7af4642159890ee64f7b6508f5cb82f9e3f8a6598f529d8103498430af5a6d
Deleted: sha256:057b3b84edcb5fe9c5d2b2828e306dbb734423a3a49d6e4c8bb15e2f64101d10
Primeros pasos con Docker 42
De esta manera puede eliminar las imágenes y liberar el espacio en disco. El único requisito es conocer
el nombre completo de la imagen que desee borrar. Liste nuevamente las imágenes para comprobar
que nginx:1.20.2 ha sido eliminada de la máquina.
Existen muchos otros comandos interesantes relacionados con las imágenes, pero recuerde que el
capítulo se llama Primeros pasos con Docker, por lo tanto este es solo el comienzo. A medida que
avancen los capítulos se van a ir mostrados nuevos comandos y funciones de Docker.
$ docker container ls
En el ejemplo realizado con la imagen hello-world, el contenedor termina justo después de mostrar
el mensaje en consola, por lo tanto, su estado es exited y no aparece en el listado. Si desea listar
contenedores sin importar su estado agregue el parámetro --all.
Docker mantiene el registro del contenedor aunque haya finalizado, pero si desea hacer limpieza en
su ordenador puede utilizar el comando rm para eliminar completamente el registro del contenedor.
fa668052b884
Fíjese que después del comando rm se ha incluido el identificador del contenedor (CONTAINER ID).
Este valor es único para cada contenedor.
En este punto usted es capaz de gestionar imágenes y contenedores. Le recomiendo que utilice unos
minutos para ejercitar los comandos vistos con otras imágenes que sean de su interés. Estos ejercicios
individuales le van a ayudar a fortalecer los conocimientos vistos aprendidos.
Primeros pasos con Docker 43
Cualquiera de estas razones, o alguna otra que no haya sido incluida, puede derivar en el deseo
de modificar el formato de salida de los comandos en Docker. Para suerte de todos Docker tiene
el mismo razonamiento, y para mostrarlo ha incluido el parámetro --format, permitiendo realizar
modificaciones en el formato de salida.
El parámetro --format utiliza la sintaxis de plantilla Golang (Go Template⁵⁷), la cual permite hacer uso
de la estructura {{ }} para crear instrucciones adaptadas a sus necesidades. Veamos a continuación
algunos ejemplos de cómo utilizar el parámetro --format.
$ docker system info --format 'El número de CPU disponible es: {{ .NCPU }}'
Ciertamente ahora está en formato JSON, pero todavía es difícil de ver. Realice una segunda mejora
dándole el formato correcto a la salida utilizando la herramienta jq⁵⁸.
{
"ID": "....",
"Containers": 5,
"ContainersRunning": 0,
...
}
¡Mucho mejor! Busque el identificador que más le guste e intente obtenerlo de forma individual como
mismo se hizo con el primer ejemplo.
⁵⁸https://stedolan.github.io/jq/
Primeros pasos con Docker 45
{
"Command": "\"/hello\"",
"CreatedAt": "2022-03-02 15:08:22 +0100 CET",
"ID": "b6d1f9e5d1ac",
"Image": "hello-world",
"Labels": "",
"LocalVolumes": "0",
"Mounts": "",
"Names": "wonderful_raman",
"Networks": "bridge",
"Ports": "",
"RunningFor": "29 seconds ago",
"Size": "0B (virtual 9.14kB)",
"State": "exited",
"Status": "Exited (0) 29 seconds ago"
}
Ahora intente crear la salida en tabla utilizando el identificador y el nombre del contenedor solamente.
CONTAINER ID NAMES
b6d1f9e5d1ac wonderful_raman
¡Sencillo verdad! Ha sido fácil cambiar el formato de los resultado. De ahora en adelante utilice los
diferentes formatos y saque el mayor provecho posible de Docker. En los próximos capítulos se va a
hacer uso del formato json para mostrar los datos relevantes en cada ejercicio.
Por último, se explicaron las diferentes formas que brinda Docker para modificar el formato de salida
en los comandos. Esta opción es muy útil si desea combinar la información brindada por Docker con
sistemas de infraestructura o con procesos de integración continua.
Con los conocimientos vistos en el capítulo ha empezado su viaje en el mundo de los contenedores,
pero todavía quedan muchos aspectos importantes a incorporar, como por ejemplo:
Continue leyendo el próximo capítulo para aprender nuevos conocimientos y lograr dar respuesta a
estas preguntas.
¡Mucho ánimo que vas bien!
Rutinas con los contenedores
Las actividades realizadas a diario con los contenedores no son diferentes a las que hacemos
actualmente con el resto de sistemas, simplemente ha cambiado la forma de hacerlo.
Cuando se desea poner en funcionamiento un sistema, el primer paso es ir al sitio oficial y conocer las
instrucciones de su instalación. Luego se descarga el fichero de la aplicación, se instala y se inicia de
la forma más fácil posible. Una vez que el sistema está en funcionamiento, se analizan las variantes
de configuración y se consultan los registros (logs) generados.
En el caso de los contenedores sucede de forma muy similar. Se descarga la imagen que contiene la
aplicación y luego se pone en funcionamiento el contenedor de la forma más fácil posible. Luego es
necesario conocer las opciones que brinda el sistema para su configuración y los registros generados
durante su funcionamiento.
Para lograr realizar estas acciones con los contenedores necesita incluir un nuevo grupo de comandos
de Docker. Cada nuevo comando incluye nuevos parámetros, pero no se preocupe, los pasos van a ser
dados poco a poco para no perder ningún detalle.
Durante el capítulo se le va a dar respuesta a las siguientes preguntas.
Le recomiendo tener listo su Terminal para ponerse manos a la obra, ya que todos los casos a analizar
incluyen ejercicios prácticos.
Componentes de la imagen
Puede utilizar el comando inspect para listar todas la información disponible. El ejemplo mostrado
continuación utiliza la imagen hello-world.
{
"Id": "sha256:18e5af...",
"RepoTags": [
"hello-world:latest"
],
"RepoDigests": [
"hello-world@sha256:4c5f3db4..."
],
"Parent": "",
...
}
En el ejemplo anterior se han utilizado las técnicas de formato explicadas en el capítulo anterior.
Dentro del grupo de elementos listados existen dos valores con la misión de establecer la instrucción
de inicio del contenedor, estos valores son Entrypoint y Cmd. La tabla que aparece a continuación le
va a mostrar el significado de cada uno.
Comando de inicio
Estos dos valores trabajan de conjunto para establecer la instrucción de inicio en los contenedores,
el Entrypoint define el comando y el Cmd aporta los parámetros. Utilice nuevamente el comando
inspect para saber cómo ha sido configurada la imagen hello-world, en esta ocasión se muestra el
subconjunto Config porque es el lugar donde están estos valores.
{
...
"Cmd": [
"/hello"
],
"Entrypoint": null,
...
}
En esta ocasión la imagen no tiene un valor asignado en el campo Entrypoint, por lo tanto el primer
elemento del campo Cmd es utilizado como comando de inicio. Todos los contenedores creados con
esta imagen van a tener como inicio del binario hello. Analice un ejemplo similar, pero cambiando la
imagen.
{
...
"Cmd": null,
"Entrypoint": [
"erase-una-vez-1"
],
...
}
En esta ocasión el campo Entrypoint no está vacío, por lo tanto el comando de inicio es la ejecución del
binario erase-una-vez-1. Este comando no tiene asociado parámetros adicionales porque no existen
Rutinas con los contenedores 50
valores en el campo Cmd. Como puede observar, con tener valores en al menos uno de estos campos
el contenedor puede iniciarse sin problemas.
Un tercer ejemplo muy interesante es el comando de inicio de la imagen Nginx.
{
...
"Cmd": [
"nginx",
"-g",
"daemon off;"
],
"Entrypoint": [
"/docker-entrypoint.sh"
],
...
}
Aquí ambos campos cuentan con información, por lo tanto el resultado es la unión de ambos. Lo
primero en ejecutarse es el fichero docker-entrypoint.sh, este es un script que realiza algunas
sustituciones y configuraciones necesarias para Nginx. Una vez terminado el script se inicia el
comando nginx utilizando como parámetros -g daemon off;.
Como puede percatarse, las alternativas de configuración con estos campos pueden ser variadas. De
ahí la importancia de saber inspeccionar las imágenes para conocer su configuración de inicio. Le
propongo, ahora que tiene nuevos conocimientos, realizar el mismo análisis del comando de inicio
con otras imágenes de su interés. Este ejercicio le permitirá ganar confianza con el comando inspect.
Imagen ubuntu:22.04
La imagen ubuntu:22.04 no tiene definido Entrypoint, y su Cmd es /bash. Inicie el contenedor
utilizando los valores predeterminados. Una vez ejecutado el comando bash, el contenedor terminará
sin mostrar nada en el Terminal.
Ahora cambie el comando Cmd, la nueva orden va a ser listar los elementos presentes en la raíz del
sistema. Los nuevos comandos tienen que ser incluidos después del nombre de la imagen.
total 56
drwxr-xr-x 1 root root 4096 Mar 16 10:05 .
drwxr-xr-x 1 root root 4096 Mar 16 10:05 ..
-rwxr-xr-x 1 root root 0 Mar 16 10:05 .dockerenv
...
Imagen nginx:1.21.6
Esta imagen tiene definido ambos comandos, Entrypoint y Cmd. Utilizando los valores por defecto va
a poder observar el inicio del servidor Nginx.
Rutinas con los contenedores 52
Detenga el servicio (Ctr+C) y cambie el comando Cmd para mostrar la versión de Nginx.
...
nginx version: nginx/1.21.6
El inicio del comando ha sido similar al anterior. Los mensajes mostrados en pantalla son iguales a los
ejemplos anteriores, pero la diferencia aparece al final mostrando la versión de Nginx. Al no cambiar
el valor del Entrypoint, el arranque sigue siendo el mismo. La modificación viene después, donde se
muestra la versión del programa en vez del inicio del servidor web.
total 76
drwxr-xr-x 1 root root 4096 Mar 16 11:05 .
drwxr-xr-x 1 root root 4096 Mar 16 11:05 ..
-rwxr-xr-x 1 root root 0 Mar 16 11:05 .dockerenv
drwxr-xr-x 2 root root 4096 Feb 28 00:00 bin
...
En este ejemplo se ha establecido como comando Entrypoint ls y luego para el Cmd se ha pasado
como parámetro -la /. Como resultado final se han listado los elementos del directorio raíz.
Como puede apreciar se puede cambiar tanto el Entrypoint como el Cmd, pero le recomiendo hacer
uso del Cmd siempre que sea posible.
Rutinas con los contenedores 53
Ahora que conoce los nuevos parámetros, utilice la imagen de Ubuntu Server para acceder de forma
interactiva.
root@6fe8bdfb8353:/#
Una vez dentro del contenedor ejecute los comandos que desee, por ejemplo:
$ cat /etc/os-release
$ apt update
No tenga miedo de romper o eliminar ficheros dentro del contenedor, recuerde que si algo va mal
siempre puede salir con el comando Exit y empezar de nuevo.
Un detalle importante a tener en cuenta en el modo interactivo, solo puede ser utilizado con los
comandos bash, sh u otro similar. Si utiliza un comando diferente a los mencionados no podrá
acceder al contenedor. En el ejemplo realizado con la imagen Ubuntu Server ha funcionado porque la
instrucción existente en Cmd es bash.
Para poner en práctica lo mencionado anteriormente realice el siguiente comando. Fíjese que el valor
Cmd ha sido modificado para establecer ls en lugar de bash. El resultado va a ser listar las carpetas,
pero sin acceder al contenedor.
Rutinas con los contenedores 54
Realice un ejercicio similar con la imagen Nginx, esta vez se ha modificado el Cmd para establecer el
comando bash.
root@faf47b73cac1:/#
4fce1b15136d715d7499aaa7a0ad9318f7fd4439d6486924549828f704d2c474
Después de ejecutar el comando vuelve a tener control sobre su Terminal. Puede realizar nuevas tareas
con total tranquilidad sin afectar su servicio. Intente listar todos los contenedores en ejecución como
parte del ejemplo, en el resultado va a aparecer el contenedor iniciado previamente.
Rutinas con los contenedores 55
$ docker container ls
Va a llegar el momento donde tenga muchos contenedores en el listado, cada uno funcionando de
forma independiente, pero ¿qué ha pasado con los registros (logs) que se veían en el Terminal? ¿Cómo
se pueden ver los registros para cada contenedor?
Avance a la próxima sección para dar respuesta a estas preguntas sobre los logs en los contenedores.
Para analizar los logs de una aplicación lo primero es tener la aplicación, ¿cierto? Puede hacer uso del
contenedor iniciado anteriormente, pero si no lo tiene disponible inicie otro con el siguiente comando.
cd709d41f9ac0415010ce803a61c36cd439064f94ff0c09d23128482a7b94900
Una vez creado el contenedor puede pasar a consultar los registros. El único parámetro obligatorio en
el comando es el identificador del contenedor.
Rutinas con los contenedores 56
Como resultado va a obtener los mensajes generados por la aplicación. El mensaje es la unión del
hostname del contenedor junto con el texto erase-una-vez. El valor del hostname va a coincidir con
el identificador del contenedor.
También note el uso del parámetro --follow, este valor le va a permitir quedarse a la espera de
los próximos mensajes que genere el contenedor. Pruebe ejecutar el comando logs sin utilizar
este parámetro para notar la diferencia entre ambos. Para salir del control de la consola utilice la
combinación de teclas Ctr+C.
El resto de parámetros disponibles para el comando logs permiten hacer algunos trucos adicionales,
como por ejemplo:
Anímese a utilizar alguno de estos nuevos parámetros y compare los resultados. Recuerde, la
curiosidad es la mejor compañía durante el estudio de nuevas tecnologías.
Fíjese en los detalles, casi todos los recursos muestran el valor de consumo actual, pero también el
límite que pueden llegar a alcanzar. Ambos datos son muy importantes, en ocasiones los sistemas
fallan por la falta de recursos en la máquina, tener ambos valores le permite identificar el problema
y encontrar una solución de forma rápida. Tenga esto presente cuando utilice los contenedores en
entornos reales.
Con el comando stats puede conocer el consumo de la CPU y la RAM, pero falta algo más, el espacio
en disco. Este último recurso se puede mostrar con el comando df, perteneciente al grupo system.
$ docker system df
Como puede observar, aparece el espacio utilizado agrupado por conceptos. En este momento le es
familiar Images y Containers, pero los demás van a ser incluidos en próximos capítulos.
Analice la columna RECLAIMABLE, los valores mostrados indican el espacio que posible de recuperar
si se realizan acciones de mantenimiento o limpieza. Los comandos de mantenimiento en Docker son
de gran importancia para liberar espacio y evitar saturaciones con el almacenamiento.
Con la ayuda de los comandos mostrados va a poder monitorizar los recursos utilizados por los
contenedores, utilizar solamente aquello que necesitas es una virtud.
Rutinas con los contenedores 58
Comando de inicio
Rutinas con los contenedores 59
También se explicó la función del parámetro --detach del comando run. Iniciar los contenedores en
segundo plano (background) le brinda independencia al servicio durante su funcionamiento. Es la
configuración recomendada para todo los contenedores que desee iniciar.
Dar seguimiento al estado de los servicios es vital para detectar errores de forma temprana. En este
punto su mejor aliado es el comando logs, brindando un forma sencilla y rápida para visualizar todos
los mensajes generados por los contenedores. Por último, pero no menos importante, se mostraron los
comandos stats y system df para conocer los recursos utilizados por los contenedores.
Los comandos vistos durante el capítulos constituyen las rutinas más repetidas al trabajar con
contenedores. Le recomiendo dedicar tiempo para practicarlas con diferentes imágenes.
En el próximo capítulo conocerá sobre el funcionamiento de las redes en Docker y cómo acceder a
sus aplicaciones a través de la gestión de puertos.
¡Mucho ánimo y siga adelante!
Publicar y consumir servicios
La comunicación y el acceso entre contenedores son piezas fundamentales en Docker. Garantizar
el aislamiento de los procesos a través de las redes es un requisito si está interesado en gestionar
correctamente los servicios.
El primer paso fue iniciar múltiples contenedores independientes, pero el siguiente es poder acceder
a ellos. Esta comunicación puede establecerse a través del navegador, pero también desde otro
contenedor iniciado en la misma máquina. En ambos casos, va a necesitar los conocimientos en redes
de Docker para poder llevarlo a cabo correctamente.
Acceso a un contenedor
Utilice el gestor de comandos network para listar las redes creadas de forma predeterminada. Si en su
listado aparecen más de tres elementos significa que ha utilizado esta funcionalidad en otro momento,
o ha sido creado por alguna otra aplicación que utilice las redes de Docker.
$ docker network ls
Los controladores bridge, host y null los instala Docker. Cada uno tiene un identificador similar a los
identificadores de los contenedores. Antes de pasar a la descripción de los controladores, le propongo
dar una mirada a los comandos disponibles para gestionar redes.
Manage networks
Commands:
connect Connect a container to a network
create Create a network
disconnect Disconnect a container from a network
inspect Display detailed information on one or more networks
ls List networks
prune Remove all unused networks
rm Remove one or more networks
Controlador bridge
Este es el controlador más utilizado por los servicios, de hecho es el controlador por defecto. La
traducción del nombre es puente, y su funcionamiento consiste en crear un segmento de red para
aislar los contenedores del resto de servicios. La forma rápida de mostrar la configuración de este
segmento de red es utilizando el comando inspect.
En la sección IPAM (IP Address Management) se muestra el rango de IPs reservadas. Todos los
contenedores iniciados con este controlador van a obtener una IP en este rango; a no ser que le indique
una configuración diferente.
Publicar y consumir servicios 62
{
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
}
Este controlador va a ser utilizado en el resto de secciones para crear redes y conectar contenedores.
Controlador host
El controlador host no aísla el contenedor, y tampoco le asigna una dirección en un segmento de red.
Este controlador comparte el espacio de red de la máquina que lo ha iniciado. Esto significa que si el
contenedor acepta peticiones por el puerto 80, entonces la máquina donde este el contenedor, también
aceptará peticiones en el mismo puerto.
Existen casos donde le interese utilizar este controlador, por ejemplo, si necesita dar una prioridad al
rendimiento del sistema, o si necesita hacer uso de un gran número de puertos para el servicio. Tenga
especial cuidado con el solapamiento de puertos si inicia múltiples contenedores con este controlador.
Controlador null
El controlador null se utiliza para eliminar todas las configuraciones de red del contenedor. Así de
sencillo, no va a existir comunicación salvo el acceso al contenedor desde la máquina.
Publicar y consumir servicios 63
1d91d577fd22583a3598f16ea48fc6ea39bc2406747df74abb5a960c371aa5de
Como no se ha indicado ningún parámetro de red, el contenedor se ha iniciado en la red por defecto,
bridge. Para conocer la IP asignada al contenedor utilice el comando inspect y reduzca el resultado
al campo IPAddress.
"172.17.0.2"
En esta ocasión se han utilizado dos comandos, el primero obtiene el identificador del último
contenedor iniciado y luego se utiliza este identificador para inspeccionar el contenedor. El resultado
de ambos comandos es la dirección IP del contenedor: 172.17.0.2.
⁵⁹https://github.com/mmorejon/erase-una-vez-2
Publicar y consumir servicios 64
{
"1d91d577fd22583a3598f16ea48fc6ea39bc2406747df74abb5a960c371aa5de": {
"Name": "zen_darwin",
"EndpointID": "ebcb88cc73373b28fb62fdff7928fb64579043d52734f56fe4c9bf4474771cd7",
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": ""
}
}
⁶⁰https://github.com/mmorejon/erase-una-vez-2
Publicar y consumir servicios 65
{
"hostname": "1d91d577fd22",
"message": "érase una vez ..."
}
5d56bd8ad9f9afa69a8b8b0718457bd2527e3e28d23a92e7dcd9fb0e9ee77c4d
Como ya es de costumbre, el resultado en pantalla es el identificador de la nueva red. Ahora liste las
redes para visualizar el elemento creado. Aproveche también para conocer el nuevo segmento de red
asignado.
{
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.20.0.0/16",
"Gateway": "172.20.0.1"
}
]
}
El próximo paso es iniciar la aplicación en modo cliente, pero esta vez indique el uso red second. La
asignación de un contenedor a una red se realiza con el parámetro --network. Fíjese que el resto de
parámetros han permanecido iguales. Recuerde cambiar la IP del servidor según sea el caso.
Las sospechas han sido confirmadas, la aplicación no ha podido realizar la petición al servidor. El
mensaje nos confirma el aislamiento existente entre las redes en Docker, desde el punto de vista de
seguridad, es un elemento bueno contar con este comportamiento.
Publicar y consumir servicios 67
Le invito a realizar un par de pasos más. Inicie otra instancia de la aplicación en modo servidor en la
red second. Luego obtenga la IP del nuevo servidor y vuelva a intentar la comunicación entre ambos
contenedores.
"172.20.0.2"
{
"hostname": "21b6ec53bfa9",
"message": "érase una vez ..."
}
En este segundo intento han podido conectarse los servicios, en este caso el valor del hostname ha
cambiado, ahora corresponde con el último servicio desplegado.
Publicar y consumir servicios 68
0710c5346fd708f6bbaaf456b7fde1f76bb3aba25801e4e32aac3a66e8f7d826
$ curl http://localhost:9000/echo
{
"hostname": "a8d2de470e65",
"message": "érase una vez ..."
}
Seguramente se imagina lo sucedido, pero de todas formas demos algunos detalles. El comando Publish
ha dirigido el tráfico desde puerto 9000 de su máquina, hacia el puerto 8000 del contenedor. El puerto
8000 recibe la petición y envía de regreso el mensaje.
Publicar y consumir servicios 69
Es muy sencillo exponer los puertos de un contenedor, con adicionar el parámetro Publish ha logrado
su objetivo. Este parámetro puede incluirlo en el comando múltiples veces dependiendo del número
de puertos que quiera exponer, si desea exponer tres puertos, debe incluirlo tres veces.
Otro detalle a tener en cuenta es el solapamiento de puertos. Usted es quien indica el puerto del host,
por lo tanto, si intenta iniciar dos contenedores en el mismo puerto recibirá un error. El mensaje de
error va a mencionar que el puerto está en uso.
La relación entre los puertos del host y del contenedor pueden verse al listar los contenedores en
ejecución, aparecen en la columna PORTS.
{
"8000/tcp": [
{
"HostIp": "0.0.0.0",
"HostPort": "9000"
}
]
}
Realice un ejemplo más, esta vez utilice la imagen del servidor Nginx. Luego acceda al siguiente enlace
en su navegador: http://localhost:8080 para comprobar el correcto funcionamiento del servidor web.
Publicar y consumir servicios 70
Con los ejemplos analizados hasta el momento tiene garantizado una gran variedad de casos de uso
de la industria para llevar a la práctica. De forma individual le recomiendo iniciar otros servicios
utilizando el parámetro Publish. La práctica le va a ayudar a fijar los conocimientos.
1. Agregue dos redes a la lista de Docker con los nombres: services y databases.
2. Muestre en pantalla los segmentos de redes asignados a cada una de las redes creadas.
3. Inicie un contenedor en la red databases con la imagen mongo:5.0.6. Luego inicie otro
contenedor utilizando la imagen mongo:5.0.6 en modo cliente y realice una conexión de prueba
con la base de datos MongoDB.
4. Inicie un contenedor en la red services con la imagen mongo:5.0.6 y publicando el puerto
27017 con la máquina local. Luego instale en su ordenador el cliente de MongoDB y realice una
conexión utilizando la dirección del servidor localhost:27017.
¡Enhorabuena por terminar otro capítulo!, pero todavía quedan retos igual de interesantes. En el
próximo capítulo se explica cómo gestionar la persistencia de la información con los contenedores.
Configuraciones y persistencia de
datos
Las configuraciones y la persistencia de la información juegan un papel importante en las aplicaciones.
Ahora que ha dado el salto al mundo de contenedores se hace necesario identificar cómo Docker
gestiona ambos elementos.
La mayoría de sistemas permiten modificar su comportamiento a través de configuraciones. Estas
configuraciones pueden ser encontradas en ficheros para facilitar su acceso y gestión, seguramente ha
accedido a alguno de ellos para cambiar los límites de memoria o para habilitar alguna funcionalidad.
La buena noticia es que estos ficheros van a seguir existiendo en el mundo de los contenedores, pero
va a cambiar la forma de gestionarlos. En este capítulo va a aprender las variantes que existen según
sea el caso de uso.
De igual forma, la persistencia de los datos implica un nivel más en la gestión de contenedores. Muchos
de los sistemas que conoce almacenan información, y este almacenamiento lo realizan mayormente
en estructuras de carpetas y ficheros en la máquina donde se ejecutan. Un ejemplo de estos sistemas
son las base de datos.
Las dos problemáticas mencionadas encontrarán respuestas a lo largo del capítulo, así como también
podrá responder las siguientes preguntas:
El almacenamiento en los sistemas es crítico por el valor de los datos hoy en día. Siga adelante con el
capítulo y descubra cómo abordar esta temática a través de los contenedores.
ejemplo con Redis. Redis⁶¹ es una base de datos open-source que permite almacenar combinaciones
de llave/valor de forma rápida y sencilla. La imagen oficial de Redis está en Docker Hub⁶².
El primer paso es iniciar Redis dentro de una red con el mismo nombre.
c0fa54ccf4bc5a2c58a76f1e82a2039ebb17ebffedd99975db58806f6005e772
52cf513250907103c4df2b4688253ac102a35cd87ed0809f98d3710606e05d4e
Liste los contenedores y compruebe que Redis se ha iniciado correctamente. También puede consultar
los registros del contenedor para comprobar que está listo para aceptar peticiones.
El próximo paso es iniciar un segundo contenedor de Redis, en la misma red, pero en modo el cliente.
Desde este contenedor se van a realizar las peticiones para almacenar una llave en el servidor.
El CONTAINER_ID de Redis ha sido utilizado para identificar al contenedor dentro de la red redis,
otra variante es utilizar la IP del contenedor. La capacidad de utilizar el ID del contenedor como alias
dentro de la red existe para las redes creadas por los usuarios. Si hubiese utilizado la red por defecto
bridge solamente hubiese podido acceder utilizando la dirección IP.
Utilice la combinación Ctr-C para salir del cliente Redis, luego vuelva a acceder y compruebe que
todavía existe el valor somevalue asociado a la llave mykey.
"somevalue"
Si por alguna razón tiene que detener el contenedor, ya sea por mantenimiento o por cualquier otro
motivo, va a perder toda la información almacenada. Detenga el contenedor actual y cree otro en su
lugar. Luego acceda a través del cliente y compruebe que ha perdido el valor de la llave.
52cf51325090
4e529f9ba930b0e6cfb1f4cf7474a7edfff8b1ae2dc704e9e5bbad6fac61527a
Como bien muestra la última consulta, sus datos se han perdido. Para este simple ejemplo pudiese
parecer un error sin mayor importancia, pero si pierde los datos de todos sus clientes entonces sí
estaríamos hablando de un gran problema.
El comportamiento deseado es poder detener el contenedor sin perder los datos almacenados, pero
para lograrlo es necesario conocer lo que Docker ha llamado Volúmenes. Continue leyendo la próxima
sección para conocer cómo resolver el problema.
Entre los múltiples comandos en docker, existe uno para gestionar los volúmenes. Utilice un par de
minutos para conocerlo y saber su descripción.
Configuraciones y persistencia de datos 76
Crear volumen
El próximo paso va a ser crear un volumen para utilizarlo en el ejemplo de Redis. El objetivo es
preparar el escenario para no perder los datos de las llaves almacenadas.
redis-data
Liste los volúmenes y encuentre el creado por usted hace unos segundos.
$ docker volume ls
Utilice el comando inspect para conocer los detalles del volumen redis-data. El resultado muestra
la ubicación en disco donde son almacenados los datos, este espacio es gestionado directamente por
Docker, por lo que otros sistemas no pueden acceder al directorio.
[
{
"CreatedAt": "2022-04-03T18:26:30Z",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/redis-data/_data",
"Name": "redis-data",
"Options": {},
"Scope": "local"
}
]
61c8d781bc51337419374bb6704520aad703c108020525573ef9947896eb9dd4
El nuevo comando es bastante intuitivo, pero de todas formas analicemos su contenido. El valor del
comando --mount es la unión de múltiples elementos llave/valor. Type define la manera de montar los
ficheros, existen tres posibles valores: volume, bind y tmpfs.
Los valores src y dst definen origen y destino de la información. En este caso, el origen es el nombre
del volumen, y el destino es la carpeta /data dentro del contenedor. El resultado es el montaje del
directorio origen en la dirección de destino. Con la unión de ambos puntos se garantiza la persistencia
de los datos en el volumen redis-data.
Utilice nuevamente el comando inspect sobre el contenedor iniciado, analice la estructura de montaje
y compruebe la relación existente entre ambos puntos, src y dst.
[
{
"Type": "volume",
"Name": "redis-data",
"Source": "/var/lib/docker/volumes/redis-data/_data",
"Destination": "/data",
"Driver": "local",
"Mode": "z",
"RW": true,
"Propagation": ""
}
]
Comprobar la persistencia
Los próximos comandos adicionan la llave en el servidor.
Configuraciones y persistencia de datos 78
61c8d781bc51
Inicie un nuevo servidor montando el mismo volumen. Una vez dentro consulte el valor de la llave.
0f16a8653f7201a9e65e7c2b457e97700dfbc7afdcd0412eb47881df65031aff
Veamos un ejemplo práctico de cómo utilizar el mecanismo bind con el servicio de Redis.
Redis⁶⁴ almacena sus configuraciones en el fichero redis.conf, sin embargo este fichero no existe cuan-
do se inicia el contenedor. Compruebe esta afirmación iniciando el servicio de Redis y consultando
los registros impresos durante el arranque.
⁶³Los entornos de desarrollo pueden ser desarrollo, pruebas y producción.
⁶⁴https://hub.docker.com/_/redis
Configuraciones y persistencia de datos 80
93099f32f92b9d161e05699953bc8a8b15e072613bd09a4dacaae9c174706bf6
En las últimas líneas se menciona la no existencia del fichero de configuración porque se están
utilizando los valores por defecto. Acceda al servidor de Redis desde un contenedor en modo cliente
para conocer el valor del parámetro maxmemory.
El valor que aparece es cero, lo que significa que no hay un límite máximo de memoria para el servidor.
Teniendo esto en cuenta, el objetivo del ejercicio va a ser crear un fichero redis.conf con el valor
máximo de memoria en 30 megas. Acto seguido el fichero va a ser montado en el contenedor utilizando
el mecanismo bind.
Detenga el contenedor creado anteriormente.
Inicie el servidor Redis montando el fichero redis.conf. Este comando incluye el parámetro --mount
especificando el tipo de montaje bind. También se ha cambiado el comando Cmd para indicarle a
Redis que utilice el fichero de configuraciones enlazado a la máquina.
4098cddc3ff10452fef131ab12c5f2cc1e7d6338a369c4230d952e8ef2120f11
Este último mensaje indica que el fichero ha sido cargado correctamente, pero nunca está demás
hacer un doble chequeo. Vuelva a acceder con el cliente para comprobar el máximo valor de memoria
permitido.
Configuraciones y persistencia de datos 82
Perfecto, en este momento el valor de memoria máximo ha cambiado, lo que significa que Redis está
utilizando el fichero de configuración creado. Ahora puede cambiar los valores a su antojo de forma
fácil, también puede ajustar las diferencias entre sus entornos de desarrollos sin cambiar la imagen.
a8d9199e972d621106bbea8ebbbf78d357139b58aface10d924fcc48dccf8586
$ docker system df
El valor más significativo de todas las columnas es el espacio a reclamar (RECLAIMABLE). En esta
ocasión no hay un valor para los volúmenes porque apenas se ha escrito una llave en Redis, pero si
realiza muchas operaciones va notar la diferencia.
Suponga que tiene un gran volumen disponible para ser eliminado, en ese caso, utilice el comando rm
para eliminar los datos.
Si recibe un mensaje de error similar el ejemplo, significa que el volumen está siendo utilizado por
un contenedor, por lo tanto no puede eliminarse. Va a necesitar detener y eliminar el contenedor para
luego borrar sus datos. Este mecanismo de protección es de mucha ayuda para evitar errores humanos.
Liste los contenedores en funcionamiento, busque el identificador de Redis y detenga el servicio. Si lo
desea, puede detener todos los contenedores en ejecución al mismo tiempo con un solo comando.
a8d9199e972d
En realidad son dos comandos de Docker en una sola línea. El comando entre paréntesis obtiene el
listado de todos los IDs. Estos IDs se pasan como parámetros de entrada al comando stop. En la salida
del comando stop se muestran los IDs de los contenedores que fueron detenidos.
Elimine todos los contenedores que estén detenidos con el comando prune.
Si en vez de eliminar todos los contenedores, desea eliminar directamente el contenedor de Redis,
utilice el comando rm seguido del identificador del contenedor.
Configuraciones y persistencia de datos 85
Después de haber eliminado el contenedor está en condiciones de eliminar el volumen sin que se
muestren errores.
redis-data
Utilice el comando prune en caso de necesitar borrar todos los volúmenes a la vez.
Perfecto, ha logrado eliminar toda la información. Este paso no puede ser revertido, por lo tanto, tenga
mucho cuidado cuando lo ejecute en entornos de producción.
Los ejercicios realizados en la sección anterior buscan practicar los conocimientos aprendidos, pero es
necesario más tiempo de entrenamiento para dominar la persistencia en contenedores. Le recomiendo
explorar otros servicios que necesiten almacenar información para desarrollar esta habilidad. Como
sugerencia puede utilizar PostgreSQL⁶⁶, MongoDB⁶⁷ y RabbitMQ⁶⁸.
⁶⁶https://hub.docker.com/_/postgres
⁶⁷https://hub.docker.com/_/mongo
⁶⁸https://hub.docker.com/_/rabbitmq
Construcción de imágenes
Las imágenes son el punto de inicio de los contenedores, son la base de ficheros utilizados por los
contenedores en su ejecución. Si la base es buena, los procesos van a funcionar correctamente, pero
si tiene fisuras, el servicio va dar muchos dolores de cabeza.
Crear correctamente las imágenes de contenedores es un reto. Son muchas las tecnologías utilizadas
a día de hoy y cada una con sus características. Todas las aplicaciones están preparadas para ser
empaquetadas en imágenes, pero no vale solamente hacer uso de ellas, también es importante crear
nuevas imágenes.
A la hora de crear imágenes, existen buenas prácticas y principios generales aplicables a todas las
tecnologías. Durante el capítulo va a aprender estos aspectos aplicables a todas las imágenes. Para
avanzar con paso firme va a ser necesario recordar el concepto de imagen explicado en secciones
anteriores.
Hasta el momento se han utilizado las imágenes para iniciar contenedores, siempre se han visto
como estructuras cerradas listas para ejecutarse, pero este capítulo es diferente. ¡Llegó el momento
de construir sus propias imágenes!
Los conceptos de capas y grupos de ficheros pueden parecer abstractos y complejos. No se preocupe si
le cuesta trabajo entenderlo a la primera, poco a poco se va a familiarizar con estos temas al avanzar
en las secciones. En las rutinas diarias con contenedores no se utiliza mucho el concepto de capas,
pero tener este conocimiento le va a ayudar a construir imágenes de forma eficiente.
En esta sección se van a mencionar aspectos básicos de las capas en los contenedores, pero si desea
ampliar la información, puede utilizar documentación oficial⁶⁹. Pasemos a tocar con las manos el
concepto de Capas a través de los siguientes comandos.
Elimine la imagen erase-una-vez-1 de su máquina para borrar todas las capas asociadas a ella.
Descargue nuevamente la imagen como si fuera la primera vez. Fíjese en los comentarios incluidos
en la salida, la imagen está compuesta por tres capas, una de ellas existía en el sistema y las otras dos
tuvieron que ser descargadas. Esta es una de las ventajas de la estructuración en capas, solamente va
a descargar los bloques que necesite y nada más. Este funcionamiento optimiza el espacio en disco,
así como también acelera los procesos de descarga y construcción de imágenes.
Liste los elementos de su directorio para comprobar que existe el fichero erase-una-vez-1.tar. Luego
cree la carpeta erase-una-vez-1-image y extraiga la información de la imagen en ella.
$ mkdir erase-una-vez-1-image
$ tar xvf erase-una-vez-1.tar -C erase-una-vez-1-image
Acceda a la carpeta erase-una-vez-1-image y compruebe que existen tres carpetas. Cada carpeta
corresponde con una capa, y entre todas ellas componen la imagen. Dentro de cada carpeta hay un
fichero layer.tar con los ficheros de cada capa.
⁶⁹https://docs.docker.com/storage/storagedriver/
Construcción de imágenes 89
$ tree erase-una-vez-1-image
erase-una-vez-1-image
├── 0ea9f89692c83558320491d8c6799d1305f6ae336c72137f6c3620ef9b13a97f
│ ├── VERSION
│ ├── json
│ └── layer.tar
├── 2d575f0b8481e4c476ada9bab76551e8e0c9439089ed94d0ed5f51d52cd2ace2
│ ├── VERSION
│ ├── json
│ └── layer.tar
├── 4f1101dd1eb24fe4a4712e939e95c86380a15f1ddb64136a64b88da53e438d57.json
├── 7aa62770f20b354a41fc388ce46740e49556c8c5225892c663a2143d767582bf
│ ├── VERSION
│ ├── json
│ └── layer.tar
├── manifest.json
└── repositories
Utilice el comando inspect para consultar el número de capas que componen la imagen. El resultado
son los identificadores de las tres capas codificados con el algoritmo sha256.
[
"sha256:ff4be4836032f1110bf03d4a17eeba4d126a1910cf0377386f2bd288469e7cfe",
"sha256:16979a96d20690fa33cde3ea2f125b262c8ac4ce2b60c78f70741ec0d79f6d35",
"sha256:1f312368577f80f0d518240893d013f769782728fd6cea747cc52b9145f23742"
]
El siguiente comando le va a permitir generar el identificador de una capa de forma manual, para
luego comprobar la correspondencia del código con los ficheros layers.tar de las carpetas.
Construcción de imágenes 90
43c0bb0e95acbd95afb0d97c2fe1ee658fef3ce7fec2666aad6933ef1cf72e20 erase-una-vez-1-image/7\
fda5d5233a852177110f9f2b4468f88ffd368230bb137d1f1fff1263d4e51b6/layer.tar
Después de conocer la estructura en capa de las imágenes el siguiente paso son las instrucciones. Las
próximas secciones explican las instrucciones más utilizadas en Docker, pero también van a permitirle
responder la siguiente pregunta:
¿Dónde se guardan las instrucciones que generan la imagen?
Docker no podía pasar por alto estas buenas prácticas y decidió utilizar un fichero como mapa para
crear las imágenes de los contenedores. A este fichero se le dio el nombre Dockerfile, y su objetivo
principal es almacenar las instrucciones que genera una imagen.
En la actualidad es muy común ver este fichero en la raíz de los proyectos como parte del código
fuente. Su presencia indica la posibilidad de crear una imagen del código para luego desplegarlo
como contenedor. Al abrir el fichero va a identificar, de forma rápida, todas las dependencias y
configuraciones necesarias para ejecutar el proyecto. Aquí le comparto un enlace de ejemplo⁷⁰.
Tener el fichero Dockerfile dentro del repositorio de código trae muchas ventajas, pero tenga cuidado
de no incluir información sensible como pueden ser: credenciales, tokens o algún otro valor similar
utilizado para instalar paquetes o dependencias.
Alrededor del fichero Dockerfile existen buenas prácticas que le van a permitir sacar provecho de
este fichero. Muchas de estas buenas prácticas son comentadas durante el transcurso del capítulo.
Adicional a los conceptos explicados en el libro está la documentación oficial de Docker⁷¹, que siempre
vale la pena consultarla.
Ahora que conoce el propósito del fichero Dockerfile, nada le impide crear su primera imagen.
$ cat Dockerfile
FROM scratch
COPY erase-una-vez-1 /
ENTRYPOINT ["./erase-una-vez-1"]
La instrucción FROM establece el punto de inicio de la imagen. Luego aparece el comando COPY,
responsable de copiar el binario desde el directorio local hacia el directorio raíz de la imagen. Por
último la instrucción ENTRYPOINT, utilizada para definir el comando de inicio del contenedor.
Seguramente recuerde el concepto Entrypoint cuando se analizó la sección modificar el comando
de inicio, pues es exactamente el mismo, pero esta vez lo está definiendo desde el fichero Dockerfile.
Cada vez que utilice la instrucción COPY se crea una capa en la imagen. En el ejemplo se utiliza
una vez, por lo tanto la imagen tiene una sola capa. Dentro de esta capa va a estar el binario de la
aplicación erase-una-vez-1⁷⁵.
Tras declarar todos los pasos en el Dockerfile, es necesario transformar esas declaraciones en una
imagen. Para lograrlo se utiliza el comando build, que significa construir la imagen.
El parámetro tag se ha utilizado para establecer el nombre de la imagen, mientras que el punto al final
indica el directorio donde se encuentra el fichero Dockerfile. Dedique unos minutos para analizar los
⁷⁵https://github.com/mmorejon/erase-una-vez-1
Construcción de imágenes 93
mensajes mostrados durante la construcción, fíjese en el momento donde se copia el binario, donde
se crean las capas y cuando se establece el nombre de la imagen.
Si ejecuta el comando nuevamente va a ver la misma secuencia de pasos. Sin importar el número de
veces, la salida va a ser siempre la misma. Esta es una de las ventajas de utilizar el fichero Dockerfile
en la construcción de imágenes.
Ahora liste las imágenes para conocer el resultado obtenido.
Si consulta el tamaño del binario va a notar que coincide con el tamaño de la imagen. Esto no es
coincidencia, se debe a que es el único fichero dentro de la imagen. También puede utilizar el comando
history para ver los pasos utilizados en la creación de la imagen, estos pasos van a coincidir con las
instrucciones del fichero Dockerfile.
En el resultado se puede apreciar las dos instrucciones, COPY y ENTRYPOINT. De estas dos
instrucciones solamente COPY genera una nueva capa con el tamaño del binario. Como último paso
se va a iniciar un contenedor a partir de la imagen creada, esta es la prueba de fuego para ver si su
imagen funciona correctamente.
Si ha obtenido los mismos mensajes en su pantalla significa que todo ha ido bien. En su caso el nombre
del hostname va a ser diferente porque corresponde con el identificador el contenedor, pero el resto
debe ser igual. ¡Enhorabuena! Ha creado su primera imagen de contenedor.
Construcción de imágenes 94
[
{
"SchemaVersion": "0.1.0",
"Vendor": "Docker Inc.",
"Version": "v0.8.1",
"ShortDescription": "Docker Buildx",
"Name": "buildx",
"Path": "/usr/local/lib/docker/cli-plugins/docker-buildx"
},
]
Si se muestra como instalado significa que puede utilizar el parámetro --help para ver todos los
subcomandos. Entre las opciones disponibles va estar el comando build con las mejoras incluidas.
En este momento usted cuenta con dos formas de construir imágenes en su ordenador, pero solo una
es la recomendada. Para evitar confusiones, Docker permite establecer el uso del plugin buildx de
forma predeterminada. De esta manera va a poder hacer uso de sus bondades a través del comando
image build.
Si utiliza Docker Desktop el plugin buildx está establecido por defecto. Acceda a Preferences > Docker
Engine, la opción buildkit debe tener el valor true. En el caso de Docker Engine tiene que crear un
fichero llamado daemon.json, luego necesita reiniciar el servicio de Docker para utilizar los cambios
realizados.
⁷⁶https://docs.docker.com/buildx/working-with-buildx/
Construcción de imágenes 95
El uso de BuildKit es fundamental para sacar provecho de las futuras funcionalidades en Docker, por
lo tanto, le recomiendo mantener activado su uso.
carpeta que no deban formar parte del proceso de construcción. La estructura interna del fichero sigue
las mismas convenciones que el fichero .gitignore. Si desea profundizar en este tema consulte el sitio
oficial en Docker⁷⁹.
En el repositorio de ejercicios del libro se ha diseñado un caso para mostrar cómo quedan excluidos
los ficheros utilizando el fichero .dockerignore.
En ejercicios previos debe haber utilizado este repositorio, si este es su caso puede saltar
el paso de clonar.
Dentro de la carpeta dockerignore va a encontrar los ficheros para crear la imagen. El fichero
.dockerignore ha sido configurado para excluir el fichero file-one.txt. El fichero Dockerfile creado es
muy simple, va a copiar los ficheros del directorio hacia la carpeta /myfiles. La imagen base utilizada
es alpine⁸⁰, porque es pequeña y tiene algunos comandos básicos como es ls.
$ cat ./dockerignore/Dockerfile
FROM alpine:3.15.4
COPY . /myfiles
Utilice el comando build para construir la nueva imagen. Tenga en cuenta un detalle, el último
parámetro del comando es la ubicación de la carpeta donde están los ficheros dentro del repositorio.
Luego inicie un contenedor utilizando la imagen construida para listar los ficheros copiados hacia la
carpeta /myfiles.
⁷⁹https://docs.docker.com/engine/reference/builder/#dockerignore-file
⁸⁰https://hub.docker.com/_/alpine
Construcción de imágenes 97
Dockerfile
file-two.txt
Como era de esperar, el fichero file-one.txt no está dentro de la carpeta, ha sido excluido por el fichero
.dockerignore. Como detalle interesante, el fichero .dockerignore tampoco aparece en el listado porque
Docker solamente lo utiliza para el proceso de construcción.
Recuerde los conceptos vistos en esta sección cuando tenga muchos ficheros en el directorio. Utilice
el fichero .dockerignore durante el proceso de construcción para excluir aquellos elementos que no
necesite.
⁸¹https://hub.docker.com/_/nginx
⁸²https://hub.docker.com/r/openresty/openresty
⁸³https://hub.docker.com/_/httpd
Construcción de imágenes 98
$ tree website
website
├── Dockerfile
└── html
└── index.html
1 directory, 2 files
La carpeta html contiene el fichero del sitio que desea publicar, mientras que el fichero Dockerfile
contiene los pasos para construir la imagen.
$ cat website/Dockerfile
FROM nginx:1.21.6
COPY html /usr/share/nginx/html
En esta ocasión se ha utilizado la imagen de nginx:1.21.6⁸⁴ como base para el servidor web. Luego
se copia la carpeta html hacia la ubicación donde Nginx publica los ficheros. En total son dos líneas
de código, pero van a ser suficientes porque el resto de configuraciones están incluidas en la imagen
base.
Construya la imagen para el sitio web e inicie un contenedor a partir de ella.
⁸⁴https://hub.docker.com/_/nginx
Construcción de imágenes 99
El sitio web mostrado es un ejemplo sencillo, pero válido. El objetivo ha sido mostrar el uso de
imágenes existentes en favor reutilizar el trabajo de la comunidad. Además, recuerde que las imágenes
oficiales cuentan con la garantía de calidad y seguridad explicada en secciones anteriores. Por último,
siempre que necesite crear una nueva imagen, analice cuál debe ser su punto de inicio.
$ tree app
app
├── Dockerfile-cache
├── Dockerfile-dep
├── Dockerfile-multi-stage
├── go.mod
├── go.sum
└── main.go
0 directories, 6 files
Si está familiarizado con las aplicaciones en Golang va a poder reconocer la estructura, en caso
contrario le comento a grandes rasgos cada elemento. El fichero main.go tiene la lógica de la
aplicación, mientras que los ficheros go.mod y go.sum registran las dependencias utilizadas en el
código. Un homólogo a estos ficheros go.* son: requirements.txt en Python, package.json en Node
o composer.json en PHP. Para construir la aplicación el primer paso tiene que ser la instalación de
dependencias, y después se compila el código. En la carpeta app se ha incluido el fichero Dockerfile-
dep con los pasos para construir la aplicación.
$ cat app/Dockerfile-dep
# base image
FROM golang:1.18.1-alpine3.15
# set up work directory
WORKDIR /opt/app
# copy app files
COPY . .
# install dependencies
RUN go mod download && \
go mod verify
# build app
RUN CGO_ENABLED=0 GOOS=linux go build -o /go/bin/app .
# define run command
ENTRYPOINT [ "app" ]
⁸⁶https://go.dev/
⁸⁷https://pkg.go.dev/github.com/docker/docker/client
Construcción de imágenes 101
En el fichero aparecen instrucciones nuevas que deben ser comentadas para entender su funciona-
miento. También han sido incluidos múltiples comentarios antes de cada instrucción para apoyar en
la comprensión del fichero.
La instrucción WORKDIR⁸⁸ establece el directorio de trabajo, si el directorio no existe, entonces se crea
automáticamente. Todos los comandos a continuación del WORKDIR se ejecutan desde este directorio,
su función es lo mismo a crear una carpeta y luego acceder a ella. Por otro lado, la instrucción
RUN ⁸⁹ es la ejecución de comando en consola, este elemento es muy utilizado en las imágenes para
instalar dependencias y configurar sistemas. En la aplicación de ejemplo se utiliza para instalar las
dependencias de Golang dentro de la imagen.
Puede utilizar la instrucción RUN tantas veces como sean necesarias, pero recuerde que cada
instrucción es una nueva capa en su imagen, y mientras menos capas tenga mejor. Una forma de
reducir el número de capas es utilizado el símbolo && para unir múltiples comando en una ejecución.
Los pasos descritos en el fichero Dockerfile-dep se pueden resumir de la siguiente forma. Partiendo de
Golang como base se crea el directorio /opt/app, luego se copian todos los ficheros desde directorio
a esta carpeta, y se instalan las dependencias definidas en los ficheros go.*. Después se compila la
aplicación, teniendo como resultado el binario app, que más tarde es configurado en el comando de
arranque del contenedor.
Una vez conocido el contenido del fichero Dockerfile-dep, el próximo paso es construir la imagen. En
el comando se ha introducido el parámetro --file, esta configuración permite indicarle a Docker el
fichero de construcción de la imagen.
El directorio app no cuenta con un fichero Dockerfile. El nombre Dockerfile es un acuerdo establecido
por Docker como nombre predeterminado, pero usted puede darle el nombre que desee a su fichero. La
recomendación es utilizar Dockerfile para el nombre, pero el ejercicio se ha diseñado de esta manera
para mostrar el uso el parámetro --file.
El parámetro --file dentro del comando build define la ubicación del fichero de construcción de la
imagen. La ruta toma como referencia el directorio donde se ejecuta el comando.
Una vez creada la imagen, es momento de iniciar el contenedor para conocer el resultado. Dentro
del contenedor se va a intentar listar las imágenes de Docker, pero como no existe Docker dentro del
contenedor, va ser necesario compartir el socket utilizado por dockerd en la máquina para comunicarse
con el cliente docker. Recuerde la sección de Arquitectura vista al inicio del libro.
⁸⁸https://docs.docker.com/engine/reference/builder/#workdir
⁸⁹https://docs.docker.com/engine/reference/builder/#run
Construcción de imágenes 102
nginx:1.21.6
nginx:1.20.2
El resultado de la ejecución muestra las imágenes Nginx presentes en su ordenador, en su caso puede
variar dependiendo de cuántas imágenes de Nginx tenga descargadas. El parámetro -v o --volume
es utilizado para montar los ficheros, su objetivo es el mismo que el del parámetro --mount, pero
utilizando una sintaxis simplificada. La siguiente línea corresponde con el mismo ejemplo, pero
utilizando el comando --mount.
--mount type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock
Aproveche para jugar un poco con la aplicación. Inicie otro contenedor, pero con la intención de listar
las imágenes alpine.
alpine:latest
alpine:3.15.1
alpine:3.15.0
Los resultados en su máquina va a variar en dependencia de las imágenes que tenga descargadas. Los
conceptos vistos en esta sección van a serle muy útiles para los proyectos con dependencias en los que
trabaje. Dedique unos minutos a analizar cómo puede aplicar estos conocimientos en su entorno.
son tomados como referencia para las próximas construcciones. Para ganar en velocidad durante el
proceso de construcción Docker evalúa si la capa a crear es igual a la obtenida en la iteración anterior,
si detecta que van a ser iguales, no la construye y utiliza la que tiene guardada (en caché).
Para detectar los cambios Docker analiza los ficheros de origen y el fichero Dockerfile. Cualquier
cambio en uno de estos puntos descarta el uso de la caché. Además, Docker bloquea el uso de la
caché para todas las instrucciones que vengan a continuación de una capa que haya tenido que ser
construida.
Para entender mejor la situación planteada realice el ejercicio de construir la imagen de la aplicación
anterior múltiples veces. La primera vez va a demorar un poco, pero luego las siguientes iteraciones
son mucho más rápidas gracias a la caché. Fíjese en las líneas de salida marcadas con la palabra
CACHED.
Ahora realice una simple modificación en el fichero main.go. Por ejemplo, en la línea 15 cambie la
palabra nginx por alpine, luego vuelva a construir la imagen.
Note que se ha eliminado la marca CACHED en el tercer paso, y de ahí en adelante todos los pasos
tuvieron que ser ejecutados nuevamente. Al realizar la copia de los ficheros Docker detectó cambios,
por tal motivo los pasos siguientes tuvieron que ser ejecutados. Intente ahora dar respuesta a las
siguientes preguntas. ¿Será necesario instalar las dependencias nuevamente si el cambio fue en el
fichero main.go? ¿Cómo se puede evitar la instalación de dependencias constantemente?
La respuesta a la primera pregunta es No. Los ficheros go.* son los encargados de gestionar las
dependencias, si no hay cambios en estos ficheros no es necesario volver a instalarlas. Para evitar
Construcción de imágenes 104
$ cat app/Dockerfile-cache
# base image
FROM golang:1.18.1-alpine3.15
# set up work directory
WORKDIR /opt/app
# copy files dependencies
COPY go.* /opt/app
# install dependencies
RUN go mod download && \
go mod verify
# copy app files
COPY . .
# build app
RUN CGO_ENABLED=0 GOOS=linux go build -o /go/bin/app .
# define run command
ENTRYPOINT [ "app" ]
La principal diferencia está en la copia e instalación de dependencias antes de copiar el código fuente
de la aplicación. De esta forma se garantiza otra buena práctica para el fichero Dockerfile, donde
los cambios menos frecuentes deben realizarse primero, quedando para el final los cambios más
frecuentes, como por ejemplo, los cambios en el código de la aplicación.
Modifique el fichero main.go y construya nuevamente la imagen. Note el uso de la caché hasta el paso
previo de la creación del binario.
Construcción de imágenes 105
Los grupos de desarrollo utilizan flujos de integración y despliegues continuos (CI/CD). En cada
iteración se construye una imagen para ser desplegada en los servidores. Teniendo esto en mente,
es de vital importancia diseñar correctamente los ficheros Dockerfiles para sacar provecho de la caché
siempre que sea posible. El uso de las buenas prácticas explicadas en esta sección le serán de mucha
ayuda en estos escenarios.
linux/aarch64
⁹⁰https://github.com/docker-library/official-images#multiple-architectures
⁹¹https://en.wikipedia.org/wiki/AArch64
Construcción de imágenes 106
linux/arm64
Hasta aquí todo va bien, porque usted puede iniciar el contenedor en su ordenador sin contratiempos,
pero ¿qué sucedería si intenta desplegar su servicio en una máquina con arquitectura distinta a la
suya? ¿Podrá funcionar correctamente?
Seguramente se imagina la respuesta, ese contenedor no va a poder funcionar en una arquitectura
distinta. En aras de buscar una solución al problema, Docker ha incluido la capacidad de especificar
la plataforma para la que se desea construir la imagen a través del parámetro --platform. El ejemplo
que aparece a continuación pretende construir la misma imagen, pero cambiando su arquitectura. En
su caso utilice una plataforma distinta a la existente en su ordenador
linux/amd64
Ahora intente iniciar un contenedor a partir de esta imagen que tiene una arquitectura diferente a su
ordenador.
WARNING: The requested image's platform (linux/amd64) does not match the detected host pl\
atform (linux/arm64/v8) and no specific platform was requested
nginx:1.21.6
nginx:1.20.2
El resultado mostrado en consola corresponde con el esperado. Para este ejemplo el cambio de
arquitectura no dificultó el inicio del servicio, esto se debe a que el binario fue compilado para linux,
Construcción de imágenes 107
pero para el resto de aplicaciones debe tener precaución con el funcionamiento. Docker por su parte,
imprime siempre un mensaje de advertencia cuando detecta la diferentes arquitecturas entre el host
y la imagen.
La imagen docker-book-app cambió de plataforma al cambiar el comando de construcción. Cada vez
que necesite cambiar el valor de la plataforma debe construir una nueva imagen, lo cual puede llegar
a ser tedioso y repetitivo en muchas ocasiones, más cuando son muchos los valores de plataformas
posibles a establecer.
¿Será que existe una forma fácil de construir una imagen para múltiples plataformas al mismo tiempo?
Sí existe la forma, de hecho, los equipos construyen y almacenan las imágenes para todas las
plataformas que necesiten en un solo paso. Para lograr la configuración mencionada se necesitan
conocimientos sobre cómo publicar imágenes en registros de Docker. Este contenido se va a explicar
en el próximo capítulo, por lo tanto debe tener un poco de paciencia para saber cómo hacerlo.
Aproveche para acceder nuevamente a la imagen hello-word en Docker Hub⁹² y conozca todas las
plataformas para las que ha sido construida la imagen.
Se han ocultado algunas líneas para resaltar el contenido relevante para la sección. Preste atención a
las dos etapas definidas, en la primera se copia el código, se instalan las dependencias y se compila la
aplicación. En la segunda etapa, se utiliza la imagen scratch como base y se copia el binario generado
en la etapa inicial.
Cada etapa tiene una numeración automática según el orden de aparición, pero también pueden
identificarse a través de alias. El objetivo de ambos mecanismos es crear referencias que puedan ser
utilizadas luego en otras etapas del Dockerfile. En el ejemplo se ha marcado la primera etapa con el
alias builder, luego este alias se utiliza en la etapa final, utilizando el parámetro --from con el fin de
copiar el binario hacia la imagen final.
Docker recorre el fichero Dockerfile de inicio a fin, ejecutando las instrucciones y generando las capas
que sean necesarias. Sin embargo, la imagen final va a ser la creada en la última etapa. Construya una
nueva imagen utilizando el fichero Dockerfile-multi-stage para comprobarlo.
Va a ver todas las instrucciones del Dockerfile-multi-stage ejecutadas, pero en la historia solamente
están reflejados los pasos de la última etapa.
El resto de capas construidas se quedan almacenadas en la máquina para ser utilizadas por la caché,
pero nunca llegan a formar parte de la imagen final.
Aunque este sea el flujo más utilizado, también existe la posibilidad de especificar la construcción de
una etapa a través del parámetro --target. Este parámetro olvida completamente el flujo anterior y
construye una imagen con los pasos presentes en la etapa especificada. El resto de instrucciones del
fichero son ignorada.
Utilice el siguiente comando para ver un ejemplo con este parámetro. Las instrucciones de la última
etapa van a ser ignoradas.
La construcción de etapas de forma individual son de mucha utilidad cuando desee comprobar el
comportamiento de una sección específica, para el resto de casos, le recomiendo mantener el flujo
predeterminado.
1. ¿Cuál de las siguientes imágenes tiene mayor número de capas? nginx:1.21.6 o redis:6.2.6.
2. ¿Cuántos ficheros Dockerfiles puede tener una aplicación? y ¿Cuál es la recomendación al
respecto?
3. ¿Qué problema resuelve el fichero .dockerignore?
4. Utilice la imagen nginx:1.21.6 para construir su propio sitio web. Cree una página html con los
datos de su currículum, luego cree se propia imagen e inicie un contenedor utilizando el puerto
8000 de su máquina.
5. Utilice la imagen alpine:3.15.1 como base para instalar curl. Luego cambie el comando ENTRY-
POINT para que utilice la nueva herramienta instalada. Utilice un fichero Dockerfile para dejar
definidas las instrucciones.
6. Construya una aplicación sencilla que muestre un mensaje en pantalla con el lenguaje de su
preferencia. Luego adicione el fichero Dockerfile para generar la imagen que permita poner en
funcionamiento la aplicación. Suba estos ficheros a un repositorio en GitHub y comparta el
enlace con el autor para recibir sus comentarios.
Los conceptos aprendidos en el capítulo le permiten construir imágenes, pero el viaje no termina aquí,
aún quedan preguntas por resolver, como por ejemplo:
• ¿Dónde deben ser almacenadas las imágenes? ¿En la máquina local o en la nube?
• ¿Cuántos registros públicos de imágenes se pueden utilizar?
• ¿Cómo mover una imagen desde la máquina hacia las plataformas en la nube?
Actualmente casi todos los proveedores de infraestructura cuentan con un Registro para las imágenes
de contenedores. Realice una consulta en la plataforma que más utilice y es casi seguro que va
a encontrar un Registro para almacenar imágenes. El objetivo que persiguen los proveedores de
infraestructura es permitirle al usuario almacenar sus aplicaciones lo más cerca posible del lugar
donde van a ser desplegadas, de esta forma reducen el tiempo de descarga y el coste de tráfico de
paquetes.
A esta competencia se han sumado las plataformas de código como GitLab⁹⁶ y GitHub⁹⁷, pero en
este caso con la intención de almacenar su aplicación lo más cerca posible del código fuente. Esta
filosofía tiene mucha aceptación desde dos puntos de vista, el primero es para las aplicaciones open-
source, donde su objetivo no es desplegar el sistema, sino hacerlo accesible al resto de la comunidad.
El segundo elemento es para los equipos donde se utilizan múltiples plataformas, porque evitan tener
la aplicación replicada por los registros de cada una de ellas.
A la fiesta de Registros se suma Docker Hub⁹⁸, que ha sido el primero de todos los registros creados
en la industria y cuenta con un gran número de funcionalidades e integraciones, tanto para equipos
⁹⁶https://about.gitlab.com/
⁹⁷https://github.com/
⁹⁸https://hub.docker.com/
Publicar imágenes 114
pequeños, como para grandes empresas. Docker Hub también comparte la filosofía de tener un único
lugar para todas las imágenes, luego estas pueden ser desplegadas en la infraestructura que desee.
Existen muchos otros Registros, con un poco de tiempo va a poder explorarlos y hacerse con un
criterio propio de cuál puede ser mejor para sus intereses. A la hora de analizar o evaluar un Registro
le recomiendo tener en cuenta los siguientes aspectos:
Después de esta breve introducción debe haber notado la importancia del tema a tratar, porque en
algún momento va a necesitar almacenar sus imágenes fuera del ordenador. Es por este motivo que
se ha creado el capítulo, para mostrar cómo publicar las imágenes de contenedores e interactuar con
los Registros.
¡Perfecto! La imagen se ha creado correctamente. El siguiente paso va a ser mover la imagen hacia un
Registro en Internet.
⁹⁹https://github.com/mmorejon/erase-una-vez-docker
Publicar imágenes 115
Ahora liste nuevamente las imágenes y compruebe que existen dos referencias apuntando al mismo
contenido. El comando mostrado a continuación filtra el listado de imágenes utilizando como
referencia el nombre. Fíjese que el identificador de las imágenes es el mismo, porque es la misma
imagen pero con dos referencias.
$ docker image ls \
--filter=reference=docker-book-app \
--filter=reference="ttl.sh/*" \
--format 'table {{.Repository}}\t{{.Tag}}\t{{.ID}}'
La imagen ha quedado etiquetada con la estructura de nombre esperada, ahora es posible publicarla en
el registro TTL. El comando que se utiliza para publicar las imágenes es image push, y como parámetro
se pasa el nombre completo que aparece en la columna repositorio junto con la etiqueta.
¹⁰⁰https://ttl.sh/
¹⁰¹https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_.28random.29
Publicar imágenes 116
La subida de la imagen es muy rápida porque apenas pesa 12 megabyte, de aquí la importancia de
construir imágenes ligeras. Los mensajes en la salida del comando muestran que ha sido copiada
correctamente. En este momento la imagen se encuentra en su máquina, pero también en el registro
público en Internet.
Antes de cumplirse las 24 horas de almacenamiento reservadas en TTL, realice el siguiente experi-
mento: elimine todas las imágenes locales y luego descargue la imagen almacenada en el registro
público. Este sería el mismo caso si intenta desplegar la imagen en un servicio de contenedores en las
infraestructuras públicas.
Utilice el comando image prune para eliminar todas las imágenes almacenadas en su ordenador.
También deben ser eliminadas las capas intermedias creadas durante la construcción de imágenes,
estas últimas pueden ser eliminadas a través del comando builder prune. En ambos comandos se ha
utilizado el parámetro --force para evitar la pregunta de confirmación sobre la eliminación de los
datos.
Liste las imágenes para estar seguros que el resultado es vacío. No deben haber imágenes en su
ordenador en este momento.
$ docker image ls
Luego utilice el comando image pull para descargar la imagen almacenada en el registro TTL. Docker
va a buscar la imagen en su ordenador, pero al no estar presente, tiene que ir al registro para
descargarla.
Publicar imágenes 117
$ docker image ls \
--format 'table {{.Repository}}\t{{.Tag}}\t{{.ID}}'
¡Muy bien¡ Ha traído de vuelta la imagen con la aplicación. Aproveche este momento para iniciar
un contenedor y así verificar que funciona correctamente. El resultado de la ejecución va a ser vacío
porque no existen imágenes con referencia nginx en el ordenador.
Como puede apreciar, el parámetro --tag va a estar presente tantas veces como sea necesario. Al
terminar la ejecución del comando va a tener dos etiquetas haciendo referencia a la misma imagen.
Luego puede empujar la imagen hacia el registro en un segundo paso de forma rápida y sencilla.
Si desconoce la estructura de nombre del registro puede poner un valor temporal, porque va a poder
establecer la etiqueta correcta en otro momento a través del comando image tag. Si tiene dudas sobre
cómo utilizar este comando haga uso del parámetro --help para recordar el orden de los elementos.
El siguiente ejemplo muestra como adicionar una nueva etiqueta a la imagen docker-book-app.
Liste las imágenes y compruebe que la nueva etiqueta ha sido asignada correctamente.
$ docker image ls \
--filter=reference=my-new-tag \
--filter=reference=docker-book-app \
--format 'table {{.Repository}}\t{{.Tag}}\t{{.ID}}'
¡Genial! Ambas etiquetas están como se esperaba. Ahora va a poder adicionar las referencias que desee
a sus imágenes.
Hasta el momento se han establecido nuevas etiquetas a las imágenes, pero también hace falta saber
cómo eliminarlas en caso de haber cometido algún error en el nombre. Las etiquetas en las imágenes
no se editan porque son referencias de nombre hacia una imagen, por lo tanto solamente pueden
adicionarse y eliminarse.
Para eliminar una etiqueta se utiliza el comando image rm, el cual es responsable de eliminar las
imágenes, pero en este caso hay múltiples etiquetas apuntando a la misma imagen, por lo tanto se
Publicar imágenes 119
Untagged: my-new-tag:latest
Como puede observar, el mensaje en la consola indica que ha eliminado la etiqueta my-new-tag y no
la imagen. Vuelva a listar las imágenes para comprobar que la etiqueta ha sido eliminada.
$ docker image ls \
--filter=reference=my-new-tag \
--filter=reference=docker-book-app \
--format 'table {{.Repository}}\t{{.Tag}}\t{{.ID}}'
¡Enhorabuena! En este punto usted es capaz de etiquetar las imágenes a su gusto. Este es un paso
importante para poder publicar luego la imagen en el registro de su preferencia.
Utilice el comando image tag, pero en esta ocasión para establecer múltiples versiones de una misma
imagen.
Publicar imágenes 120
$ docker image ls \
--filter=reference=docker-book-app \
--format 'table {{.Repository}}\t{{.Tag}}\t{{.ID}}'
Todas las etiquetas hacen referencia a la misma imagen, pero en entornos reales cada versión va a
corresponder con versiones distintas de la aplicación.
los repositorios. El proyecto se encuentra en fase experimental, pero funciona bien para el objetivo
buscado en esta sección.
Para hacer uso de hub-tool es necesario descargar el archivo comprimido con el binario, extraer su
contenido y colocar el binario de forma tal que pueda ser ejecutado. La versión utilizada es la v0.4.5¹⁰⁵
y puede ser descargada directamente desde GitHub. Si tiene dudas sobre cómo instalar la herramienta
puede apoyarse en la documentación del propio repositorio¹⁰⁶.
Después de instalar la herramienta confirme la versión instalada a través del siguiente comando.
$ hub-tool version
Version: v0.4.5
Git commit: d383e722fffcd4f0c7ccf835aace7138abb2e937
$ hub-tool --help
Usage:
hub-tool
hub-tool [command]
Available Commands:
account Manage your account
help Help about any command
login Login to the Hub
logout Logout of the Hub
org Manage organizations
repo Manage repositories
tag Manage tags
token Manage Personal Access Tokens
version Version information about this tool
El próximo paso es hacer login con la herramienta hub-tool para acceder con su usuario a Docker
Hub. Si ha configurado el segundo factor de autenticación el comando le va a pedir el código de seis
cifras para completar el registro, en caso contrario solamente va a necesitar su contraseña.
¹⁰⁵https://github.com/docker/hub-tool/releases/tag/v0.4.5
¹⁰⁶https://github.com/docker/hub-tool#install
Publicar imágenes 122
Password:
2FA required, please provide the 6 digit code:
Login Succeeded
En este punto la herramienta se encuentra lista para su uso. Es momento entonces de avanzar a la
próxima sección, donde va a poder crear su primer repositorio en Docker Hub.
$ hub-tool repo ls
El listado debe aparecer vacío si no ha trabajado con Docker Hub previamente, de lo contrario, van a
aparecer los repositorios creados en momentos anteriores.
El próximo paso es referenciar correctamente la imagen a través de etiquetas. Docker Hub tiene
su propia estructura de nombre para los repositorios, la cual debe ser respetada y establecida para
garantizar el almacenamiento de las imágenes.
<su-usuario-en-dockerhub>/<nombre-del-repositorio>
$ DOCKERHUB_USERNAME=<su-usuario>
$ docker image tag docker-book-app ${DOCKERHUB_USERNAME}/docker-book-app
En este punto la imagen está lista para ser enviada a Docker Hub, pero primero es necesario configurar
el acceso a esta plataforma desde el Terminal. La forma correcta de realizar el enlace es utilizando un
token personal (PAT, personal access token) junto con el usuario. En este caso no debe utilizarse la
contraseña del usuario. Para crear un token en Docker Hub acceda a las configuraciones de seguridad
de su usuario¹⁰⁷ y genere un token con permisos de lectura y escritura.
Utilice el token generado para hacer login en Docker Hub.
$ export DOCKERHUB_PAT=<su-token>
$ echo $DOCKERHUB_PAT | docker login docker.io \
--username ${DOCKERHUB_USERNAME} \
--password-stdin
Login Succeeded
$ hub-tool repo ls
¡Enhorabuena! Ha creado su primer repositorio en Docker Hub, pero no va a ser el último :).
Le propongo como reto crear nuevamente el repositorio, pero esta vez a través de la web. Después de
creado intente empujar la misma imagen. El resultado va a ser exactamente el mismo, con la diferencia
que Docker Hub no tiene que crear el repositorio por usted.
Un detalle a tener en cuenta durante el reto son los nombres de los repositorios. No pueden existir
dos repositorios con el mismo nombre en la plataforma, por lo tanto es necesario borrar el repositorio
actual antes de crear uno nuevo a través de la interfaz web.
Para borrar el repositorio puede utilizar la web o la herramienta hub-tool.
¹⁰⁷https://hub.docker.com/settings/security
Publicar imágenes 124
Con los conocimientos adquiridos en esta sección va poder almacenar las imágenes en Docker Hub,
pero existe un detalle que necesita saber. Docker Hub permite solamente un repositorio privado, si
desea tener más de uno tiene que cambiar de plan y pagar una suscripción. Sin embargo, existen
registros de imágenes con ilimitados repositorios privados sin coste alguno. Para mostrar un ejemplo
de ello se ha diseñado la siguiente sección, donde se utiliza el registro de imágenes de GitHub.
Veamos a continuación algunos detalles a tener en cuenta a la hora de realizar estos pasos.
Seguramente usted tiene creada su cuenta en GitHub, pero si no es el caso, este puede ser el mejor
momento para empezar. Utilice el siguiente enlace para acceder a la plataforma y crearse un usuario.
Recuerde configurar el 2FA para fortalecer la seguridad de su cuenta.
https://github.com/signup
El segundo paso es crear un token con permisos de escrituras de paquetes. Este token va a ser su
clave secreta para autenticarse con la plataforma. Para crear el token utilice el siguiente enlace: https:
//github.com/settings/tokens/new. Luego agregue una nota para recordar el propósito del token y
¹⁰⁸https://docs.github.com/es/packages/learn-github-packages/introduction-to-github-packages
¹⁰⁹https://docs.github.com/es/billing/managing-billing-for-github-packages/about-billing-for-github-packages
Publicar imágenes 125
marque la casilla de escritura de paquetes. Va a notar que la sección repo se activa automáticamente
quedando de la siguiente forma:
Complete el proceso dando clic al final de la página en el botón Generate Token. Luego copie el token
generado hacia un lugar seguro para utilizarlo en los próximos pasos.
El tercer paso es habilitar el acceso al registro en GitHub desde las líneas de comandos en Docker.
Para resolver este punto ejecute el comando docker login haciendo uso de su usuario en GitHub y
el token creado en el paso anterior. La documentación oficial para este proceso se encuentra en este
enlace¹¹⁰.
Adicione el valor del token y de su usuario en GitHub como variables de entorno en el terminal. En el
comando de autenticación se hace referencia a ghcr.io porque es el registro donde se necesita acceso.
¹¹⁰https://docs.github.com/es/packages/working-with-a-github-packages-registry/working-with-the-container-registry
Publicar imágenes 126
$ GH_USERNAME=<su-usuario>
$ export CR_PAT=<su-token>
$ echo $CR_PAT | docker login ghcr.io \
--username ${GH_USERNAME} \
--password-stdin
Login Succeeded
El cuarto paso es etiquetar la imagen según la estructura de nombre definida por GitHub. La estructura
establecida es la siguiente: ghcr.io/OWNER/IMAGE_NAME. Modifique los valores acorde a su usuario e
imagen para etiquetar la imagen.
El último paso le va a resultar familiar, porque se ha utilizado en la sección anterior. Utilice el comando
image push para empujar la imagen hacia el registro en GitHub.
Si ha recibido un mensaje similar a este significa que su imagen ha sido almacenada correctamente en
GitHub. Acceda a la web de su usuario en GitHub, específicamente en la pestaña Packeges, y podrá
ver el paquete correspondiente a su imagen. Utilice el siguiente enlace modificando el OWNER por su
usuario.
https://github.com/OWNER?tab=packages
Dedique unos minutos a explorar las opciones que brinda GitHub para esta imagen. Tenga en cuenta
el siguiente detalle, la visibilidad de la imagen es privada por defecto. Esto significa que solamente
usted puede acceder a ella. Si desea cambiar su configuración debe modificar su visibilidad a pública.
¡Enhorabuena! Ahora cuenta con otro registro de imágenes para compartir sus aplicaciones.
Cada Registro permite crear múltiples repositorios, donde cada repositorio almacena múltiples
etiquetas de la imagen. Para almacenar las imágenes en los repositorios es requisito que cumplan
con la estructura de nombre definida por el Registro. Para establecer los nombres en las imágenes se
utilizan las etiquetas, las cuales pueden ser adicionadas o eliminadas según sea necesario.
También se mostraron tres ejemplos de registros: ttl.sh, Docker Hub y GitHub. En cada caso se
explicaron los pasos a seguir para almacenar una imagen en cada uno de ellos. Existen muchos otros
registros en la industria, cada uno con sus ventajas y desventajas, de aquí la necesidad que explore
las opciones e identifique cuál es mejor para sus necesidades.
Después de tener la imagen distribuida en Registros todo queda listo para desplegar la aplicación en
servidores de producción. Si le interesa saber cómo lograrlo, el próximo capítulo tiene las respuestas
a sus preguntas.
Desplegar contenedores en la nube
En desarrollo …
Docker Compose V2
Pendiente de desarrollo.
Recomendaciones y Próximos Pasos
Pendiente de desarrollo.