Imagina que eres un jugador avanzando por los niveles de un videojuego en el que cada paso te acerca más a un sistema ideal. Has logrado montar servidores, gestionar infraestructuras, y ya te sientes bastante hábil en tu dominio. Sin embargo, en la siguiente pantalla aparece un nuevo desafío: Docker.
Este artefacto poderoso promete cambiar el juego y llevarte al siguiente nivel, pero dominarlo parece tan difícil como enfrentarte a un jefe final sin un manual de instrucciones.
Como en muchos videojuegos, este poder es esencial si quieres seguir adelante. Docker es esa herramienta que todos los que desarrollan aplicaciones o gestionan infraestructuras deben aprender para no quedarse atrás. Pero enfrentarse a él por primera vez puede ser intimidante.
Tu misión es clara: necesitas aprender Docker para mejorar tus desarrollos, ahorrar recursos y hacer que tus sistemas sean más eficientes. El problema es que no has encontrado un mentor o guía que te lleve de la mano.
Hasta ahora.
En este blog, te enseñaremos a dominar Docker como un pro. Considera en este artículo como una guía que te llevará a través de los niveles más básicos de Docker, hasta que logres usarlo con maestría.
Volvamos en el tiempo
Antes de Docker, la mayoría de las aplicaciones requerían un servidor para funcionar. Esto implicaba altos costos porque un servidor, generalmente, tenía más capacidad de la que una sola aplicación necesitaba. Además, mover una aplicación de un servidor a otro era complicado: si el nuevo servidor no tenía el mismo sistema operativo, simplemente no funcionaba.
La era de la virtualización
Aquí es donde aparece la virtualización, como un «power-up» que cambia las reglas del juego. En lugar de un servidor por aplicación, podías tener múltiples máquinas virtuales (VMs) en un solo servidor. Cada VM funciona como una computadora independiente, permitiendo que diferentes sistemas operativos coexistieran en el mismo espacio.
Contenedores: la siguiente evolución
Pero las máquinas virtuales no son perfectas. Consumen muchos recursos, y debes mantener cada sistema operativo de manera independiente. Aquí es donde los contenedores entran en acción, como la siguiente evolución en este videojuego.
Los contenedores, a diferencia de las VMs, no necesitan un sistema operativo completo.
En lugar de eso, utilizan el sistema operativo de la máquina host , haciendo que todo sea más liviano y rápido.
Docker, el héroe de la historia
Y aquí es donde entra Docker, el motor que permite crear y gestionar estos contenedores de manera eficiente. Con Docker, puedes crear aplicaciones portátiles, escalables y fáciles de mantener.
Las imágenes y los contenedores: tus herramientas clave
Ahora que ya tienes una idea general de lo que es Docker, es hora de profundizar en dos de los conceptos más importantes: imágenes y contenedores.
Pensemos en una imagen de Docker como una receta detallada. Esta receta (imagen) no solo contiene las instrucciones (Dockerfile) para un plato específico, como una pizza (contenedor), sino que también incluye todos los ingredientes pre-medidos (códigos, configuraciones y las dependencias necesarias) y herramientas necesarias.
Pero tener una receta no es suficiente. Necesitas convertir esa receta en una pizza real, ¿verdad?
Aquí es donde entran los contenedores.
¿Cómo se crean las imágenes?
Para crear una imagen es necesario utilizar un archivo especial llamado Dockerfile (en nuestra analogía se trata de las instrucciones para una pizza), que indica paso a paso qué componentes necesita tu aplicación y cómo deben instalarse.
Por ejemplo, si quieres construir una aplicación con Node.js, tu Dockerfile podría incluir los siguientes pasos:
- Empieza con una imagen base (un sistema operativo ligero como Alpine Linux).
- Añade Node.js.
- Copia tu código dentro de la imagen.
- Especifica cómo debe ejecutarse la aplicación.
Una vez que tienes Dockerfile, Docker se encarga de construir la imagen. Esta imagen luego se puede compartir y reutilizar.
Los contenedores: ¿Cómo funcionan?
Un contenedor es una instancia de tu imagen. Para continuar con nuestra analogía de la pizza: una vez que tienes la receta (imagen), podrías hacer tantas pizzas como quieras (contenedores), todas basadas en la misma receta.
La magia de Docker es que puedes tener múltiples contenedores corriendo simultáneamente, todos basados en la misma imagen, pero cada uno funcionando de manera independiente. Esto te permite escalar aplicaciones fácilmente o tener varias versiones corriendo sin problemas.
Volúmenes y redes: Lo que hace que todo funcione
Aquí es donde Docker muestra todo su poder. Además de manejar imágenes y contenedores, Docker también gestiona volúmenes y redes, dos elementos que permiten que las aplicaciones se comuniquen y almacenen datos de forma persistente.
- Los volúmenes funcionan como bases de datos donde tu aplicación guarda información, conservando los datos generados y utilizados por los contenedores Docker.
- Las redes en Docker son como carreteras que conectan y comunican diferentes contenedores entre sí o con el mundo exterior.
Hasta aquí deberías tener una idea bastante clara de cómo funcionan los componentes básicos de Docker.
Los Componentes de Docker: La Maquinaria Detrás del Telón
Para que Docker funcione correctamente, se basa en varios componentes clave. Cada uno tiene una función específica en el proceso de creación y gestión de contenedores.
client (cliente)
El Cliente de Docker es como el control remoto de tu videojuego. Es la interfaz que utilizas para interactuar con Docker. A través de este, envías comandos para crear, gestionar y destruir contenedores. Cuando ejecutas un comando en la línea de comandos, es el cliente el que se encarga de comunicarse con el Daemon de Docker para que realice las acciones solicitadas.
daemon (dockerd)
El Daemon de Docker, conocido como dockerd, es el cerebro detrás de Docker. Se encarga de ejecutar los comandos que le envía el Cliente y maneja todo lo relacionado con la creación, ejecución y gestión de contenedores. Es el proceso en segundo plano que hace que las cosas sucedan.
containerd
containerd es un nivel intermedio en la arquitectura de Docker. Se encarga de gestionar el ciclo de vida de los contenedores, desde su creación hasta su eliminación. Administra aspectos como el inicio, la detención y la eliminación de contenedores, y también se encarga de manejar volúmenes, redes y otros recursos asociados.
runc
runc es el componente más cercano al sistema operativo. Es un runtime de bajo nivel que se encarga de ejecutar los contenedores. Interactúa directamente con el sistema operativo para iniciar y detener los contenedores.
Instalando Docker: Preparándote para jugar
Ahora que entiendes los componentes básicos, es hora de instalar Docker. Dependiendo de tu sistema operativo, el proceso varía un poco. Aquí te damos una guía rápida para Linux, Windows y Mac.
Instalación en Linux.
Para los usuarios de Linux, instalaremos Docker en Ubuntu. Sigue estos pasos para que tu sistema esté listo para correr contenedores:
- Actualiza los paquetes existentes:
sudo apt-get update
2. Instala las dependencias necesarias:
sudo apt-get install
apt-transport-https
ca-certificates
curl
gnupg
lsb-release
3. Añade la clave GPG del repositorio de Docker:
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
4. Añade el repositorio de Docker:
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
5. Actualiza los paquetes nuevamente:
sudo apt-get update
6. Instala Docker:
sudo apt-get install docker-ce docker-ce-cli containerd.io
7. Verifica la instalación:
sudo docker -v
Si deseas ejecutar Docker siendo root:
sudo groupadd docker
Y agregar usuario al grupo de Docker para ejecutar Docker sin ser root
sudo usermod -aG docker $USER
Instalación en Windows.
Para Windows, el proceso es mucho más simple. Solo sigue estos pasos:
- Descarga Docker Desktop desde el sitio web oficial.
- Instala el software siguiendo las instrucciones del instalador.
- Inicia Docker Desktop y asegúrate de que esté funcionando correctamente.
Instalación en Mac.
Al igual que en Windows, la instalación en Mac es bastante directa:
- Descarga Docker Desktop desde el sitio web oficial.
- Abre el archivo descargado y arrastra el icono de Docker a tu carpeta de Aplicaciones.
- Inicia Docker Desktop y verifica que esté funcionando.
¡Prueba Docker sin instalación!
Si no quieres instalar Docker de inmediato, puedes usar Play with Docker. Este sitio te proporciona un entorno de prueba en línea durante 4 horas para que puedas experimentar con Docker sin necesidad de instalar nada en tu máquina.
Primeros pasos con Docker: Crear y ejecutar contenedores
Ahora que tienes Docker instalado y funcionando, es el momento de adentrarse en los conceptos que hacen que Docker sea tan poderoso. Vamos a desglosar imágenes, contenedores y volúmenes de una manera sencilla, usando analogías para que todo quede claro.
Imágenes.
Las imágenes de Docker son como las plantillas en un juego: contienen todo lo necesario para ejecutar una aplicación, incluyendo el código, las dependencias, la configuración y las variables de entorno. Si estás familiarizado con las máquinas virtuales, una imagen es similar a un template o una snapshot. En términos de programación, podríamos considerar una imagen como una clase, mientras que un contenedor sería una instancia de esa clase.
¿Cómo se recuperan las imágenes?
Cuando instalas Docker, no hay imágenes predefinidas en tu máquina. Puedes verificar esto con el comando:
docker image ls
Si no hay imágenes, obtendrás una lista vacía como esta:
REPOSITORY TAG IMAGE ID CREATED SIZE
Para recuperar una imagen, usas el comando de pull
:
docker image pull repository:tag
«OR
docker pull node: latest»
Por ejemplo, para descargar la última versión de Node.js, usarías:
docker image pull node:latest
NOTA: Docker Hub es un marketplace en el que puedes encontrar imágenes oficiales, como la de node:latest
, junto con imágenes creadas por la comunidad o por empresas. Además, puedes almacenar tus propias imágenes a Docker Hub para compartirlas o reutilizarlas en otros proyectos.
Verás mensajes de progreso mientras se descargan varios elementos, por ejemplo.
latest: Pulling from library/node
d960726af2be: Pull complete
e8d62473a22d: Pull complete
8962bc0fad55: Pull complete
65d943ee54c1: Pull complete
532f6f723709: Pull complete
f8463f32765b: Pull complete
39c1cd906e85: Pull complete
9b89015c57b4: Pull complete
6a93d724110f: Pull complete
Digest:sha256:ddc2ebfc3759299afb2085bba7648416e016b326389323a257c4f8d1da097c9c
Status: Downloaded newer image for node:latest
docker.io/library/node:latest
Una vez descargada, puedes verificar la imagen con:
docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
node latest 7493e35c7ffa 13 days ago 908MB
¿Dónde se almacenan estas imágenes?
Las imágenes están almacenadas en lo que se llama image registries. Un registro es un repositorio donde se almacenan y distribuyen las imágenes de Docker, similar a npm para JavaScript o Packagist para PHP. Existen registros tanto públicos como privados, lo que permite almacenar imágenes de acceso abierto o restringido, según las necesidades. El registro por defecto es Docker Hub, un registro público, pero también hay otros disponibles que pueden ser configurados como privados para un control más seguro y personalizado de las imágenes.
Puedes ver el registro utilizado por Docker con:
docker info
Esto te mostrará información sobre el registro actual, como:
Server:
...
Registry: https://index.docker.io/v1/
...
Entender los repositorios y Tags:
Un registro puede contener varios repositorios, cada uno con diferentes versiones (tags) de las imágenes. Por ejemplo, en el Docker Hub, la imagen node puede tener diferentes tags, como latest o 16. Si descargas una imagen con un tag específico, como node:16, y luego listas las imágenes, verás que los IDs de las imágenes pueden ser iguales, indicando que en esencia es la misma imagen.
Las Capas (Layers) de las Imágenes:
Cada imagen Docker está compuesta de varias capas, y cada una es de solo lectura. Estas capas se crean a partir de las instrucciones en un Dockerfile. Cada línea en el Dockerfile (como FROM
, RUN
, COPY
, etc.) genera una nueva capa.
Docker utiliza un sistema de archivos llamado Union File System para fusionar estas capas y presentar una vista unificada. Una ventaja importante de este sistema es que las capas pueden ser compartidas entre diferentes imágenes, lo que optimiza el almacenamiento y la eficiencia.
Por ejemplo, la imagen oficial de Node está compuesta de múltiples capas, cada una creada a partir de una instrucción específica en su Dockerfile.
Comandos Principales para Imágenes:
Recuperar una Imagen:
docker image pull [OPTIONS] NAME[:TAG|@DIGEST]
Ejemplo para descargar una versión específica:
docker image pull node:16-alpine3.11
Listar Imágenes:
docker image ls [OPTIONS] [REPOSITORY[:TAG]]
- Algunas opciones útiles:
--all
,-a
: Lista todas las imágenes.--filter
: Aplica un filtro para mostrar imágenes específicas, como las sin tags.
Por ejemplo, para mostrar imágenes sin tags:
docker image ls --filter dangling=true
Eliminar Imágenes:
docker image rm [OPTIONS] IMAGE [IMAGE...]
Para eliminar imágenes no etiquetadas:
docker image prune
Buscar Imágenes:
docker search TERM
Por ejemplo, para buscar un imagen en Redis:
docker search redis
Contenedores: La Instancia en Acción
Un contenedor es simplemente un entorno de ejecución de procesos aislado del resto del sistema, por lo que no es posible que un contenedor acceda a los recursos de otro contenedor o de la máquina host, al menos si no se le ha autorizado explícitamente.
Para iniciar tu primer contenedor, comienza con algo simple. Utiliza la siguiente orden para ejecutar un contenedor de prueba que te saludará con un mensaje amigable:
docker run hello-world
¿Qué sucederá aquí? Docker está realizando estos pasos:
- El cliente Docker envía una orden al demonio Docker.
- El demonio Docker descarga la imagen hello-world desde Docker Hub (si aún no está localmente).
- El demonio de docker (dockerd) crea un nuevo contenedor a partir de esta imagen y ejecuta el programa dentro.
- Finalmente, el demonio transmite el mensaje al cliente Docker y lo muestra en la terminal.
¡Felicidades! Has lanzado tu primer contenedor. Pero no te detengas aquí.
Prueba con algo más ambicioso como un contenedor Ubuntu:
docker run -it ubuntu bash
Aquí usamos las opciones -it
para activar el modo interactivo y acceder a la terminal del contenedor. Verás un prompt de Ubuntu en el contenedor, ¡ahora puedes explorar y ejecutar comandos!
Trabajando con Contenedores
Ya que estás dentro del contenedor, ejecuta comandos como ls -al
para listar los archivos ocultos y todos los archivos/directorios que hay. Y ps -l
para ver los procesos en ejecución. Verás que el único programa ejecutándose es el bash que lanzaste, y una vez que termines con él, el contenedor se detendrá.
Para salir del contenedor, simplemente usa el comando exit
y regresarás a tu máquina anfitriona
Desplegando Aplicaciones
Ahora, intentemos algo diferente. Lanza un contenedor con Apache en segundo plano usando:
docker run -d --name test-apache2 -p 8080:80 httpd
Aquí usamos varias opciones nuevas:
-d
para ejecutar el contenedor en segundo plano.--name
para darle un nombre al contenedor.-p
para mapear el puerto 8080 del anfitrión al puerto 80 del contenedor.
Visita http://localhost:8080/ para verificar que Apache está funcionando.
Puedes conectarte al contenedor en ejecución con:
docker exec -ti test-apache2 bash
Desde aquí, puedes modificar archivos y ver los cambios reflejados en tiempo real en tu navegador.
Gestionando el Ciclo de Vida de los Contenedores
Los contenedores tienen un ciclo de vida que incluye inicio, detención y eliminación.
Para detener un contenedor, usa:
docker container stop test-apache2
- Si verificamos los contenedores en ejecución con el comando
docker container ls
(también podemos usardocker ps
)
docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
- Observaremos que no hay contenedores en ejecución. Para ver todos los contenedores, incluso los detenidos, debemos agregar la opción
-a
(o--all
)
Para eliminar un contenedor detenido, usa:
docker container rm test-apache2
- Verifiquemos que se detenido correctamente
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Recuerda que un contenedor debe estar detenido antes de ser eliminado. Puedes también pausar y reanudar contenedores según sea necesario.
Políticas de Reinicio
Puedes configurar cómo se reinician tus contenedores usando la opción --restart
. Las opciones disponibles incluyen:
no
: No reinicia los contenedores automáticamente.always
: Reiniciar siempre que se detenga.unless-stopped
: Reiniciar siempre que se detenga, excepto si se detiene manualmente.on-failure
: Reiniciar solo si el contenedor falla.
Ejemplo de uso con Ubuntu:
docker run -it --restart always ubuntu bash
Esto hará que el contenedor se reinicie automáticamente si se detiene.
Comandos Clave para Contenedores
Aquí tienes algunos comandos esenciales para manejar contenedores:
- Iniciar un contenedor:
docker container run [OPTIONS] IMAGE [COMMAND] [ARG...]
Opciones útiles incluyen -d
para ejecución en segundo plano y --name
para asignar un nombre al contenedor.
- Detener un contenedor:
docker container stop [OPTIONS] CONTAINER [CONTAINER...]
Este comando envía un SIGTERM
al proceso principal del contenedor y, si es necesario, SIGKILL
después de 10 segundos.
- Eliminar contenedores:
docker container rm [OPTIONS] CONTAINER [CONTAINER...]
Para eliminar un contenedor, primero debe estar detenido. Usa -f para forzar la eliminación de un contenedor en ejecución.
- Ejecutar el comando en un contenedor:
docker container exec [OPTIONS] CONTAINER COMMAND [ARG...]
Esto te permite ejecutar comandos directamente en un contenedor en funcionamiento.
- Algunas opciones útiles son:
Modo interactivo: --interactive
, -i
Abrir una terminal: --ty
, -t
- Listar contenedores:
docker container ls [OPTIONS]
Añade -a
(o --all
) para ver todos los contenedores, incluidos los que están detenidos.
La persistencia de datos en Docker
Una de las grandes dificultades con Docker es cómo manejar los datos dentro de los contenedores. Cuando trabajas con imágenes, estas están formadas por varias capas en modo de solo lectura. Esto es perfecto para crear entornos reproducibles y consistentes, pero ¿qué pasa con los datos que quieres conservar cuando terminas el trabajo? Aquí es donde entra el truco.
Cuando creas un contenedor, Docker añade una capa adicional en modo lectura/escritura.
El problema es que esta capa es efímera: cuando el contenedor se elimina, esta capa y todos los datos que contiene también se pierden. Afortunadamente, Docker ofrece dos soluciones para resolver este problema:
- los montajes vinculados (bind mounts);
- los volúmenes nombrados.
Montajes vinculados (Bind Mounts)
La primera solución es usar los montajes vinculados, que es una forma bastante sencilla de vincular un directorio en tu máquina host a un directorio en el contenedor.
Solo necesitas usar la opción -v
en el comando docker run
, especificando el directorio en la máquina host y el directorio en el contenedor.
Por ejemplo, si tienes un contenedor Apache y quieres vincular el directorio /home/arkerone/docker/apache-test en tu máquina host con el directorio /usr/local/apache2/htdocs en el contenedor, el comando sería:
docker run -d --name test-apache2 -p 8080:80 -v /home/arkerone/docker/apache-test:/usr/local/apache2/htdocs httpd
Esto crea una vinculación directa entre tu máquina host y el contenedor.
Puedes modificar los archivos en el directorio de tu máquina host y esos cambios se reflejarán en el contenedor.
Sin embargo, ten en cuenta que los montajes vinculados dependen de la estructura del sistema de archivos de la máquina host, lo que puede causar problemas si accidentalmente modificas esa estructura. Además, pueden presentar riesgos de seguridad al permitir que un contenedor modifique la estructura del sistema de archivos del host.
Volúmenes nombrados
Para superar las limitaciones de los montajes vinculados, Docker ofrece volúmenes nombrados. Estos son gestionados completamente por Docker, independientemente de la estructura de archivos de la máquina host, lo que los hace una opción más robusta y segura.
Para crear un volumen nombrado, usa el siguiente comando:
docker volume create test-apache2-vol
Luego, puedes montar este volumen en un contenedor como lo harías con un montaje vinculado, pero sin preocuparte por la estructura del sistema de archivos de la máquina host. El comando sería:
docker run -dit --name test-apache2-1 -p 8080:80 -v test-apache2-vol:/usr/local/apache2/htdocs httpd
Si deseas usar la opción --mount
, también puedes hacerlo, pero asegúrate de que el volumen haya sido creado previamente:
docker run -dit --name test-apache2-1 -p 8080:80 --mount type=volume,src=test-apache2-vol,dst=/usr/local/apache2/htdocs httpd
Los volúmenes nombrados permiten que varios contenedores compartan los mismos datos. Por ejemplo, si creas un segundo contenedor que también usa el volumen test-apache2-vol, ambos contendores tendrán acceso a los mismos datos.
¿Montaje vinculado o volumen nombrado?
Entonces, ¿cuándo usar cada uno?
Los montajes vinculados son útiles en el desarrollo, donde necesitas ver rápidamente los cambios en tu código sin tener que reconstruir la imagen del contenedor. Sin embargo, para la mayoría de las situaciones de producción, los volúmenes nombrados son la opción recomendada. Son más seguros y gestionados por Docker, lo que evita los problemas asociados con la estructura de archivos del host.
Comandos principales para manejar volúmenes
Para trabajar con volúmenes en Docker, aquí tienes algunos comandos útiles:
- Crear un volumen:
docker volume create [OPTIONS] [VOLUME]
- Listar volúmenes:
docker volume ls [OPTIONS]
- Eliminar un volumen:
docker volume rm [OPTIONS] VOLUME [VOLUME...]
- Eliminar volúmenes no utilizados:
docker volume prune [OPTIONS]
Redes en Docker
La comunicación entre contenedores es otro aspecto esencial en Docker. Los contenedores necesitan una capa de red para comunicarse entre sí o con el mundo exterior. Docker proporciona varios tipos de redes, pero en este artículo nos centraremos en el tipo bridge, que es el más común.
Para crear una red bridge:
docker network create -d bridge localnetwork
Esto crea una red llamada localnetwork. Luego, puedes conectar contenedores a esta red y permitir que se comuniquen entre sí. Por ejemplo:
docker container run -it --name container-1 --network localnetwork alpine sh
docker container run -it --name container-2 --network localnetwork alpine sh
Desde el primer contenedor, puedes hacer ping al segundo contenedor para verificar la conexión:
/ # ping container-2
Comandos principales para manejar redes
Aquí tienes algunos comandos útiles para trabajar con redes en Docker:
Crear una red:
docker network create [OPTIONS] NETWORK
- Listar redes:
docker network ls [OPTIONS]
- Eliminar una red:
docker network rm NETWORK [NETWORK...]
- Eliminar redes no utilizadas:
docker network prune [OPTIONS]
- Conectar un contenedor a una red:
docker network connect [OPTIONS] NETWORK CONTAINER
- Desconectar un contenedor de una red:
docker network disconnect [OPTIONS] NETWORK CONTAINER
Conclusión
Docker puede parecer un desafío al principio, pero con la comprensión de las bases, te acercarás a dominar esta potente herramienta. Si bien este artículo cubre lo básico, hay mucho más por explorar.
¡Continúa practicando y experimentando con Docker, y pronto te sentirás como un experto en esta herramienta esencial!