Levantar la plataforma con un comando ya es una victoria. Pero si para ver el efecto de cambiar una línea de server.js hay que reconstruir la imagen y recrear el contenedor, nadie usará Docker para programar: acabarán ejecutando Node en el host y volveremos a los quince pasos.
Esta lección convierte el compose.override.yaml de desarrollo en un entorno de trabajo diario: editas en tu editor, el cambio se ve al instante, depuras paso a paso, ejecutas pruebas aisladas y reseteas el entorno con un comando. Es la última lección del módulo 4.
Contenido
- El objetivo y el override de desarrollo
- Bind mount del código y el problema de
node_modules - Recarga automática del proceso
develop.watch: el mecanismo nativo de Composedocker compose watchen marcha- Depuración paso a paso con VS Code
- Acceso a la base de datos
- Pruebas en contenedores efímeros
- Datos de desarrollo y reseteo del entorno
- Rendimiento de los bind mounts en macOS y Windows
- Los atajos del equipo:
dev.shyMakefile
- El objetivo y el override de desarrollo
El ciclo que buscamos es: guardar el fichero en el editor → el proceso del contenedor se reinicia solo → recargar el navegador. Sin build, sin up, sin esperas. Todo lo que hace falta vive en compose.override.yaml, que Compose carga automáticamente en local (lección 04-06) y que en el servidor nunca se usa.
# compose.override.yaml — entorno de desarrollo de Aurora Libros
services:
aurora-api:
build:
context: ./api
target: desarrollo # etapa del Dockerfile con las devDependencies
command: ["node", "--watch", "--inspect=0.0.0.0:9229", "src/server.js"]
environment:
NODE_ENV: development
LOG_NIVEL: debug
ports:
- "3000:3000"
- "9229:9229" # inspector de Node
volumes:
- ./api/src:/app/src # SOLO el código, no todo el proyecto
- /app/node_modules # volumen anónimo que protege las dependencias
restart: "no"
aurora-db:
ports:
- "127.0.0.1:5432:5432"
networks: [trasera, frontal] # necesaria para poder publicar el puerto
- Bind mount del código y el problema de
node_modules
node_modulesEl bind mount hace que el directorio del host sustituya al del contenedor. Y ahí está la trampa clásica: si montas todo el proyecto (./api:/app), el node_modules del host tapa el que instaló la imagen durante el docker build.
Las consecuencias son de las que arruinan una tarde:
- Si no tienes
node_modulesen el host, el contenedor se queda sin dependencias:Error: Cannot find module 'express'. - Si lo tienes, son las que instaló tu sistema operativo y tu versión de Node. Cualquier paquete con binarios nativos compilados para macOS no funciona dentro de una imagen Alpine.
| Opción | Cómo | Ventajas | Inconvenientes |
|---|---|---|---|
Montar solo src |
- ./api/src:/app/src |
Sencilla y explícita; node_modules intacto |
Cambiar package.json exige reconstruir |
| Volumen anónimo encima | - ./api:/app + - /app/node_modules |
Se puede montar todo el proyecto | El volumen se queda obsoleto al cambiar dependencias |
node_modules fuera del árbol |
ENV NODE_PATH=/deps/node_modules en el Dockerfile |
Imposible que colisionen | Requiere tocar el Dockerfile |
| Instalar en el host | npm install local |
Nada que configurar | Rompe la premisa de Docker: binarios del host |
La segunda opción merece una explicación, porque el mecanismo no es obvio: el montaje más específico gana. Docker monta ./api sobre /app y, encima, un volumen anónimo sobre /app/node_modules, que en su primera creación se inicializa con el contenido que la imagen ya tenía ahí. El resultado es código del host y dependencias de la imagen.
Para Aurora Libros usamos la primera, que es la más predecible. Y la regla que evita el 90 % de los problemas: cuando cambies package.json, reconstruye.
docker compose up -d --build aurora-api
docker compose down -v && docker compose up -d --build # si usas volumen anónimo
- Recarga automática del proceso
Montar el código no basta: el proceso node sigue ejecutando lo que cargó al arrancar. Node 22 trae la solución de serie:
--watch vigila los ficheros importados y reinicia el proceso al detectar un cambio. Ya no hace falta nodemon, aunque sigue siendo válido si necesitas sus opciones avanzadas (--watch-path, retardos, ejecución de comandos previos).
aurora-api-1 | Aurora API escuchando en el puerto 3000
aurora-api-1 | Restarting 'src/server.js'
aurora-api-1 | Aurora API escuchando en el puerto 3000Un aviso sobre sistemas de ficheros: la propagación de eventos inotify a través de un bind mount no siempre funciona en macOS y Windows. Si --watch no reacciona, usa --watch-preserve-output con sondeo o el mecanismo del apartado siguiente, que no depende de inotify dentro del contenedor.
develop.watch: el mecanismo nativo de Compose
develop.watch: el mecanismo nativo de ComposeCompose incorpora su propio sistema de sincronización. La diferencia clave: vigila desde el host, no desde dentro del contenedor, así que funciona igual en Linux, macOS y Windows.
develop:
watch:
- action: sync # copiar ficheros al contenedor
path: ./api/src
target: /app/src
ignore:
- "**/*.test.js"
- action: sync+restart # copiar y reiniciar el contenedor
path: ./api/config
target: /app/config
- action: rebuild # reconstruir la imagen entera
path: ./api/package.json| Acción | Qué hace | Cuándo usarla |
|---|---|---|
sync |
Copia los ficheros cambiados al contenedor | Código fuente, con recarga en caliente en el proceso |
sync+restart |
Copia y reinicia el contenedor | Configuración que se lee al arrancar |
rebuild |
Reconstruye la imagen y recrea el contenedor | package.json, Dockerfile, dependencias |
restart |
Solo reinicia, sin copiar | Cambios que ya están dentro por otra vía |
El bloque completo para Aurora Libros:
# compose.override.yaml
services:
aurora-api:
build: { context: ./api, target: desarrollo }
command: ["node", "--watch", "--inspect=0.0.0.0:9229", "src/server.js"]
ports: ["3000:3000", "9229:9229"]
develop:
watch:
- action: sync
path: ./api/src
target: /app/src
- action: rebuild
path: ./api/package.json
- action: rebuild
path: ./api/Dockerfile
aurora-web:
develop:
watch:
- action: sync+restart
path: ./web/nginx.conf
target: /etc/nginx/conf.d/default.conf
- action: sync
path: ./web/index.html
target: /usr/share/nginx/html/index.htmlFíjate en el reparto: la página HTML solo necesita copiarse (sync), pero nginx.conf se lee al arrancar, así que exige sync+restart. Y package.json no puede resolverse copiando: hay que reconstruir.
docker compose watch en marcha
docker compose watch en marchaWatch enabled
⦿ Syncing "aurora-api" 1 file to /app/src
aurora-api-1 | Restarting 'src/server.js'
⦿ Rebuilding service "aurora-api" after changes were detected in package.json
✔ Container aurora-libros-aurora-api-1 Recreated
⦿ Syncing "aurora-web" 1 file to /usr/share/nginx/htmlEl comando deja el terminal ocupado mostrando la sincronización en vivo. Con --no-up no levanta la pila, solo vigila lo ya levantado; y si prefieres verlo junto a los logs, docker compose up --watch combina ambas cosas.
Una ventaja poco comentada de watch frente al bind mount: puedes usar el mismo Dockerfile y la misma imagen que en producción, porque el código no se monta, se copia. Eso elimina toda una clase de diferencias entre "funciona en mi máquina" y el servidor.
- Depuración paso a paso con VS Code
Con --inspect=0.0.0.0:9229, Node abre su inspector. El 0.0.0.0 no es opcional: por defecto escucha solo en 127.0.0.1 del contenedor, inalcanzable desde el host.
.vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Adjuntar a Aurora API (Docker)",
"type": "node",
"request": "attach",
"address": "localhost",
"port": 9229,
"localRoot": "${workspaceFolder}/api/src",
"remoteRoot": "/app/src",
"restart": true,
"skipFiles": ["<node_internals>/**"]
}
]
}Las dos claves que casi todo el mundo configura mal:
| Clave | Valor | Por qué importa |
|---|---|---|
localRoot |
Ruta del código en tu máquina | Traduce las rutas de los puntos de interrupción |
remoteRoot |
Ruta del código en el contenedor | Sin la correspondencia, los breakpoints salen "no verificados" |
restart |
true |
Reengancha el depurador tras cada reinicio de --watch |
Pon un punto de interrupción en el manejador de /libros, lanza la configuración y ejecuta curl -s http://localhost:8080/api/libros. La ejecución se detiene y puedes inspeccionar variables, la pila y la consola, con el proceso corriendo dentro del contenedor, con sus límites de memoria y su red.
Aviso de seguridad: el puerto 9229 da ejecución arbitraria de código. Publícalo solo en local ("127.0.0.1:9229:9229") y nunca en un servidor.
- Acceso a la base de datos
Dos vías, y conviene tener las dos:
# Desde el host, con tu cliente favorito (requiere el puerto publicado del override)
psql -h 127.0.0.1 -p 5432 -U aurora -d aurora_libros
# Sin instalar nada: el psql que ya vive en el contenedor
docker compose exec aurora-db psql -U aurora -d aurora_libros
docker compose exec aurora-db psql -U aurora -d aurora_libros -c "\dt"
docker compose exec aurora-cache redis-cli KEYS 'libros:*' List of relations
Schema | Name | Type | Owner
--------+--------+-------+--------
public | libros | table | aurora
1) "libros:todos"Recuerda de la lección 04-04 que aurora-db está en la red trasera, que es internal: para publicar su puerto, el override la añade también a frontal. Y para copias rápidas, -T es obligatorio:
- Pruebas en contenedores efímeros
La forma directa, ya vista en la lección 04-03:
Pero las pruebas de integración necesitan una base de datos de verdad y desechable. Un servicio dedicado con su PostgreSQL en tmpfs —es decir, en RAM— resuelve las dos cosas: aislamiento total y una velocidad que el disco no da.
# compose.pruebas.yaml
name: aurora-pruebas
services:
db-pruebas:
image: postgres:16-alpine
environment:
POSTGRES_USER: aurora
POSTGRES_PASSWORD: pruebas
POSTGRES_DB: aurora_pruebas
tmpfs:
- /var/lib/postgresql/data # TODO en RAM: se evapora al parar
command: ["postgres", "-c", "fsync=off", "-c", "full_page_writes=off"]
healthcheck:
test: ["CMD-SHELL", "pg_isready -U aurora -d aurora_pruebas"]
interval: 2s
retries: 15
pruebas:
build: { context: ./api, target: desarrollo }
command: ["npm", "run", "test:integracion"]
environment:
DB_HOST: db-pruebas
DB_USER: aurora
DB_PASSWORD: pruebas
DB_NAME: aurora_pruebas
NODE_ENV: test
depends_on:
db-pruebas: { condition: service_healthy }pruebas-1 | ✔ GET /libros devuelve el catálogo completo (48ms)
pruebas-1 | ✔ GET /libros/:id devuelve un título (11ms)
pruebas-1 | ✔ POST /libros rechaza un ISBN duplicado (23ms)
pruebas-1 | ℹ pass 12 fail 0
pruebas-1 exited with code 0Tres piezas encajadas: --abort-on-container-exit para todo en cuanto terminan las pruebas, --exit-code-from pruebas hace que el comando devuelva el código de las pruebas (imprescindible en CI), y fsync=off es aceptable solo porque estos datos son desechables; en producción sería una barbaridad.
- Datos de desarrollo y reseteo del entorno
El servicio semillas del perfil datos (lección 04-06) carga un catálogo ficticio ampliado sobre los nueve títulos de init.sql:
Y el reseteo completo, la maniobra que más veces harás cuando algo se enrede:
docker compose down -v && docker compose up -d --wait && \
docker compose --profile datos run --rm semillasSesenta segundos y tienes un entorno idéntico al del resto del equipo. Aquí -v es correcto y deseable: son datos de desarrollo generados a partir de ficheros versionados. Es el único contexto en el que ese comando no debe darte miedo.
- Rendimiento de los bind mounts en macOS y Windows
En Linux, un bind mount es una operación del kernel: coste cero. En macOS y Windows, Docker corre dentro de una máquina virtual ligera y cada lectura cruza una frontera entre sistemas de ficheros. Con node_modules —decenas de miles de ficheros pequeños— la diferencia se nota mucho.
| Plataforma | Situación | Mitigación |
|---|---|---|
| Linux | Nativo, sin penalización | Ninguna necesaria |
| macOS | Traducción host ↔ VM | VirtioFS activado en Docker Desktop (por defecto desde 2023) |
| Windows + WSL 2 | Rápido si el código está dentro de WSL | Guardar el repositorio en ~/proyectos, nunca en /mnt/c/... |
| Windows sin WSL 2 | Muy lento | Migrar a WSL 2 |
| Cualquiera | node_modules compartido |
No montarlo: volumen anónimo o sync de watch |
El error de rendimiento más frecuente en Windows es tener el repositorio en C:\Users\... y accederlo desde WSL por /mnt/c/: cada require() cruza dos capas de traducción. Mover el proyecto al sistema de ficheros de Linux multiplica la velocidad por diez.
Las opciones :cached y :delegated que verás en documentación antigua eran ajustes de consistencia para el antiguo osxfs. Hoy se aceptan y se ignoran: VirtioFS las ha dejado obsoletas. Y la mejor mitigación sigue siendo estructural: docker compose watch copia en lugar de montar, así que evita el problema de raíz.
- Los atajos del equipo:
dev.sh y Makefile
dev.sh y MakefileNadie recuerda docker compose -f compose.yaml -f compose.pruebas.yaml up --abort-on-container-exit --exit-code-from pruebas. Ponlo en un fichero y documenta el entorno de paso:
# Makefile — atajos de Aurora Libros
.PHONY: arriba abajo logs sh psql pruebas semillas reset watch
arriba: ## Levanta la plataforma y espera a que esté sana
docker compose up -d --build --wait
abajo: ## Para la plataforma (conserva los datos)
docker compose --profile herramientas down
logs: ## Sigue los logs de todos los servicios
docker compose logs -f --tail 100
watch: ## Desarrollo con sincronización automática
docker compose watch
sh: ## Shell dentro de la API
docker compose exec aurora-api sh
psql: ## Consola de PostgreSQL
docker compose exec aurora-db psql -U aurora -d aurora_libros
pruebas: ## Pruebas de integración con base de datos efímera
docker compose -f compose.pruebas.yaml up \
--abort-on-container-exit --exit-code-from pruebas
semillas: ## Carga el catálogo de demostración
docker compose --profile datos run --rm semillas
reset: ## BORRA los datos y reconstruye el entorno desde cero
docker compose down -v
docker compose up -d --build --wait
docker compose --profile datos run --rm semillasDos detalles no negociables: en un Makefile la indentación es con tabulador, y el objetivo destructivo se llama reset —no limpiar, no down— para que nadie lo teclee por inercia. Si prefieres un dev.sh, la idea es la misma: los comandos del equipo, en Git, junto al código.
Errores Comunes y Consejos
Montar todo el proyecto sobre /app sin proteger node_modules. El node_modules del host tapa el de la imagen y aparecen módulos que no existen o binarios de otra plataforma.
Cambiar package.json y esperar que baste con guardar. Instalar dependencias exige reconstruir: up -d --build o una regla rebuild en develop.watch.
Usar --inspect sin 0.0.0.0. El inspector escucha solo dentro del contenedor y VS Code no puede conectar.
Dejar el 9229 publicado en todas las interfaces. Es ejecución remota de código. 127.0.0.1:9229:9229 y solo en local.
Poner las comodidades de desarrollo en el compose.yaml base. Un build o un puerto de depuración se acaban colando en el servidor.
Trabajar desde /mnt/c/ en WSL 2. El rendimiento cae en picado. El repositorio va en el sistema de ficheros de Linux.
Consejo: si el reinicio automático no se dispara, el orden de diagnóstico es: ¿está el fichero realmente dentro de la ruta montada o vigilada? (docker compose exec aurora-api ls -la src/), ¿el proceso arranca con --watch? (docker compose exec aurora-api ps aux), y si estás en macOS o Windows, cambia a docker compose watch, que no depende de inotify dentro del contenedor.
Ejercicios
Ejercicio 1. Monta ./api/src en aurora-api, arranca con node --watch y demuestra el ciclo completo: cambia el mensaje de /salud en tu editor y comprueba con curl que la respuesta cambia sin reconstruir ni recrear el contenedor. Verifica también que el contenedor es el mismo de antes.
Ejercicio 2. Configura develop.watch con las tres acciones (sync para src, sync+restart para un fichero de configuración y rebuild para package.json), lanza docker compose watch y provoca las tres. Explica qué observas en cada caso y por qué cada fichero necesita una acción distinta.
Ejercicio 3. Monta el entorno de pruebas con PostgreSQL en tmpfs, ejecútalo dos veces seguidas y demuestra que (a) la segunda ejecución parte de una base de datos completamente limpia y (b) el comando devuelve el código de salida de las pruebas, no el del contenedor de la base de datos.
Soluciones
Solución 1.
docker compose up -d --build
docker inspect --format '{{.Id}}' aurora-libros-aurora-api-1 | cut -c1-12
curl -s http://localhost:3000/salud | jq -r '.mensaje'Edita api/src/server.js cambiando el mensaje a "Aurora API operativa y recargada" y guarda:
sleep 2
curl -s http://localhost:3000/salud | jq -r '.mensaje'
docker inspect --format '{{.Id}}' aurora-libros-aurora-api-1 | cut -c1-12
docker compose logs aurora-api --tail 2Aurora API operativa y recargada
c4f1a9e0b73d
aurora-api-1 | Restarting 'src/server.js'
aurora-api-1 | Aurora API escuchando en el puerto 3000El identificador del contenedor es idéntico: no se ha recreado nada. Lo único que ha ocurrido es que el proceso node dentro del contenedor se ha reiniciado al detectar el cambio en un fichero que, gracias al bind mount, es el mismo inodo que el de tu editor. Ese es el ciclo de desarrollo que buscábamos: dos segundos entre guardar y ver el efecto.
Solución 2.
| Cambio provocado | Salida observada | Por qué esa acción |
|---|---|---|
Editar src/server.js |
Syncing "aurora-api" 1 file y Restarting 'src/server.js' |
El fichero solo hay que copiarlo; la recarga la hace --watch |
Editar config/ajustes.json |
Syncing ... Restarting service |
La configuración se lee al arrancar: copiar no basta |
Editar package.json |
Rebuilding service "aurora-api" y Container ... Recreated |
Una dependencia nueva exige npm install, que solo ocurre en el build |
La progresión es de menor a mayor coste: sync tarda milisegundos, sync+restart unos segundos, rebuild decenas de segundos. Por eso conviene reservar rebuild para los ficheros que de verdad lo necesitan —package.json, package-lock.json, Dockerfile— y no apuntarlo a un directorio entero: una regla rebuild sobre ./api reconstruiría la imagen cada vez que tocas una línea de código.
Solución 3.
docker compose -f compose.pruebas.yaml up --abort-on-container-exit --exit-code-from pruebas
echo "código de la primera ejecución: $?"
docker compose -f compose.pruebas.yaml down
docker compose -f compose.pruebas.yaml up --abort-on-container-exit --exit-code-from pruebas
echo "código de la segunda ejecución: $?"
docker volume ls --filter name=aurora-pruebas --format "{{.Name}}" | wc -lpruebas-1 | ℹ pass 12 fail 0
código de la primera ejecución: 0
pruebas-1 | ℹ pass 12 fail 0
código de la segunda ejecución: 0
0(a) La segunda ejecución da exactamente los mismos resultados y no existe ningún volumen: el tmpfs vive en la memoria del host y desaparece al eliminar el contenedor, así que cada ejecución parte de una base de datos recién inicializada. Es la propiedad que hace que unas pruebas de integración sean fiables: si la ejecución número cincuenta puede fallar por residuos de la cuarenta y nueve, las pruebas no valen nada.
(b) --exit-code-from pruebas es lo que hace utilizable esto en CI. Sin esa opción, up devuelve el código del primer contenedor que termina, que puede ser el de la base de datos parándose ordenadamente con un 0 alegre mientras las pruebas fallaban. Compruébalo haciendo fallar una prueba a propósito: el código pasa a 1 y el pipeline se pone en rojo, que es justo lo que debe ocurrir.
Conclusión
Docker ha dejado de ser algo que se usa "al final, para empaquetar" y se ha convertido en el entorno donde programas. Sabes montar el código con un bind mount y esquivar el problema eterno de node_modules —el del host tapando el de la imagen— con las cuatro estrategias posibles y su regla de oro: si cambia package.json, hay que reconstruir. Tienes recarga automática con el --watch nativo de Node 22, y por encima el mecanismo propio de Compose, develop.watch, con sus acciones graduadas por coste: sync para el código, sync+restart para la configuración que se lee al arrancar y rebuild para las dependencias, vigilando desde el host y funcionando igual en las tres plataformas.
Depuras de verdad: --inspect=0.0.0.0:9229, el puerto publicado solo en 127.0.0.1, y un launch.json con la correspondencia localRoot/remoteRoot que hace que los puntos de interrupción se detengan en el código que corre dentro del contenedor. Entras en la base de datos desde el host o con docker compose exec, ejecutas pruebas de integración contra un PostgreSQL efímero en tmpfs con --abort-on-container-exit y --exit-code-from, cargas datos ficticios con un servicio de semillas y reseteas el entorno entero en un comando. Y conoces el porqué de la lentitud en macOS y Windows —la frontera entre el host y la máquina virtual— con sus mitigaciones reales: VirtioFS, el código dentro del sistema de ficheros de WSL 2 y, sobre todo, no montar node_modules. Todo ello resumido en un Makefile versionado que cualquiera del equipo puede leer.
Y con esto se cierra el módulo 4. Empezaste con cincuenta líneas de comandos imperativos encadenados, frágiles y sin versionar, y terminas con la plataforma entera declarada en un fichero de texto: cuatro servicios más una migración, dos redes segmentadas con la zona de datos aislada del exterior, un volumen persistente, sondas de salud que distinguen "arrancado" de "listo", dependencias que respetan ese matiz, límites de recursos, políticas de reinicio, variables parametrizadas con su .env.example versionado y sus secretos fuera del entorno, perfiles para las herramientas opcionales y overrides que hacen que el mismo proyecto sirva en tu portátil, en CI y en el servidor. Todo revisable en una pull request, todo reproducible. Los quince pasos de onboarding de la lección 01-07 son hoy exactamente dos: git clone y docker compose up -d --wait.
A partir de aquí, el curso cambia de registro. Hasta ahora has aprendido a usar Docker muy bien; en el módulo 5 vas a abrir la caja negra y entender por qué funciona como funciona: las redes por dentro, con sus drivers, tablas de rutas y reglas de iptables; el almacenamiento a fondo, con controladores y estrategias de copia de seguridad; la seguridad real, con usuarios sin privilegios, capacidades del kernel, seccomp y análisis de vulnerabilidades; la optimización de imágenes, donde esos 274 MB de PostgreSQL y esos 142 MB de la API se ponen a dieta con construcciones multietapa; BuildKit y Buildx, con caché remota y compilación multiarquitectura; el registro y la monitorización de una plataforma en marcha; y, al final, los namespaces, cgroups y capas del kernel de Linux que llevan sosteniendo todo lo que has hecho desde la primera lección. Cuando termines ese módulo, ya no serás alguien que sabe escribir un compose.yaml: serás alguien que sabe qué ocurre exactamente cuando lo ejecuta.
Docker: De Principiante a Avanzado
Módulo 1: Introducción a Docker
- ¿Qué es Docker?
- Instalando Docker
- Arquitectura de Docker
- Comandos Básicos de Docker
- Entendiendo las Imágenes de Docker
- Creando tu Primer Contenedor Docker
- El Proyecto del Curso: la Plataforma Aurora Libros
Módulo 2: Trabajando con Imágenes Docker
- Docker Hub y Repositorios
- Construyendo Imágenes Docker
- Conceptos Básicos de Dockerfile
- Instrucciones Avanzadas del Dockerfile
- Gestionando Imágenes Docker
- Etiquetado y Publicación de Imágenes
Módulo 3: Contenedores Docker
- Ejecutando Contenedores
- Ciclo de Vida del Contenedor
- Gestionando Contenedores
- Inspección y Depuración de Contenedores
- Redes en Docker
- Persistencia de Datos con Volúmenes
- Límites de Recursos y Políticas de Reinicio
Módulo 4: Docker Compose
- Introducción a Docker Compose
- Definiendo Servicios en Docker Compose
- Comandos de Docker Compose
- Aplicaciones Multi-Contenedor
- Variables de Entorno en Docker Compose
- Perfiles, Overrides y Múltiples Entornos
- Desarrollo Local con Docker Compose
Módulo 5: Conceptos Avanzados de Docker
- Profundización en Redes Docker
- Opciones de Almacenamiento Docker
- Mejores Prácticas de Seguridad en Docker
- Optimizando Imágenes Docker
- Builds Avanzadas con BuildKit y Buildx
- Registro y Monitoreo en Docker
- El Runtime por Dentro: Namespaces, Cgroups y Capas
Módulo 6: Docker en Producción
- Preparar una Imagen para Producción
- CI/CD con Docker
- Orquestando Contenedores con Docker Swarm
- Introducción a Kubernetes
- Desplegando Contenedores Docker en Kubernetes
- Escalado y Balanceo de Carga
- Estrategias de Despliegue y Rollback
