Edición 2026. Un completo curso en audio sobre Docker, que cubre los conceptos básicos de los contenedores, imágenes, Dockerfile, redes, Compose, CI/CD y las últimas funciones de IA como MCP Toolkit, Docker Sandboxes y Docker Agent.
Descubre por qué Docker cambió radicalmente el desarrollo de software. Este episodio cubre la propuesta de valor fundamental de separar las aplicaciones de la infraestructura y lograr una paridad perfecta entre los entornos de desarrollo y producción.
3m 26s
2
Contenedores vs Máquinas Virtuales
Comprende las diferencias arquitectónicas entre los contenedores y las máquinas virtuales. Aprende cómo los contenedores logran el aislamiento compartiendo el kernel del host, lo que los hace increíblemente ligeros en comparación con los hipervisores tradicionales.
4m 29s
3
La anatomía de una imagen de Docker
Explora qué es realmente una imagen de Docker. Este episodio explica los principios de inmutabilidad de la imagen y la composición de capas, mostrando cómo se apilan los cambios del sistema de archivos para crear una plantilla de contenedor.
4m 17s
4
El plano del Dockerfile
Aprende a escribir un Dockerfile para construir imágenes personalizadas. Cubrimos instrucciones esenciales como FROM, RUN y CMD, y explicamos la diferencia crucial entre las formas shell y exec.
3m 41s
5
Dominando la build cache
Optimiza la construcción de tus imágenes utilizando la build cache de Docker. Aprende por qué el orden de las instrucciones en tu Dockerfile es fundamental para evitar instalaciones innecesarias de dependencias.
4m 00s
6
Multi-Stage Builds
Mantén tus imágenes de producción ligeras y seguras. Este episodio presenta las Multi-Stage Builds, demostrando cómo separar tu pesado entorno de compilación de tu entorno de ejecución minimalista.
4m 21s
7
Ejecución e interacción
Aprende la mecánica práctica de la ejecución de contenedores. Cubrimos los modos detached frente a interactive, la publicación básica de puertos y cómo ejecutar comandos de shell dentro de un contenedor en ejecución.
4m 29s
8
Conceptos básicos de persistencia de datos
Evita la pérdida catastrófica de datos cuando se eliminan los contenedores. Este episodio compara los Bind Mounts para el hot-reloading en desarrollo local con los Docker Volumes para la persistencia segura de bases de datos.
4m 18s
9
Redes de contenedores
Comprende cómo Docker maneja el tráfico de red. Aprende los conceptos básicos de la publicación de puertos en el host y cómo los contenedores se comunican de forma segura entre sí a través de redes bridge aisladas.
4m 26s
10
Introducción a Docker Compose
Ve más allá de los comandos de un solo contenedor. Aprende cómo Docker Compose utiliza un archivo YAML declarativo para definir, conectar en red y orquestar múltiples servicios simultáneamente.
4m 40s
11
Docker en la pipeline de CI/CD
Elimina los tests inestables con entornos de build en contenedores. Este episodio cubre cómo usar Docker en pipelines de Continuous Integration para garantizar pruebas automatizadas perfectamente reproducibles.
3m 38s
12
Imágenes multiplataforma
Resuelve la incompatibilidad entre Apple Silicon y los servidores en la nube. Aprende cómo Docker Buildx te permite compilar de forma cruzada y empaquetar aplicaciones para las arquitecturas ARM y AMD64 simultáneamente.
4m 31s
13
El Docker MCP Toolkit
Conecta de forma segura tus agentes de IA a herramientas locales. Este episodio presenta el Docker Model Context Protocol (MCP) Toolkit, explicando cómo gestionar servidores MCP en contenedores utilizando catálogos y perfiles.
4m 12s
14
Auto-Discovery dinámico de MCP
Explora Dynamic MCP, una función experimental que permite a los clientes de IA buscar en el Docker MCP Catalog e instalar dinámicamente nuevos servidores de herramientas durante una conversación sin configuración manual.
4m 47s
15
Docker Sandboxes para IA
Comprende la arquitectura de Docker Sandboxes. Aprende por qué los agentes de programación de IA autónomos requieren microVMs aisladas con demonios de Docker dedicados en lugar de namespaces de contenedores estándar.
4m 29s
16
Construyendo equipos de agentes de IA
Deja de depender de un único modelo de IA para tareas complejas. Este episodio presenta el framework Docker Agent, mostrando cómo componer equipos especializados de agentes definidos en YAML.
3m 49s
17
Toolsets y workflows de agentes
Haz que tus agentes de IA sean realmente útiles dándoles las restricciones adecuadas. Aprende a configurar toolsets del sistema de archivos y a aplicar workflows de desarrollo estructurados en Docker Agent.
3m 55s
18
Modelos de IA en Compose
Trata tus LLMs locales como cualquier otra dependencia de la aplicación. Aprende a declarar, configurar y vincular modelos de IA directamente dentro de tu archivo YAML de Docker Compose.
3m 34s
Episodios
1
La promesa de Dev igual a Prod
3m 26s
Descubre por qué Docker cambió radicalmente el desarrollo de software. Este episodio cubre la propuesta de valor fundamental de separar las aplicaciones de la infraestructura y lograr una paridad perfecta entre los entornos de desarrollo y producción.
Hola, soy Alex de DEV STORIES DOT EU. Masterclass de Docker, episodio 1 de 18. Acabas de pasarte tres días buscando un error que solo salta en el servidor de staging. El código se ejecuta sin problemas en tu portátil, pero en el momento en que llega al pipeline de deploy, se rompe. El culpable casi siempre es una librería del sistema incompatible, una versión diferente del runtime o una variable de entorno que falta. Esta es exactamente la fricción que Docker se diseñó para eliminar, cumpliendo la promesa de que dev sea igual a prod.
Docker es una plataforma abierta para desarrollar, distribuir y ejecutar aplicaciones. Su objetivo principal es separar tus aplicaciones de tu infraestructura. Históricamente, los desarrolladores escribían el código y los equipos de operaciones aprovisionaban los servidores. Los desarrolladores entregaban la aplicación, y el equipo de operaciones se pasaba horas o días configurando la máquina host para cumplir con los requisitos del software. Esta alineación manual de entornos es frágil y lenta.
Docker resuelve esto empaquetando la aplicación junto con sus dependencias, herramientas del sistema, librerías y el runtime en una unidad estandarizada llamada contenedor. Puede que asocies este concepto con las máquinas virtuales tradicionales. Aunque comparten el objetivo de aislar las aplicaciones, los contenedores son muchísimo más ligeros porque no necesitan un sistema operativo guest completo. Veremos esa diferencia de arquitectura en el próximo episodio. Ahora mismo nos vamos a centrar en lo que consigue este empaquetado.
Aquí está la clave. Como el contenedor guarda tanto el código como el entorno exacto necesario para ejecutarlo, la máquina host subyacente pasa a ser prácticamente irrelevante. Docker te asegura que si un contenedor funciona en tu portátil de desarrollo local, funcionará exactamente igual en un servidor de QA, y exactamente igual en un centro de datos de producción. Eliminas la frase de en mi máquina funciona, porque tu máquina y la máquina de producción ahora proporcionan exactamente el mismo entorno de ejecución.
El workflow es el siguiente. Un desarrollador escribe código en local y define el entorno necesario en un archivo de configuración de texto plano. Docker lee ese archivo y construye un artefacto estático llamado imagen. Esa única imagen inmutable es la que se testea. Cuando los tests pasan, se hace deploy de esa misma imagen a producción. No estás copiando código a un servidor y ejecutando un setup script. Estás moviendo todo el entorno de trabajo como una unidad sellada.
Esta portabilidad cambia cómo escalan los sistemas. Como los contenedores son estandarizados y ligeros, levantar nuevas instancias de una aplicación en respuesta a un pico de tráfico ocurre en segundos. Puedes mover fácilmente cargas de trabajo entre diferentes entornos, pasando una aplicación de un servidor de testing local a un cloud provider sin cambiar ni una sola línea de código o reconfigurar el host.
La conclusión final es que Docker transforma la infraestructura en una commodity predecible, creando un límite rígido donde los desarrolladores son dueños de todo el entorno dentro del contenedor, y operaciones simplemente proporciona el compute power para ejecutarlo. Si quieres ayudar a apoyar el programa, busca DevStoriesEU en Patreon. Eso es todo por este episodio. Gracias por escuchar, y sigue construyendo.
2
Contenedores vs Máquinas Virtuales
4m 29s
Comprende las diferencias arquitectónicas entre los contenedores y las máquinas virtuales. Aprende cómo los contenedores logran el aislamiento compartiendo el kernel del host, lo que los hace increíblemente ligeros en comparación con los hipervisores tradicionales.
Hola, soy Alex de DEV STORIES DOT EU. Masterclass de Docker, episodio 2 de 18. No necesitas arrancar un sistema operativo entero solo para ejecutar un único script de Python. Sin embargo, durante años, los desarrolladores aceptaron un overhead considerable y tiempos de arranque lentos para mantener sus aplicaciones aisladas entre sí. Hoy solucionamos esto analizando los contenedores frente a las máquinas virtuales.
Imagina ejecutar un stack complejo en local. Necesitas un frontend en React, una API en Python y una base de datos PostgreSQL funcionando simultáneamente. Si los instalas directamente en tu máquina host, te expones a conflictos de dependencias. La API podría requerir una versión específica de una librería del sistema que entre en conflicto con lo que necesita tu base de datos.
Un contenedor resuelve esto actuando como un proceso en un sandbox. Aquí está la clave. Un contenedor no es un ordenador en miniatura. Es estrictamente un proceso que se ejecuta de forma nativa en tu máquina host. La magia reside en el aislamiento. Gracias a las características integradas en el sistema operativo, a este proceso se le asigna su propio filesystem privado, su propio stack de red y una vista aislada del sistema. Para la API de Python que se ejecuta dentro, parece ser el único software en la máquina. Para tu sistema operativo host, es simplemente otro proceso estándar, muy parecido a tu navegador web o tu editor de texto.
Como un contenedor es solo un proceso, comparte el kernel del sistema operativo host. Cuando la base de datos PostgreSQL dentro de un contenedor necesita asignar memoria o escribir un registro en el disco, se comunica directamente con el kernel del host. No hay intermediarios ni ningún sistema operativo secundario arrancando en segundo plano. Por eso, arrancar un contenedor es prácticamente instantáneo. Tarda exactamente lo mismo que arrancar la aplicación directamente.
Ahora, contrasta esto directamente con una máquina virtual. Una máquina virtual aborda el aislamiento simulando hardware. Se basa en un hypervisor, que es un software que crea una CPU virtual, memoria virtual y una unidad de disco virtual. Sobre este hardware falso, tienes que instalar un sistema operativo guest completo.
Si quieres ejecutar esa misma API en Python en una VM aislada, tienes que arrancar una distribución de Linux entera. La VM carga su propio kernel independiente, inicializa los drivers de los dispositivos y arranca los servicios del sistema en segundo plano antes siquiera de pensar en ejecutar tu código en Python. Cada vez que la aplicación necesita leer un archivo, la petición pasa por el sistema operativo guest, baja al hypervisor y, finalmente, llega al hardware host. Esto proporciona un aislamiento de seguridad increíblemente fuerte, pero tiene un coste muy alto en ciclos de CPU, uso de memoria y tiempo de arranque.
Debido a estas diferencias, existe la idea errónea de que tienes que elegir una u otra. En realidad, los contenedores y las máquinas virtuales no son mutuamente excluyentes. En los entornos cloud modernos, casi siempre se usan juntos. Cuando aprovisionas una instancia cloud, estás alquilando una máquina virtual. Esa máquina virtual proporciona una fuerte barrera a nivel de hardware que separa tu workload del de otros clientes en el mismo servidor físico. Luego instalas un runtime de contenedores dentro de esa máquina virtual para ejecutar tu frontend en React, tu API y tu base de datos. La máquina virtual aísla la infraestructura, mientras que los contenedores aíslan las aplicaciones individuales.
La distinción, en última instancia, se reduce a los límites. Las máquinas virtuales virtualizan el hardware para ejecutar múltiples sistemas operativos, mientras que los contenedores virtualizan el sistema operativo para ejecutar múltiples procesos aislados.
Gracias por pasar unos minutos conmigo. Hasta la próxima, cuídate.
3
La anatomía de una imagen de Docker
4m 17s
Explora qué es realmente una imagen de Docker. Este episodio explica los principios de inmutabilidad de la imagen y la composición de capas, mostrando cómo se apilan los cambios del sistema de archivos para crear una plantilla de contenedor.
Hola, soy Alex de DEV STORIES DOT EU. Masterclass de Docker, episodio 3 de 18. Haces deploy de una aplicación y funciona perfectamente. Dos semanas después, reinicias exactamente la misma aplicación en la misma máquina y falla porque una librería del sistema se ha actualizado en segundo plano. Ese drift silencioso del entorno es precisamente lo que eliminamos al comprender la anatomía de una imagen de Docker.
Primero, aclaremos el punto de confusión más común. Una imagen no es un contenedor. Una imagen es una plantilla estática. Contiene el código de tu aplicación, tus librerías, tus herramientas del sistema y tu runtime. Un contenedor es simplemente una instancia en ejecución de esa imagen. Puedes arrancar mil contenedores a partir de una sola imagen, pero la imagen en sí se queda en el disco, esperando a ser leída.
La característica principal de una imagen de Docker es la inmutabilidad. Una vez creada, una imagen nunca se modifica. No puedes cambiar un archivo de configuración dentro de una imagen existente. Si quieres modificar una imagen, tienes que hacer una build de una completamente nueva. Esta inmutabilidad garantiza que una imagen probada en tu portátil se comporte de forma idéntica en producción. La plantilla no puede sufrir drift con el tiempo.
Si no puedes modificar una imagen, tienes que construir otras nuevas. Una imagen de Docker no es un solo archivo enorme. Es una composición de múltiples capas independientes apiladas unas sobre otras. Cada capa representa un conjunto específico de cambios en el filesystem, lo que implica añadir, modificar o eliminar archivos.
Imagina una aplicación Node.js. Rara vez haces la build del sistema operativo tú mismo. En su lugar, empiezas con una imagen base. Esta imagen base contiene una distribución mínima de Linux y el runtime de Node. Esa base, en realidad, está compuesta por sus propias capas, pero para ti, actúa como los cimientos.
Cuando añades tu aplicación a estos cimientos, Docker registra tus cambios como nuevas capas apiladas encima. Primero, traes tu archivo de configuración de dependencias. Eso crea una nueva capa. Luego, le dices al sistema que descargue e instale tus dependencias. Todas esas librerías descargadas se empaquetan en la siguiente capa. Finalmente, copias el código fuente de tu aplicación. Eso forma la capa superior.
Cuando arrancas un contenedor, Docker apila estas capas usando un union filesystem. Esto hace que todas las capas independientes se vean como una única estructura de directorios estándar. Si el mismo path del archivo existe en dos capas, la versión de la capa superior oculta la versión de la capa inferior.
Aquí está la clave. Como las capas son inmutables, se cachean y se comparten muchísimo. Si actualizas el código fuente de tu aplicación y haces una build de una nueva imagen, Docker calcula qué ha cambiado. Ve que la imagen base de Linux, el runtime de Node y tu capa de dependencias son idénticos a los de la build anterior. Reutiliza esas capas existentes al instante y solo crea una nueva capa para tu código actualizado. Esto convierte un deploy que podría tardar minutos en una operación de milisegundos.
Esta arquitectura también ahorra espacio en disco y ancho de banda de red. Cuando haces push de tu nueva imagen a un servidor, solo transmites la única capa que contiene el nuevo código fuente. El servidor ya tiene las capas base. Al forzar el uso de capas inmutables, Docker garantiza que el entorno de tu aplicación no pueda cambiar silenciosamente, a la vez que asegura que solo transmitas o almacenes los bytes exactos que has modificado.
Eso es todo por este episodio. ¡Nos vemos en la próxima!
4
El plano del Dockerfile
3m 41s
Aprende a escribir un Dockerfile para construir imágenes personalizadas. Cubrimos instrucciones esenciales como FROM, RUN y CMD, y explicamos la diferencia crucial entre las formas shell y exec.
Hola, soy Alex de DEV STORIES DOT EU. Docker Masterclass, episodio 4 de 18. Todo el entorno operativo de tu aplicación compleja se puede expresar en tan solo diez líneas de texto plano. Le pasas un único archivo a un build system, y obtienes un sistema perfectamente configurado, listo para ejecutarse en cualquier lugar. Este es el blueprint del Dockerfile.
Un Dockerfile es un documento de texto que contiene todos los comandos que un usuario podría ejecutar en la línea de comandos para ensamblar una imagen. El formato es sencillo. Cada línea empieza con una instrucción, escrita en mayúsculas por convención, seguida de los argumentos para esa instrucción. Docker lee este archivo línea por línea, de arriba a abajo.
Todo Dockerfile válido debe empezar con una base. Esto lo defines usando la instrucción FROM. Si escribes FROM ubuntu, Docker hace pull de la imagen oficial de Ubuntu y la usa como punto de partida. Cada línea siguiente en tu archivo modificará este entorno base.
Una vez que tu base está lista, normalmente necesitas instalar dependencias. Esto lo haces usando la instrucción RUN. La instrucción RUN ejecuta cualquier comando dentro de la imagen actual y hace commit del resultado. Si escribes RUN apt-get update seguido de los comandos de instalación de tus paquetes, Docker arranca el entorno, ejecuta el gestor de paquetes, instala el software y guarda el nuevo estado.
A continuación, necesitas tu aplicación real. Un sistema operativo con dependencias instaladas no sirve de nada sin tu código. La instrucción COPY se encarga de esto. Le pasas un path de origen desde tu workspace local y un path de destino dentro de la imagen. Docker coge tus archivos y los copia directamente en el filesystem del contenedor.
Hacer el build de la imagen es solo la primera fase. También tienes que decirle a Docker qué aplicación lanzar cuando arranque un contenedor. Defines este comportamiento por defecto usando la instrucción CMD o ENTRYPOINT.
Aquí está la clave. Hay dos formas distintas de formatear estas instrucciones de ejecución, y mezclarlas causa bugs sutiles.
El primer enfoque es la forma shell. Escribes la instrucción seguida del comando exactamente como lo escribirías en una terminal. Cuando Docker ve esto, envuelve tu comando en una shell, ejecutándolo a través de bin barra sh. Esto es muy cómodo porque las variables de entorno se expanden automáticamente. Sin embargo, el proceso de la shell se queda entre Docker y tu aplicación. Si Docker envía una señal para detener el contenedor de forma controlada, la shell la atrapa y tu aplicación nunca la recibe, lo que provoca terminaciones forzadas.
El segundo enfoque es la forma exec. Esta se escribe como un array JSON. Lo formateas con corchetes, pasando el ejecutable como el primer string y sus argumentos como los strings siguientes. Cuando usas la forma exec, Docker se salta la shell por completo. Ejecuta tu ejecutable directamente. Tu aplicación se convierte en el process ID uno dentro del contenedor. Esto garantiza que las señales del sistema pasen directamente a tu aplicación, asegurando apagados suaves y predecibles.
Si quieres un contenedor de producción estable, usa siempre la forma exec para tus comandos finales, para que tu aplicación controle su propio ciclo de vida.
Eso es todo por este episodio. Gracias por escuchar, ¡y sigue programando!
5
Dominando la build cache
4m 00s
Optimiza la construcción de tus imágenes utilizando la build cache de Docker. Aprende por qué el orden de las instrucciones en tu Dockerfile es fundamental para evitar instalaciones innecesarias de dependencias.
Hola, soy Alex de DEV STORIES DOT EU. Docker Masterclass, episodio 5 de 18. Si tu build de Docker tarda diez minutos cada vez que cambias una sola línea de código fuente, lo estás haciendo mal. La solución está totalmente en cómo estructuras tu archivo, y eso significa dominar la build cache.
Cuando lanzas un build de Docker, el builder procesa tu Dockerfile secuencialmente, de arriba a abajo. Cada instrucción, ya sea copiar un directorio o ejecutar un script, genera una capa distinta en la imagen resultante. Como ejecutar estos pasos requiere tiempo de computación y ancho de banda de red, Docker guarda automáticamente el resultado de cada paso en una build cache local. En builds posteriores, el engine intenta reutilizar estas capas guardadas para saltarse el trabajo redundante.
Para determinar si es posible un cache hit, Docker evalúa cada instrucción comparándola con el historial de la cache existente. Para las instrucciones que ejecutan comandos, comprueba si el command string en sí es idéntico al usado en el build anterior. Para las instrucciones que copian archivos desde tu máquina host a la imagen, Docker va un paso más allá. Calcula un checksum del contenido y los metadatos de cada archivo que se está copiando. Luego, compara este nuevo checksum con el checksum de los archivos de la capa previamente cacheada. Si los checksums coinciden perfectamente, Docker reutiliza la capa cacheada y pasa a la siguiente línea. Si difiere aunque sea un solo byte, la cache se invalida.
Presta atención a esto. La invalidación de la cache es una reacción en cadena estricta. En el instante en que Docker detecta un cambio e invalida una capa, deja de mirar la cache para el resto del build. Cada instrucción que viene después de la capa invalidada se ve forzada a ejecutarse desde cero. Esto sucede porque cada capa depende del estado exacto de la capa anterior.
Esta reacción en cadena determina cómo debes organizar tu Dockerfile. Imagina una aplicación Node donde gestionas dependencias externas. Un error frecuente es usar una sola instrucción para copiar toda la carpeta de tu proyecto en la imagen, seguida de una instrucción para ejecutar tu comando de instalación de paquetes. Si modificas una sola línea en un archivo de texto en cualquier parte de tu código fuente, el checksum de la instrucción de copia cambia. La cache se rompe en ese paso. Como consecuencia, la siguiente instrucción se ve forzada a ejecutarse. Te toca esperar a que se vuelvan a descargar cientos de megabytes de dependencias, aunque tu lista real de dependencias haya quedado totalmente intacta.
El enfoque óptimo aísla las dependencias del código de la aplicación. Primero, añades una instrucción para copiar únicamente tu archivo de configuración de dependencias, en concreto tu package manifest, a la imagen. Segundo, ejecutas el comando para descargar las dependencias. Tercero, añades una instrucción separada para copiar el resto de tu código fuente general.
Ahora, cuando modificas ese mismo archivo de texto y haces un rebuild, Docker evalúa la primera instrucción. El dependency manifest no ha cambiado, así que se usa la cache. Pasa al paso de instalación. Como la capa anterior fue un cache hit y el command string es idéntico, la cache se usa aquí de nuevo, saltándose la descarga masiva. La cache solo se rompe en la instrucción final, donde el builder copia tus archivos fuente actualizados. Una espera de diez minutos se convierte en una actualización de dos segundos.
La forma más efectiva de acelerar tu pipeline es ordenar tus instrucciones estrictamente desde las menos propensas a cambiar hasta las más propensas.
Eso es todo por este episodio. ¡Gracias por escuchar, y sigue desarrollando!
6
Multi-Stage Builds
4m 21s
Mantén tus imágenes de producción ligeras y seguras. Este episodio presenta las Multi-Stage Builds, demostrando cómo separar tu pesado entorno de compilación de tu entorno de ejecución minimalista.
Hola, soy Alex de DEV STORIES DOT EU. Docker Masterclass, episodio 6 de 18. Hacer deploy de tu compilador a producción es un riesgo de seguridad enorme, y aumenta el tamaño de tu contenedor en gigabytes. Escribes código limpio, pero tu artifact final se ve lastrado por todas las herramientas pesadas que necesitas solo para hacer la build. La solución a esto son las multi-stage builds.
Cuando haces la build de aplicaciones en lenguajes compilados como Java, Go o C++, el proceso de compilación requiere build tools, Software Development Kits y el código fuente tal cual. Históricamente, los developers usaban un enfoque estándar en el que instalaban todas estas dependencias en el contenedor, compilaban el código y luego ejecutaban la aplicación. El problema es que todas esas build tools se quedan en la imagen de producción final. Acabas haciendo deploy de tu compilador, tu package manager y los archivos intermedios junto a tu aplicación real. Esto hace que tu contenedor sea enorme. Los contenedores grandes tardan más en hacer pull a través de la red y consumen más almacenamiento. Peor aún, crea una superficie de ataque enorme. Si un atacante logra vulnerar tu contenedor, de repente tiene a su disposición un entorno de desarrollo totalmente equipado.
Un error común es pensar que solucionar esto requiere mantener dos archivos separados: un archivo para hacer la build del software, y un script para extraer el resultado y pasarlo a un segundo archivo para el deploy. Ese no es el caso. Las multi-stage builds gestionan toda esta separación de responsabilidades dentro de un solo archivo.
Aquí está la clave. Una multi-stage build te permite definir múltiples entornos distintos, o stages, de forma secuencial. Cada stage empieza definiendo su propia imagen base. Empiezas el primer stage con una imagen base pesada que contiene todas tus herramientas de desarrollo. Le asignas un nombre a este stage, como builder. Dentro de este stage builder, copias tu código fuente desde tu máquina local y ejecutas tus comandos de compilación. El stage builder hace el trabajo pesado, generando el archivo ejecutable final.
Luego, más abajo en ese mismo archivo, defines una segunda imagen base. Esto inicia un stage completamente nuevo. Para este stage, eliges una imagen de runtime mínima. Este entorno solo contiene las dependencias exactas necesarias para ejecutar la aplicación, con cero build tools.
En lugar de copiar archivos desde tu máquina local otra vez, usas una instrucción copy especializada. Esta instrucción le dice al build engine que vuelva al stage builder, coja solo el artifact terminado y compilado, y lo suelte en tu nuevo stage mínimo. Cuando el build engine termina, produce un contenedor basado únicamente en el stage final. Todo lo del primer stage (el compilador, los paquetes descargados, el código fuente) se descarta por completo. Nunca llega a tu imagen de producción.
Imagina un escenario concreto con una aplicación Java Spring Boot. En tu archivo, tu primer stage usa una imagen de Maven voluminosa. Dentro de este stage, ejecutas el comando de Maven para empaquetar tu aplicación. Maven descarga todas las dependencias necesarias del proyecto, compila el código Java y lo empaqueta en un archivo JAR terminado.
A continuación, empiezas el segundo stage usando una imagen base ligera de Java Runtime Environment. No instalas Maven en este entorno. No copias tus archivos fuente de Java aquí. En su lugar, le indicas al engine que copie solo el archivo JAR compilado directamente desde el stage de Maven a este entorno de runtime mínimo. Finalmente, configuras el comando por defecto para ejecutar ese archivo JAR.
Al separar estrictamente el entorno de build del entorno de runtime, garantizas que tu contenedor de producción esté completamente aislado de tus build tools. La imagen final solo ve el artifact compilado y el runtime mínimo indispensable, manteniendo tu deploy rápido, ligero y altamente seguro.
Eso es todo por este episodio. ¡Hasta la próxima!
7
Ejecución e interacción
4m 29s
Aprende la mecánica práctica de la ejecución de contenedores. Cubrimos los modos detached frente a interactive, la publicación básica de puertos y cómo ejecutar comandos de shell dentro de un contenedor en ejecución.
Hola, soy Alex de DEV STORIES DOT EU. Masterclass de Docker, episodio 7 de 18. Iniciar un proceso en segundo plano es exactamente lo que quieres para un servidor web en producción. Pero cuando ese servidor se niega a cargar tu página, necesitas una forma de romper el cristal, entrar al entorno y ver qué se ha roto realmente. Este episodio trata sobre cómo ejecutar e interactuar con contenedores.
El comando principal para iniciar cualquier contenedor es docker run. Por defecto, si ejecutas un contenedor, conecta su output directamente a la pantalla de tu terminal. Toma el control de tu prompt, y si pulsas control C, el contenedor termina. Para un servicio de larga duración como un servidor web Nginx, esto es totalmente poco práctico. Quieres que el servidor se ejecute en segundo plano. Esto lo consigues usando la flag de modo detached, que escribes como un simple guion d. Cuando le pasas guion d, Docker inicia el contenedor, imprime un container ID largo y único en tu pantalla, e inmediatamente te devuelve el prompt de tu terminal. El contenedor sigue ejecutándose silenciosamente en segundo plano.
Sin embargo, ese contenedor en ejecución está aislado. Aunque Nginx esté sirviendo tráfico activamente en el puerto 80 dentro del contenedor, tu máquina host no puede verlo. Tienes que abrir explícitamente un agujero en ese aislamiento de red. Esto lo haces con la flag publish, que se escribe como guion p. Esto te permite mapear un puerto específico en tu portátil host a un puerto específico dentro del contenedor. Si especificas guion p 8080 dos puntos 80, Docker intercepta cualquier tráfico web que llegue a tu portátil por el puerto 8080 y lo enruta directamente al puerto 80 dentro del contenedor. Ahora tienes un servidor web detached al que puedes acceder sin problemas desde tu navegador local.
Pero, ¿qué pasa cuando cargas la página y ves un error de configuración? Tu servidor Nginx se está ejecutando en segundo plano, pero necesitas leer los archivos de configuración en su filesystem. Aquí está la clave. No necesitas parar un contenedor para mirar en su interior. En su lugar, usas el comando docker exec. Mientras que docker run crea un contenedor totalmente nuevo, docker exec ejecuta un comando totalmente nuevo dentro de un contenedor ya existente y en ejecución.
Para obtener una terminal útil y funcional, necesitas combinar dos flags específicas en guion i t. La i significa interactive. Esto mantiene abierto el canal de standard input, permitiéndote escribir comandos realmente en el contenedor. La t asigna una pseudo-TTY. Esto engaña al contenedor haciéndole creer que está conectado a una terminal física, lo cual es necesario para que los prompts y el formato del texto se muestren correctamente.
Si ejecutas docker exec guion i t, seguido del nombre del contenedor y el comando barra bin barra bash, entras instantáneamente a un prompt dentro del contenedor Nginx en ejecución. Ahora estás dentro de la caja. Puedes leer archivos de configuración, comprobar los logs de errores e inspeccionar el filesystem exactamente como lo harías en un servidor Linux estándar. Cuando termines, escribir exit cierra tu sesión de shell temporal. El contenedor Nginx en sí permanece completamente inalterado, y sigue ejecutándose en segundo plano.
Eventualmente, tendrás que hacer limpieza. Ejecutar docker stop con el nombre del contenedor envía una señal de terminación, dándole tiempo a la aplicación para cerrarse correctamente. Sin embargo, parar un contenedor no lo borra de tu sistema. El contenedor parado permanece en tu disco duro, conservando sus logs y cualquier cambio interno en el filesystem. Para borrarlo permanentemente y liberar ese espacio en disco, ejecutas el comando docker rm.
La distinción más crítica que debes memorizar es la diferencia entre run y exec. Docker run arranca un sistema aislado totalmente nuevo, mientras que docker exec te permite entrar en un sistema que ya está respirando.
Gracias por escuchar. Cuidaos todos.
8
Conceptos básicos de persistencia de datos
4m 18s
Evita la pérdida catastrófica de datos cuando se eliminan los contenedores. Este episodio compara los Bind Mounts para el hot-reloading en desarrollo local con los Docker Volumes para la persistencia segura de bases de datos.
Hola, soy Alex de DEV STORIES DOT EU. Docker Masterclass, episodio 8 de 18. Haces deploy de una base de datos dentro de un contenedor, escribes miles de filas y todo funciona a la perfección. Luego eliminas el contenedor para actualizar la imagen y tu base de datos desaparece para siempre. Por defecto, el almacenamiento de los contenedores es estrictamente temporal. Para evitar la pérdida de datos, necesitamos los fundamentos de la persistencia de datos.
Cuando un contenedor se inicia, crea una capa de escritura sobre su imagen subyacente. Cualquier archivo que el contenedor cree o modifique se almacena en esta capa específica. Si el contenedor se destruye, esa capa se destruye con él. Los datos son completamente efímeros. No existen fuera del ciclo de vida del propio contenedor. Para mantener los datos seguros, debes redirigirlos fuera del contenedor y a la máquina host. Docker ofrece dos mecanismos principales para esto: bind mounts y managed volumes.
Un bind mount mapea un path específico y explícito de tu máquina host directamente a un path dentro del contenedor. Le indicas a Docker exactamente qué carpeta de tu portátil debe aparecer dentro del entorno del contenedor. Esto depende en gran medida del sistema operativo host y de la estructura de archivos local. La máquina host conserva el control total sobre los archivos.
Este enfoque es perfecto para el desarrollo local. Haces un bind mount de tu directorio de código fuente local en el path de la aplicación web de tu contenedor. Cuando editas y guardas un script en tu portátil, el contenedor lee el archivo actualizado inmediatamente. Obtienes hot-reloading instantáneo sin necesidad de hacer rebuild de la imagen del contenedor cada vez que modificas una línea de código.
El segundo mecanismo es un managed volume. En lugar de apuntar a un path específico que controlas en tu disco duro, le pides a Docker que cree una entidad de almacenamiento. Docker aprovisiona el espacio en la máquina host y lo gestiona por completo. No necesitas saber dónde Docker coloca físicamente los archivos en tu sistema host. Simplemente le das un nombre al volumen e indicas al contenedor dónde montarlo internamente.
Los volúmenes son la solución estándar para la persistencia de bases de datos. Al ejecutar PostgreSQL, creas un volumen ejecutando un comando sencillo y asignándole un identificador, como db-data. Luego, al iniciar tu contenedor, pasas un flag de configuración que vincula ese volumen db-data con el path interno donde Postgres escribe los registros de sus tablas. Si detienes y eliminas el contenedor de la base de datos, Docker deja el volumen completamente intacto. Cuando levantas un nuevo contenedor más tarde, simplemente haces attach de ese volumen existente, y todos tus registros están intactos.
Aquí está la clave. La elección entre estos dos métodos se reduce a quién necesita acceder a los archivos. Usa bind mounts cuando tu máquina host necesite interactuar activamente con los datos, como un desarrollador editando código fuente. Usa managed volumes cuando el contenedor sea el dueño de los datos, como un motor de base de datos escribiendo registros, y simplemente quieras que Docker mantenga esos archivos seguros entre reinicios del contenedor.
Los contenedores efímeros son una decisión de diseño, no un defecto, porque te obligan a desacoplar tus datos de tu runtime compute. Asume siempre que tu contenedor será destruido inmediatamente, y mapea explícitamente tu estado persistente fuera de él. Si estos episodios te resultan útiles, puedes apoyar el programa buscando DevStoriesEU en Patreon. Eso es todo por este episodio. Gracias por escuchar, ¡y sigue construyendo!
9
Redes de contenedores
4m 26s
Comprende cómo Docker maneja el tráfico de red. Aprende los conceptos básicos de la publicación de puertos en el host y cómo los contenedores se comunican de forma segura entre sí a través de redes bridge aisladas.
Hola, soy Alex de DEV STORIES DOT EU. Docker Masterclass, episodio 9 de 18. Por defecto, un contenedor en ejecución está completamente aislado del mundo exterior. Se encuentra en una burbuja privada, y si quieres que internet lo alcance, tienes que abrir huecos intencionadamente en ese aislamiento. Gestionar esos huecos y las conexiones entre contenedores es el trabajo del Container Networking.
Cuando arranca un contenedor, Docker le asigna una dirección IP interna. El contenedor normalmente puede salir a internet para descargar actualizaciones o hacer peticiones de red, pero nada externo al host puede entrar. Para aceptar tráfico entrante, usas el port publishing. El publishing coge un puerto de tu host físico y lo vincula directamente a un puerto dentro del contenedor. Si tienes un contenedor con un servidor web escuchando internamente en el puerto ochenta, puedes publicarlo en el puerto ochenta ochenta de tu host. Cuando un usuario envía una request a tu host en el puerto ochenta ochenta, Docker la intercepta y la reenvía directamente a través del firewall al contenedor en el puerto ochenta. Configuras este mapeo al arrancar usando el flag publish. Sin ese flag, el contenedor sigue siendo inaccesible para la red externa.
Eso cubre el tráfico externo. Ahora, la segunda parte de esto es la comunicación interna. Las aplicaciones rara vez se ejecutan como un único proceso aislado. Normalmente tienes varios contenedores que necesitan compartir datos. Por defecto, Docker conecta cada nuevo contenedor a una red integrada llamada default bridge. Un bridge es un switch de red por software que se ejecuta en tu host. Conecta los contenedores para que puedan intercambiar paquetes, a la vez que los aísla de las redes externas.
Aquí está la clave. El default bridge permite que los contenedores se comuniquen usando sus direcciones IP internas, pero las IPs de los contenedores cambian cada vez que un contenedor se reinicia o se actualiza. Hacer hardcoding de una dirección IP en la configuración de tu aplicación romperá tu sistema casi de inmediato. Para solucionar esto, creas una red user-defined bridge.
Cuando conectas varios contenedores a un user-defined bridge personalizado, Docker proporciona resolución DNS interna automática. Esto significa que los contenedores pueden encontrarse entre sí usando sus nombres de contenedor exactos. Imagina un escenario donde tienes un contenedor con una aplicación backend y un contenedor con una base de datos. Creas una única red bridge personalizada y conectas ambos contenedores a ella. Dentro del código de tu aplicación backend, no escribes una connection string a la base de datos usando una dirección IP frágil. Simplemente usas el nombre del contenedor de la base de datos como dirección del host. Docker intercepta la query DNS, encuentra el contenedor de la base de datos en ese bridge específico, y enruta el tráfico a la dirección IP interna correcta de forma dinámica.
Este diseño te da control total sobre la seguridad de la aplicación. El backend y la base de datos pueden hablar entre sí libremente a través del bridge personalizado, pero ningún tráfico externo puede llegar a la base de datos. Para ejecutar tu aplicación de forma segura, dejas la base de datos oculta en el bridge interno privado sin ningún puerto publicado. Luego, publicas solo el puerto del contenedor del backend en tu host. Los usuarios externos llegan al puerto público del backend, y el backend hace queries a la base de datos de forma segura a través del bridge privado.
La arquitectura de tu aplicación dicta tu topología de red: usa puertos publicados para invitar a entrar a los usuarios externos, y user-defined bridges personalizados para dejar que tus contenedores internos hablen entre sí de forma segura por nombre. Eso es todo por este episodio. Gracias por escuchar, ¡y sigue programando!
10
Introducción a Docker Compose
4m 40s
Ve más allá de los comandos de un solo contenedor. Aprende cómo Docker Compose utiliza un archivo YAML declarativo para definir, conectar en red y orquestar múltiples servicios simultáneamente.
Hola, soy Alex de DEV STORIES DOT EU. Masterclass de Docker, episodio 10 de 18. No deberías necesitar un documento de texto lleno de comandos de terminal complejos solo para arrancar tu entorno de desarrollo local. Depender del historial de la shell para recordar los flags, puertos y nombres de red exactos para múltiples contenedores es una forma muy frágil de trabajar. La introducción a Docker Compose soluciona esto convirtiendo todo el stack de tu aplicación en un único archivo declarativo.
Cuando ejecutas una aplicación, rara vez existe de forma aislada. Normalmente tienes un servidor web, una base de datos y, quizás, una caching layer. Arrancarlos manualmente requiere ejecutar múltiples comandos independientes. Tienes que crear una red personalizada, conectar cada contenedor a ella, exponer los puertos correctos y montar las unidades de almacenamiento. Si cometes un error tipográfico en cualquiera de esos pasos, los contenedores no se pueden comunicar y la aplicación falla.
Docker Compose reemplaza este proceso imperativo por un archivo YAML declarativo, normalmente llamado compose punto yaml. En lugar de decirle a Docker exactamente qué hacer paso a paso, declaras el estado final deseado de todo tu sistema. Docker Compose averigua los pasos necesarios para alcanzar ese estado.
El archivo YAML se divide en tres secciones estructurales principales. La primera y más destacada se llama services. Un service es simplemente una definición para un contenedor específico de tu aplicación. Imagina un escenario donde estás ejecutando una aplicación Node junto a una base de datos MySQL. Bajo la sección services, defines dos entradas. A la primera la llamas web, especificando la imagen de Node y los puertos locales que quieres exponer. A la segunda la llamas database, especificando la imagen de MySQL y las variables de entorno necesarias, como la contraseña de root.
Aquí está la clave. No necesitas enlazar estos contenedores manualmente. Por defecto, Docker Compose crea automáticamente una única red interna para tu aplicación. Conecta todos los services definidos a esta red y asigna a cada contenedor un hostname que coincide con el nombre de su service. El código de tu aplicación Node puede conectarse a la base de datos simplemente apuntando al hostname database, y el DNS interno lo resuelve a la IP correcta del contenedor. Puedes definir manualmente redes personalizadas en la sección networks del archivo YAML, pero para la mayoría de setups de desarrollo estándar, el comportamiento por defecto hace exactamente lo que necesitas.
La última pieza estructural es la sección volumes. Las bases de datos requieren almacenamiento persistente. Si el contenedor de MySQL se apaga, no quieres que tus datos se borren. Al final de tu archivo YAML, declaras un named volume. Luego, dentro de la definición de tu service de base de datos, mapeas una ruta específica dentro del contenedor a ese named volume. Docker Compose gestiona la creación y el ciclo de vida de este almacenamiento por ti.
Una vez que tu archivo está escrito, gestionas todo el stack con dos comandos. Escribes docker compose up. Compose lee el archivo YAML, crea la red interna, configura los volumes y arranca los contenedores de MySQL y Node. Si quieres seguir trabajando en tu terminal, añades el flag detach para ejecutar todo en segundo plano.
Cuando terminas de trabajar, no detienes y eliminas cada contenedor individualmente. Escribes docker compose down. Compose detiene la app de Node de forma segura, detiene la base de datos, y elimina los contenedores y la red por defecto, manteniendo tu sistema completamente limpio. Deja tus named volumes intactos, lo que significa que los registros de tu base de datos te estarán esperando la próxima vez que levantes el stack.
Docker Compose cambia tu mentalidad de gestionar contenedores individuales aislados a gestionar entornos de aplicación completos. El setup de tu infraestructura se convierte en una única pieza de código a la que puedes hacer commit en el control de versiones y compartir al instante con tu equipo.
Eso es todo por este episodio. Gracias por escuchar, ¡y a seguir programando!
11
Docker en la pipeline de CI/CD
3m 38s
Elimina los tests inestables con entornos de build en contenedores. Este episodio cubre cómo usar Docker en pipelines de Continuous Integration para garantizar pruebas automatizadas perfectamente reproducibles.
Hola, soy Alex de DEV STORIES DOT EU. Docker Masterclass, episodio 11 de 18. Haces push de tu código, se ejecuta el pipeline y los tests fallan. Los ejecutas en local y pasan perfectamente. Tu servidor de CI tiene una versión de dependencia ligeramente anterior a la de tu portátil. Este desfase es la principal causa de los famosos flaky tests, pero contenerizar tu entorno de build hace que cada ejecución sea perfectamente predecible. Hoy hablaremos de Docker en el pipeline de CI/CD.
Históricamente, la Integración Continua implicaba mantener servidores de build estáticos. Con el tiempo, los ingenieros se conectan a estas máquinas virtuales para instalar paquetes, actualizar runtimes y ajustar configuraciones del sistema. Estos servidores se convierten en pet VMs. Acumulan estado oculto y archivos de caché residuales. Cuando un pipeline falla, pierdes tiempo intentando averiguar si el código está realmente roto o si el servidor solo necesita una actualización de software.
Usar Docker como entorno de build evita por completo este problema. En lugar de ejecutar tus scripts de test directamente en el sistema operativo host de un worker de CI, el worker levanta un contenedor. El runner de CI hace pull de una imagen Docker específica, arranca el contenedor, monta tu código fuente y ejecuta tus pasos de build dentro de ese entorno aislado.
Aquí está la clave. Cuando el job termina, el contenedor se destruye. La siguiente ejecución del pipeline obtiene un entorno completamente nuevo e idéntico. No hay procesos en background conflictivos de ejecuciones anteriores. El entorno es stateless y está totalmente definido por la imagen.
Piensa en el proceso de actualizar un runtime de programación. Supón que necesitas migrar tu proyecto de Node 18 a Node 20. En una configuración tradicional, alguien tiene que hacer login en el servidor de build, actualizar el software en todo el sistema y esperar que no rompa otros proyectos que comparten ese mismo worker. Con Docker como tu entorno de build, todo ese proceso es solo un cambio de string. Actualizas el tag de la imagen base en tu configuración de Node 18 a Node 20. El runner de CI hace pull de la nueva imagen. Tu build se ejecuta instantáneamente en el entorno actualizado. Si un test falla, reviertes el tag y lo vuelves a intentar más tarde. Gestionas la infraestructura directamente junto a tu código.
Hay otra capa en todo esto. Si utilizas Docker para hacer build de tu aplicación, tu pipeline de CI necesita la capacidad de hacer build y push de imágenes. Si tu job de CI ya se está ejecutando dentro de un contenedor, ¿cómo ejecutas los comandos de build de Docker? Esto requiere un patrón llamado Docker-in-Docker.
Docker-in-Docker significa ejecutar un daemon de Docker aislado dentro de tu contenedor de CI. El contenedor externo proporciona el entorno controlado para los pasos de tu pipeline, mientras que el daemon interno procesa los builds de tu aplicación. Esto permite que tu job de CI haga pull de imágenes base, construya el contenedor de tu aplicación y haga push del artifact final a un registry, todo sin ensuciar la máquina host que ejecuta el worker de CI.
Mover tu entorno de CI a un contenedor transfiere el control del sistema de build al desarrollador. La misma imagen que hace build de tu código en un servidor remoto se puede ejecutar en tu máquina local, lo que garantiza que si un test falla en CI, puedes reproducir ese mismo fallo en local.
Eso es todo por este episodio. ¡Gracias por escuchar y sigue programando!
12
Imágenes multiplataforma
4m 31s
Resuelve la incompatibilidad entre Apple Silicon y los servidores en la nube. Aprende cómo Docker Buildx te permite compilar de forma cruzada y empaquetar aplicaciones para las arquitecturas ARM y AMD64 simultáneamente.
Hola, soy Alex de DEV STORIES DOT EU. Masterclass de Docker, episodio 12 de 18. La frase "En mi máquina funciona" adquiere un significado completamente nuevo cuando tu máquina local usa un procesador ARM, pero tu nube de producción funciona con Intel. Pruebas tu contenedor en local, le haces push a un registry, le haces pull en el servidor, y crashea al instante con un error de formato de ejecución. El problema es una incompatibilidad en la arquitectura de hardware. Para solucionarlo, utilizas imágenes multiplataforma.
Una imagen de contenedor es, fundamentalmente, un conjunto de binarios y sistemas de archivos. Si haces la build de una imagen en un Mac con Apple Silicon, los binarios resultantes se compilan para la arquitectura ARM64. Cuando haces deploy de esa imagen en un servidor Linux estándar en la nube con un procesador AMD64, la CPU del host literalmente no entiende las instrucciones que hay dentro del contenedor. Históricamente, tenías que mantener build pipelines separados para diferentes targets de hardware. Docker Buildx elimina ese requisito.
Docker Buildx es un plugin de línea de comandos que extiende el sistema de build estándar de Docker. Utiliza un motor backend llamado BuildKit para ejecutar builds de forma concurrente y gestionar tareas complejas, como apuntar a múltiples plataformas en una sola pasada. Cuando haces la build de una imagen multiplataforma usando Buildx, no estás metiendo dos sistemas de archivos distintos en un único contenedor gigante. En su lugar, Buildx crea una manifest list de la imagen. Piensa en este manifest como una tabla de enrutamiento. Contiene una lista de punteros a diferentes imágenes específicas de cada arquitectura almacenadas en tu registry. Cuando una máquina hace pull de tu imagen, su daemon de Docker lee este manifest, identifica la arquitectura de su propia CPU host, y descarga automáticamente solo las capas de la imagen que coinciden con su hardware.
Para hacer compilación cruzada y empaquetar una API backend para ambas arquitecturas simultáneamente, utilizas el comando docker buildx build. Incluyes un flag platform, pasándole una lista de tus targets separados por comas. Por ejemplo, escribes el flag, seguido de linux barra amd64 coma linux barra arm64. Añades tu tag de imagen estándar, y luego añades un flag push.
Aquí está la clave. Al hacer la build para múltiples plataformas al mismo tiempo, no puedes simplemente cargar la imagen multiplataforma final de vuelta en la cache de tu motor Docker local. El daemon local no está diseñado para almacenar una manifest list que apunte a múltiples arquitecturas. Tienes que indicarle a Buildx que haga push de los resultados directamente a tu registry de contenedores. El registry actúa como el sistema de almacenamiento que organiza correctamente la manifest list y las imágenes individuales de cada arquitectura.
Para ejecutar físicamente la build para un procesador que no tienes, Buildx se apoya en un emulador llamado QEMU. Docker Desktop lo configura automáticamente. Cuando tu máquina ARM llega a un paso que requiere una instrucción AMD64, el emulador la traduce sobre la marcha. Esto requiere cero cambios en tu Dockerfile. Si necesitas tiempos de build más rápidos, también puedes usar herramientas de compilación cruzada directamente dentro de una multi-stage build, lo que se salta la emulación, pero requiere configurar flags de compilador específicos en tu código.
El verdadero poder de un manifest multiplataforma es que aísla completamente al consumidor de los detalles del hardware subyacente. Un desarrollador en un Mac y un cluster de producción con Intel hacen pull del mismo tag de imagen exacto, y el registry le sirve a cada uno el binario correcto automáticamente sin ninguna configuración extra.
Gracias por pasar unos minutos conmigo. Hasta la próxima, cuídate.
13
El Docker MCP Toolkit
4m 12s
Conecta de forma segura tus agentes de IA a herramientas locales. Este episodio presenta el Docker Model Context Protocol (MCP) Toolkit, explicando cómo gestionar servidores MCP en contenedores utilizando catálogos y perfiles.
Hola, soy Alex de DEV STORIES DOT EU. Masterclass de Docker, episodio 13 de 18. Darle a un agente de IA acceso directo a tu base de datos o sistema de archivos local es increíblemente potente. Pero instalar scripts de integración no fiables directamente en tu máquina host para conseguirlo es un desastre de seguridad a punto de ocurrir. El Docker MCP Toolkit soluciona esto moviendo esas integraciones a contenedores aislados.
El Model Context Protocol, o MCP, es un estándar abierto que permite a los clientes de IA, como la app de escritorio de Claude o el editor Cursor, conectarse a fuentes de datos y herramientas externas. Para darle a tu IA una nueva capacidad, ejecutas una pequeña aplicación llamada servidor MCP. Históricamente, esto significaba descargar scripts de Python o Node de terceros y ejecutarlos directamente en tu sistema operativo. Esto introduce una gran fricción operativa con conflictos de dependencias y, lo que es más importante, da a código no fiable acceso ilimitado a tu máquina. El Docker MCP Toolkit resuelve esto encapsulando estos servidores en contenedores Docker estándar.
La primera pieza de este sistema es el Catalog. Un Catalog es un registry de servidores MCP verificados y containerizados. En lugar de hacer pull de repositorios aleatorios de internet, haces pull de imágenes Docker estandarizadas. Estas imágenes vienen preempaquetadas para ejecutar las herramientas necesarias sin requerir runtimes de lenguajes locales en tu máquina host.
Una vez que tienes acceso a estos servidores, necesitas una forma de organizarlos. Esto se hace usando Profiles. Un Profile es una agrupación de configuración que define exactamente qué herramientas se necesitan para un proyecto específico. Por ejemplo, podrías crear un Profile llamado web-dev. Dentro de esta configuración, especificas que este Profile requiere el servidor de GitHub para leer repositorios de código y el servidor de Playwright para la automatización del navegador. Configuras tus API keys y variables de entorno para ambas herramientas una sola vez dentro de la configuración del Profile.
Ahora tienes herramientas aisladas y un Profile definido. ¿Cómo se conecta la IA a ellas? Aquí es donde la cosa se pone interesante. La conexión la gestiona el MCP Gateway. El Gateway actúa como un router central ejecutándose en tu host. No configuras tu cliente de IA para lanzar contenedores individuales. En su lugar, apuntas Claude o Cursor al MCP Gateway y solicitas el Profile web-dev.
Cuando el cliente se conecta, el Gateway lee el Profile, levanta automáticamente los contenedores de GitHub y Playwright solicitados en segundo plano, y establece la conexión. El Gateway gestiona la comunicación entre el cliente de IA y los contenedores usando el protocolo estándar. El cliente de IA cree que está hablando con herramientas locales, pero toda la ejecución ocurre de forma segura dentro de Docker. Solo configuras las herramientas una vez en el Profile, y puedes compartir esa misma configuración con cualquier cantidad de aplicaciones de IA diferentes. Si una de esas herramientas se comporta mal o se ve comprometida, queda atrapada en un contenedor, completamente ciega al resto de tu sistema.
El verdadero valor del MCP Toolkit es que separa la configuración de tus herramientas de IA de los clientes que las usan, proporcionando fuertes garantías de aislamiento sin sacrificar la inteligencia de tus workflows. Gracias por escuchar. Cuidaos.
14
Auto-Discovery dinámico de MCP
4m 47s
Explora Dynamic MCP, una función experimental que permite a los clientes de IA buscar en el Docker MCP Catalog e instalar dinámicamente nuevos servidores de herramientas durante una conversación sin configuración manual.
Hola, soy Alex de DEV STORIES DOT EU. Docker Masterclass, episodio 14 de 18. Estás a mitad de una conversación con un coding agent de IA y le pides que haga una query a una base de datos. Normalmente, si te olvidaste de configurar la tool de la base de datos de antemano, el agente falla y te pide que intervengas. El problema es que la configuración manual de la tool rompe el flujo de trabajo. Pero, ¿qué pasaría si el agente se diera cuenta de que le falta una capability, buscara en un catálogo e instalara el servidor necesario totalmente on the fly? Eso es exactamente lo que consigue Dynamic MCP Auto-Discovery.
Normalmente, darle tools a un Large Language Model significa definirlas estáticamente en un archivo de configuración antes de empezar la sesión. Si tu agente puede que necesite leer un repositorio de GitHub, publicar un mensaje en Slack y hacer una query a una base de datos, tienes que cargar todos esos servidores de Model Context Protocol por adelantado. Este enfoque satura la context window con tools que a lo mejor nunca se usan, y requiere que predigas las necesidades del agente a la perfección. Dynamic MCP cambia este paradigma. Permite al agente descubrir y conectar tools justo cuando la tarea lo requiere, sin ninguna intervención humana.
Cuando habilitas la feature dinámica, el Docker MCP Gateway expone un conjunto de tools de gestión directamente al agente de IA. Básicamente, el gateway le da al agente la capacidad de gestionar su propia toolchain. Las dos tools críticas que proporciona el gateway para este proceso son mcp-find y mcp-add. El agente interactúa con ellas exactamente igual que como interactúa con cualquier function call estándar.
Podemos ver cómo fluye esta lógica usando un escenario concreto. Supongamos que le pides a tu agente que analice unas métricas de usuario almacenadas en una base de datos SQL. El agente evalúa la request, comprueba su toolkit actual y se da cuenta de que no tiene cargada ninguna tool para hacer queries a la base de datos. En lugar de lanzar un error, el agente invoca la tool mcp-find, pasándole un string de búsqueda relevante como postgres.
El gateway intercepta esta llamada y hace una query al catálogo de Docker MCP configurado buscando servidores disponibles que coincidan con ese string. Le devuelve al agente los metadatos y las descripciones de los servidores que coinciden. El agente lee la descripción, confirma que el servidor de Postgres resolverá el problema y pasa al siguiente paso.
A continuación, el agente invoca la tool mcp-add, pasándole el identificador del servidor de Postgres que acaba de encontrar. Aquí es donde la cosa se pone interesante. El gateway captura la request de mcp-add, hace un pull de la imagen necesaria, levanta el servidor MCP en un contenedor de Docker y vincula dinámicamente las nuevas tools a la conexión activa. De repente, el agente tiene acceso a las tools de la base de datos, se conecta a tu base de datos, ejecuta la query que le pediste originalmente y devuelve el resultado. Todo el proceso ocurre en background, manteniendo tu conversación completamente ininterrumpida.
Hay una tercera tool en esta suite de gestión para la ejecución de código experimental, pero maneja un conjunto de problemas completamente diferente, así que hoy mantendremos nuestro foco estrictamente en el descubrimiento.
Aquí está el insight clave sobre este proceso. Cuando el agente usa mcp-add para cargar un nuevo servidor, esa adición está estrictamente limitada al scope de la sesión actual. El gateway no reescribe tus archivos de configuración globales, y las tools recién añadidas no persisten entre reinicios. Cuando cierras la sesión, el binding temporal de la tool se destruye. Esto asegura que tu entorno base se mantenga limpio y seguro, mientras le sigue dando al agente la máxima flexibilidad para resolver problemas complejos de múltiples pasos de forma dinámica.
Al exponer la búsqueda en el catálogo y la instalación como function calls estándar, Dynamic MCP elimina la carga de la configuración inicial y permite que el agente construya su propio entorno on demand.
Eso es todo por este episodio. Gracias por escuchar, ¡y sigue programando!
15
Docker Sandboxes para IA
4m 29s
Comprende la arquitectura de Docker Sandboxes. Aprende por qué los agentes de programación de IA autónomos requieren microVMs aisladas con demonios de Docker dedicados en lugar de namespaces de contenedores estándar.
Hola, soy Alex de DEV STORIES DOT EU. Docker Masterclass, episodio 15 de 18. Un agente de código de IA autónomo es exactamente el tipo de proceso que no quieres ejecutar con acceso root en tu portátil. Le pides que arregle un bug y, de repente, está descargando paquetes arbitrarios, modificando archivos del sistema o intentando reconstruir tu infraestructura local. Necesitas un lugar donde el agente pueda actuar como administrador sin serlo realmente. Esto es exactamente lo que los Docker Sandboxes para IA están diseñados para resolver.
Tradicionalmente, Docker aísla los procesos usando namespaces y control groups de Linux. Esos contenedores comparten el kernel del sistema operativo host. Para un servicio web predecible, ese modelo funciona perfectamente. Pero un agente de IA es inherentemente impredecible. Genera código sin verificar, lo ejecuta y, a menudo, necesita instalar nuevos paquetes del sistema sobre la marcha para probar sus propias soluciones. Compartir el kernel del host con un agente impredecible es un riesgo de seguridad demasiado grande. Para solucionar esto, los Docker Sandboxes abandonan los namespaces de contenedores estándar en favor de microVMs aisladas.
Cuando levantas un sandbox para un agente, este arranca una máquina virtual dedicada y ligera. El agente obtiene su propio kernel independiente. No puede ver los procesos de tu host. Por defecto, no puede acceder al network stack de tu host. Y lo más importante, elimina por completo el riesgo de las vulnerabilidades tradicionales de container escape. El agente está estrictamente confinado a un entorno virtualizado por hardware.
Esto importa muchísimo cuando consideras lo que realmente hacen los agentes de IA. Imagina que tu agente tiene la tarea de escribir una aplicación web compleja, crear un Dockerfile para ella y probar la build. Para lograr esto, el agente necesita ejecutar comandos de Docker. Si simplemente mapearas el Docker socket de tu sistema host en un contenedor estándar, el agente podría, en teoría, lanzar contenedores privilegiados directamente en tu máquina host. Los Docker Sandboxes evitan esto ejecutando un daemon de Docker completamente aislado dentro de la propia microVM. El agente puede hacer build de imágenes, hacer pull de dependencias externas y ejecutar contenedores anidados todo el día. Como se está comunicando con el daemon aislado dentro de la microVM, el entorno de Docker de tu sistema host permanece completamente ajeno y limpio. Cuando la tarea termina y el sandbox se destruye, el daemon interno y todas sus imágenes descargadas desaparecen inmediatamente.
Aquí es donde se pone interesante. Si la microVM está completamente aislada, ¿cómo sacas realmente el código terminado? La arquitectura resuelve esto usando workspace mounting. Este es un mecanismo seguro de passthrough del filesystem. Al inicializar el sandbox, defines un directorio específico en tu host para que actúe como el workspace. Este único directorio se monta de forma segura en la microVM. A medida que el agente escribe código, ejecuta tests o genera assets, los guarda en este directorio del workspace. El passthrough sincroniza esos archivos específicos de vuelta al filesystem de tu host en tiempo real. El agente entrega el output solicitado sin tener acceso jamás al resto de tu disco duro. Puede romper cosas libremente dentro de la microVM, pero tus archivos locales permanecen intactos.
La idea fundamental es que el aislamiento en este contexto ya no se trata solo de proteger el host de software externo malicioso. Se trata de habilitar de forma segura las operaciones de sistema impredecibles y altamente privilegiadas que un agente autónomo debe realizar para ser realmente útil.
Si disfrutas de estos episodios y quieres apoyar el programa, puedes buscar DevStoriesEU en Patreon.
Eso es todo por este episodio. ¡Hasta la próxima!
16
Construyendo equipos de agentes de IA
3m 49s
Deja de depender de un único modelo de IA para tareas complejas. Este episodio presenta el framework Docker Agent, mostrando cómo componer equipos especializados de agentes definidos en YAML.
Hola, soy Alex de DEV STORIES DOT EU. Docker Masterclass, episodio 16 de 18. Le pasas un error masivo de la aplicación a un único modelo de IA. Intenta mantener toda la arquitectura, los logs y la sintaxis objetivo en su cabeza a la vez. A mitad de camino, se confunde y alucina un fix para un archivo que no tiene nada que ver. Un modelo genérico intentando hacer de todo provoca un context overload. Para resolver problemas complejos de forma fiable, necesitas construir equipos de agentes de IA.
El framework Docker Agent te permite definir equipos especializados de agentes de IA usando un simple archivo de configuración YAML. En lugar de escribir un system prompt monolítico, divides el workflow en roles discretos. Estructuras esto como una jerarquía. Hay un root agent que orquesta el workflow, y múltiples subagentes que ejecutan tareas específicas. Esto aísla el contexto. Cada subagente solo recibe la información que necesita para su trabajo específico.
Imagina un workflow de debugging. Necesitas un equipo con dos roles distintos. Primero, un investigador de bugs que analiza los stack traces. Segundo, un fixer que realmente reescribe el código roto. Defines toda la composición de este equipo en un archivo llamado docker dash agent dot yml.
Empiezas configurando el root agent en la parte superior del archivo. Le das un nombre, seleccionas un modelo de lenguaje subyacente y le pasas las system instructions. El root agent actúa como el manager. Su responsabilidad principal no es resolver el problema directamente, sino delegar el trabajo. Le das instrucciones al root agent para que coordine entre el investigador y el fixer basándose en los inputs que recibe.
A continuación, defines los subagentes dentro del mismo archivo YAML. Declaras el agente investigador de bugs. Le asignas un modelo que destaca en razonamiento y leyendo logs. Le das instrucciones estrictas para que solo lea stack traces, identifique la función que falla y devuelva un output con una breve explicación de por qué falló. Luego, declaras el agente code fixer. Puedes asignarle un modelo optimizado específicamente para la generación de código. Sus instrucciones le dicen estrictamente que coja una función que falla y devuelva como output una versión corregida. Nada de análisis de logs, solo código de entrada y código de salida.
Cuando ejecutas este equipo, el usuario solo interactúa con el root agent. Le pasas al root agent un dump masivo de logs de la aplicación. El root agent evalúa la request y lee las descripciones de sus subagentes disponibles. Determina que el investigador de bugs es el agente adecuado para el primer paso. El root agent le pasa el dump de logs al investigador. El investigador procesa el ruido, encuentra un null pointer exception en una función específica, y devuelve solo ese detalle específico.
El root agent coge esa información aislada y se la pasa al agente code fixer. El code fixer escribe el patch y se lo devuelve al root manager, que luego te devuelve a ti el resultado final y limpio.
Aquí está la clave. El code fixer nunca ve el stack trace masivo. Solo ve la función exacta que necesita arreglar. Proteges la context window del modelo de código filtrando el ruido de antemano. Al asignar instrucciones específicas y limitadas a subagentes individuales en el archivo YAML, evitas que los modelos se desvíen de la tarea. El root agent gestiona la secuencia, y los subagentes gestionan la ejecución.
Estructurar los agentes jerárquicamente te obliga a tratar la IA como una arquitectura de microservices, asignando límites estrictos a lo que a cualquier modelo individual se le permite gestionar.
Eso es todo por este episodio. Gracias por escuchar, ¡y sigue programando!
17
Toolsets y workflows de agentes
3m 55s
Haz que tus agentes de IA sean realmente útiles dándoles las restricciones adecuadas. Aprende a configurar toolsets del sistema de archivos y a aplicar workflows de desarrollo estructurados en Docker Agent.
Hola, soy Alex de DEV STORIES DOT EU. Docker Masterclass, episodio 17 de 18. Un agente de IA con el modelo de lenguaje más avanzado es prácticamente inútil para el desarrollo si no puede leer tu source code o ejecutar tu test suite. Sin capacidades externas, solo adivina la sintaxis. Los toolsets y workflows del agente solucionan esto, acortando la brecha entre un simple generador de texto y un ingeniero de software real.
Por defecto, un agente de Docker se ejecuta en un contenedor aislado. No tiene ni idea de qué archivos existen en el directorio de tu proyecto. Para cambiar esto, configuras el array de toolsets en el archivo YAML de tu agente. Los toolsets son capacidades preempaquetadas que dan al agente acceso directo a su entorno host. Para un agente de desarrollo, normalmente inyectas dos toolsets principales: acceso al filesystem y acceso a la shell.
El toolset del filesystem permite al agente leer tu árbol de directorios, abrir source files y escribir código de vuelta en el disco. El toolset de la shell permite al agente ejecutar comandos de terminal. Sin el array de toolsets, tu agente está atrapado en una caja. Con él, tu agente tiene manos y ojos.
Sin embargo, darle manos y ojos a un agente es una receta para el caos si carece de disciplina. Un agente sin estructura podría modificar un archivo, asumir que ha funcionado y reportar éxito sin comprobar nunca si hay errores de sintaxis. Controlas este comportamiento usando el bloque de instrucciones en el archivo YAML. Este bloque no es un lugar para sugerencias vagas. Es donde defines un workflow operativo estricto.
La forma más fiable de estructurar estas instrucciones es dividiendo las tareas del agente en cuatro fases obligatorias: Analyze, Examine, Modify y Validate. Las escribes directamente en el bloque de instrucciones, diciéndole al agente que debe completar una fase antes de pasar a la siguiente.
Primero es Analyze. El agente lee el prompt del usuario para entender la feature o el bug fix solicitado. Lo siguiente es Examine. Aquí, le indicas al agente que use su toolset de filesystem para buscar en tu codebase, encontrar los archivos relevantes y leer su contenido para entender la lógica actual. El tercero es Modify. El agente escribe el código actualizado en el disco.
Esta es la parte que importa. La cuarta fase es Validate. Aquí es donde obligas al agente a demostrar su trabajo usando el toolset de la shell. Piensa en un agente desarrollador experto en Go. En la sección Validate de tus instrucciones, exiges explícitamente que el agente debe ejecutar el comando go test punto barra punto punto punto, seguido de golangci guion lint run.
Como el agente tiene acceso a la shell, ejecuta exactamente esos comandos. Si el compilador de Go lanza un error de sintaxis, o si un test falla, el toolset devuelve esa salida de la terminal directamente al agente. Como tus instrucciones dicen que la tarea no está completa hasta que pase la validación, el agente se ve obligado a leer el error, volver a la fase de Modify, arreglar el código y ejecutar los tests de nuevo. Repetirá este ciclo hasta que el linter esté contento y los tests pasen.
Dar acceso al filesystem y a la shell hace que tu agente sea capaz de escribir software. Pero estructurar sus instrucciones para exigir la ejecución explícita de tests hace que tu agente sea fiable. Vinculas sus herramientas a un loop de validación estricto para que nunca tengas que revisar código roto. Eso es todo por este episodio. Gracias por escuchar, ¡y sigue desarrollando!
18
Modelos de IA en Compose
3m 34s
Trata tus LLMs locales como cualquier otra dependencia de la aplicación. Aprende a declarar, configurar y vincular modelos de IA directamente dentro de tu archivo YAML de Docker Compose.
Hola, soy Alex de DEV STORIES DOT EU. Docker Masterclass, episodio 18 de 18. Tu aplicación depende de un Large Language Model local. Seguramente levantas un motor de inferencia externo, configuras la red manualmente e inyectas las URL de los endpoints a mano. Funciona, pero rompe la reproducibilidad aislada de tu entorno. Tu modelo de IA es solo otra dependencia, y debería estar en tu fichero de configuración justo al lado de tu base de datos. Eso es exactamente lo que consigue el elemento models de primer nivel en Docker Compose.
A partir de la versión 2.38 de Compose, los modelos son un concepto nativo. Antes, correr un modelo local implicaba escribir definiciones de services complejas para un motor de inferencia, exponer puertos a mano y configurar bridges de red para que el container de tu aplicación pudiera comunicarse con él. El nuevo bloque models elimina esa fricción al tratar el modelo de IA como una pieza de infraestructura independiente.
Añades un bloque models en el primer nivel de tu fichero, con la misma indentación que services y volumes. Dentro, le das un nombre a tu modelo. Vamos a usar ai/smollm2 para una aplicación de chat sencilla. Debajo de este nombre, declaras el identificador real del modelo al que vas a hacer pull. Aquí es donde también defines las restricciones de hardware y los parámetros del motor. Puedes configurar el tamaño del contexto para limitar el uso de memoria. Si el motor subyacente requiere parámetros de lanzamiento específicos, los defines usando flags de runtime. La configuración del modelo queda aislada y clara.
A continuación, vinculas tu aplicación al modelo. Dentro de tu bloque services, localizas el service de tu app de chat y añades un array de models. Usando la sintaxis corta, simplemente listas ai/smollm2. No necesitas configurar dependencias a mano ni montar aliases de red personalizados.
Aquí está la clave. Cuando usas esta sintaxis corta de binding, Compose asume la orquestación. Provisiona el motor de inferencia correcto en segundo plano para servir tu modelo especificado. Y lo más importante, autogenera variables de entorno estándar y las inyecta directamente en el container de tu aplicación de chat. Tu código arranca con variables como OPENAI_BASE_URL ya pobladas, apuntando perfectamente al endpoint interno del modelo.
Ejecutas un único comando docker compose up. Compose hace pull del modelo smollm2, configura el motor, arranca tu service de chat y establece la conexión. Nada de API keys manuales, nada de adivinar direcciones IP internas. Todo se enruta correctamente de serie.
Te animo a explorar la documentación oficial y a intentar escribir uno de estos ficheros tú mismo. Como esto cierra nuestra serie de masterclass, no dudes en visitar devstories dot eu para sugerir temas para lo próximo que cubramos. Al elevar los modelos de IA a elementos nativos en tu configuración, tu infraestructura se vuelve completamente declarativa, asegurando que la versión exacta del modelo que espera tu código sea siempre la que arranque. Eso es todo por hoy. Gracias por escuchar, ve a construir algo genial.
Tap to start playing
Browsers block autoplay
Share this episode
Episode
—
Copy this episode in another language:
Este sitio web no utiliza cookies. Nuestro proveedor de alojamiento puede registrar tu dirección IP con fines analíticos. Saber más.