Docker Mastery 2026

Contenedores · De Principiante a Avanzado · Casos Reales · 14 Módulos

Capítulo 1 · Introducción a Docker y Arquitectura

Nivel 1 · Principiante

Docker es la plataforma de contenedores más utilizada en la industria. En 2026, prácticamente cada pipeline de CI/CD, cada startup y cada empresa Fortune 500 usa Docker o una tecnología derivada. Entender Docker profundamente te convierte en un profesional de alto valor.

¿Qué es Docker?
Docker es una plataforma open-source que permite empaquetar aplicaciones y sus dependencias en unidades aisladas llamadas contenedores. Estos contenedores pueden correr en cualquier máquina que tenga Docker instalado, garantizando que el entorno sea idéntico en desarrollo, staging y producción.

1.1 El Problema que Resuelve Docker

Antes de Docker, la frase más temida en IT era: "En mi máquina funciona". Las diferencias entre sistemas operativos, versiones de librerías, variables de entorno y configuraciones hacían que el despliegue fuera un proceso frágil y propenso a errores.

Portabilidad Total
Un contenedor Docker corre igual en tu laptop, en AWS, en GCP o en un servidor bare-metal.
Inicio Instantáneo
Los contenedores arrancan en milisegundos, no minutos como una VM tradicional.
Aislamiento
Cada contenedor tiene su propio filesystem, red y procesos. Cero conflictos entre apps.
Escalabilidad
Escala de 1 a 1000 instancias en segundos. Base de Kubernetes y microservicios.

1.2 Contenedores vs Máquinas Virtuales

La diferencia fundamental está en el nivel de abstracción:

AspectoMáquina Virtual (VM)Contenedor Docker
VirtualizaciónHardware completo (Hypervisor)Kernel del SO host
Tiempo de arranque1–5 minutosMilisegundos
Tamaño en discoGBs (incluye SO completo)MBs (solo capas delta)
Consumo de RAMAlto (SO + App)Mínimo (solo App)
Densidad por hostDecenasCientos o miles
AislamientoFuerte (kernel separado)Bueno (namespace + cgroups)
PortabilidadMediaExcelente
┌─────────────────────────────────────────────────────────┐ │ MÁQUINA VIRTUAL │ │ ┌────────┐ ┌────────┐ ┌────────┐ │ │ │ App A │ │ App B │ │ App C │ │ │ │ Bins/ │ │ Bins/ │ │ Bins/ │ │ │ │ Libs │ │ Libs │ │ Libs │ │ │ │ Guest │ │ Guest │ │ Guest │ │ │ │ OS │ │ OS │ │ OS │ │ │ └────────┘ └────────┘ └────────┘ │ │ Hypervisor (VMware / KVM) │ │ Infrastructure (Hardware) │ └─────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────┐ │ CONTENEDORES DOCKER │ │ ┌────────┐ ┌────────┐ ┌────────┐ │ │ │ App A │ │ App B │ │ App C │ │ │ │ Bins/ │ │ Bins/ │ │ Bins/ │ │ │ │ Libs │ │ Libs │ │ Libs │ │ │ └────────┘ └────────┘ └────────┘ │ │ Docker Engine (Daemon) │ │ Host OS + Kernel Linux │ │ Infrastructure (Hardware) │ └─────────────────────────────────────────────────────────┘

1.3 Arquitectura de Docker

Docker usa una arquitectura cliente-servidor. El cliente Docker se comunica con el Docker Daemon (dockerd) mediante una API REST sobre un socket Unix o TCP.

┌──────────────────────────────────────────────────────────────┐ │ ARQUITECTURA DOCKER │ │ │ │ ┌─────────────┐ REST API ┌──────────────────────┐ │ │ │Docker Client│ ◄──────────────► │ Docker Daemon │ │ │ │(docker CLI) │ │ (dockerd) │ │ │ └─────────────┘ │ │ │ │ │ ┌──────────────────┐ │ │ │ ┌─────────────┐ │ │ containerd │ │ │ │ │Docker Compose│ ◄───────────► │ │ (runtime mgr) │ │ │ │ └─────────────┘ │ └──────────────────┘ │ │ │ │ │ │ │ │ ┌─────────────┐ │ ┌───────▼──────────┐ │ │ │ │ Docker Hub │ ◄── pull/push ── │ │ runc (OCI) │ │ │ │ │ Registry │ │ │ (container run) │ │ │ │ └─────────────┘ │ └──────────────────┘ │ │ │ └──────────────────────┘ │ └──────────────────────────────────────────────────────────────┘

1.4 Conceptos Fundamentales

Imagen (Image)
Plantilla de solo lectura que contiene el sistema de archivos y configuración de una aplicación. Se construye en capas (layers) usando un Dockerfile. Equivale a una "clase" en programación orientada a objetos.
Contenedor (Container)
Instancia en ejecución de una imagen. Tiene su propio sistema de archivos, red y procesos. Equivale a un "objeto" instanciado de una clase. Es efímero por defecto.
Dockerfile
Archivo de texto con instrucciones para construir una imagen Docker. Define el SO base, dependencias, archivos de la aplicación y comandos de inicio.
Registry
Repositorio de imágenes Docker. Docker Hub es el registry público oficial. También existen registries privados como AWS ECR, Google Artifact Registry, Azure ACR y Harbor.

1.5 El Sistema de Capas (Union File System)

Las imágenes Docker usan un sistema de archivos en capas (OverlayFS). Cada instrucción en el Dockerfile crea una nueva capa inmutable. Cuando se crea un contenedor, se agrega una capa de escritura encima:

Capa de escritura del contenedor (RW) ← Solo esta capa cambia ───────────────────────────────────── Capa 4: COPY app/ /app/ (RO) ← Solo lectura Capa 3: RUN pip install -r req.txt (RO) Capa 2: COPY requirements.txt / (RO) Capa 1: FROM python:3.12-slim (RO) ← Base ───────────────────────────────────── OverlayFS (Kernel Linux)
Eficiencia de capas: Si dos imágenes comparten las mismas capas base (ej: python:3.12-slim), Docker las reutiliza en disco. Esto ahorra almacenamiento y acelera las descargas significativamente.

1.6 Historia y Evolución

AñoHito
2013Docker 0.1 lanzado por dotCloud (Solomon Hykes). Demostración en PyCon.
2014Docker 1.0. Soporte de Google, Red Hat, IBM, Microsoft.
2015Estándar OCI (Open Container Initiative). Docker Compose 1.0.
2016Docker Swarm nativo. Integración Docker + Windows Server.
2017Kubernetes supera a Swarm. Moby Project.
2019containerd se dona a CNCF. Docker Desktop para Mac/Win.
2021Docker Desktop pasa a ser de pago para empresas grandes.
2023Docker Compose V2 es el default. Docker Scout para seguridad.
2025Docker Desktop 5.x. Soporte nativo Apple Silicon. Docker Build Cloud GA.
2026Docker Engine 28.x. WebAssembly containers. Integración IA nativa.

1.7 Casos Reales de la Industria

PayPal
Migró 700+ servicios a contenedores Docker. Redujo el tiempo de deploy de 4 horas a 15 minutos.
Netflix
Usa contenedores para microservicios de streaming. Maneja picos de 36M+ streams/hora.
Spotify
Contenedores en GKE para sus microservicios. Más de 1200 microservicios en producción.
Cuestionario — Capítulo 1
1. ¿Cuál es la diferencia principal entre un contenedor Docker y una máquina virtual?
Los contenedores usan el kernel del sistema operativo host mediante namespaces y cgroups, lo que los hace mucho más ligeros que las VMs que necesitan un hypervisor y un OS guest completo.
2. ¿Qué es una imagen Docker?
Una imagen Docker es una plantilla inmutable (de solo lectura) compuesta por capas. A partir de una imagen se crean los contenedores (instancias en ejecución).
3. ¿Qué componente de Docker es responsable de gestionar el ciclo de vida de los contenedores?
El Docker Daemon (dockerd) es el proceso servidor que gestiona imágenes, contenedores, redes y volúmenes. El CLI envía comandos al daemon a través de la API REST.

Capítulo 2 · Instalación en Windows, Linux y macOS

Nivel 1 · Principiante

2.1 Instalación en Windows con WSL2

En 2026, la forma recomendada de usar Docker en Windows es con Docker Desktop + WSL2. El backend de WSL2 da rendimiento casi nativo y compatibilidad completa con el ecosistema Linux.

Prerequisito: Windows 10 versión 2004+ o Windows 11, con WSL2 instalado y habilitado. También necesitas virtualización habilitada en BIOS/UEFI.
1
Habilitar WSL2
Abre PowerShell como administrador y ejecuta:
PowerShell (Admin)
wsl --install
# Reinicia el sistema después de la instalación
wsl --set-default-version 2
wsl --status
2
Instalar Docker Desktop
Descarga Docker Desktop desde docker.com/products/docker-desktop. Ejecuta el instalador y asegúrate de seleccionar "Use WSL2 instead of Hyper-V".
3
Integrar con distribución WSL
En Docker Desktop → Settings → Resources → WSL Integration → Habilitar tu distribución (Ubuntu).
4
Verificar instalación
Desde la terminal WSL o PowerShell verifica que Docker responde correctamente.
Bash / PowerShell
docker --version
Docker version 28.1.0, build abc1234

docker info
Client: Docker Engine - Community
 Version: 28.1.0
 Server Version: 28.1.0
 ...

docker run hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.

2.2 Instalación en Linux (Ubuntu/Debian)

En Linux, instalamos directamente Docker Engine sin necesidad de Docker Desktop. Este es el método preferido para servidores de producción.

Bash — Ubuntu 22.04/24.04
# 1. Remover versiones antiguas
sudo apt remove docker docker-engine docker.io containerd runc

# 2. Instalar dependencias
sudo apt update && sudo apt install -y \
  ca-certificates curl gnupg lsb-release

# 3. Agregar GPG key oficial de Docker
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
  | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

# 4. Agregar repositorio
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" \
  | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# 5. Instalar Docker Engine
sudo apt update && sudo apt install -y \
  docker-ce docker-ce-cli containerd.io \
  docker-buildx-plugin docker-compose-plugin

# 6. Agregar usuario al grupo docker (no usar sudo)
sudo usermod -aG docker $USER
newgrp docker

# 7. Verificar
docker run hello-world

2.3 Instalación en macOS

En macOS, Docker Desktop es la solución estándar. En 2026 tiene soporte nativo para Apple Silicon (M1/M2/M3/M4) con rendimiento excepcional.

Bash — macOS (Homebrew)
# Opción 1: Homebrew (recomendado)
brew install --cask docker

# Opción 2: Alternativa sin Docker Desktop (más ligera)
brew install colima docker
colima start --cpu 4 --memory 8

# Verificar
docker version
docker context ls

2.4 Configuración Post-Instalación

JSON — /etc/docker/daemon.json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "default-address-pools": [
    {"base": "172.17.0.0/16", "size": 24}
  ],
  "live-restore": true,
  "metrics-addr": "0.0.0.0:9323",
  "storage-driver": "overlay2"
}
Buena práctica: Siempre configura límites de log en producción. Los logs de contenedores pueden crecer indefinidamente y llenar el disco del host si no se limitan.

2.5 Docker en Fedora/RHEL/Rocky Linux

Bash — Fedora/RHEL 9
# Agregar repositorio Docker
sudo dnf -y install dnf-plugins-core
sudo dnf config-manager \
  --add-repo https://download.docker.com/linux/fedora/docker-ce.repo

# Instalar Docker Engine
sudo dnf install -y docker-ce docker-ce-cli containerd.io \
  docker-buildx-plugin docker-compose-plugin

# Habilitar y arrancar el servicio
sudo systemctl enable --now docker

# Agregar usuario al grupo docker
sudo usermod -aG docker $USER && newgrp docker
Cuestionario — Capítulo 2
1. ¿Por qué es recomendable agregar el usuario al grupo "docker" en Linux?
El Docker socket (/var/run/docker.sock) pertenece al grupo docker. Al añadir el usuario a ese grupo puede comunicarse con el daemon sin escalar privilegios con sudo.
2. ¿Qué configuración en daemon.json evita que los logs llenen el disco?
Con log-opts max-size y max-file se limita el tamaño máximo de cada archivo de log y la cantidad de archivos rotados, evitando el llenado del disco.

Capítulo 3 · Comandos Básicos y Avanzados

Nivel 1-2 · Principiante-Intermedio

Dominar la CLI de Docker es fundamental. En 2026, Docker CLI usa el subcomando docker [objeto] [acción] como estructura estándar (ej: docker container run en lugar del antiguo docker run).

3.1 Comandos de Contenedores

Bash — Gestión de Contenedores
# ── CREAR Y EJECUTAR ─────────────────────────────
docker run nginx                  # Corre en primer plano
docker run -d nginx               # -d: modo detached (background)
docker run -it ubuntu bash        # -i: interactivo, -t: terminal
docker run --name mi-app -d nginx # Nombre personalizado
docker run -p 8080:80 -d nginx    # -p host:container port mapping
docker run -P -d nginx            # -P: mapear todos los puertos automáticamente
docker run --rm -it alpine sh     # --rm: eliminar al terminar

# ── LISTAR ───────────────────────────────────────
docker ps                          # Contenedores en ejecución
docker ps -a                       # Todos (incluyendo detenidos)
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"

# ── INSPECCIONAR ────────────────────────────────
docker inspect mi-app             # JSON completo del contenedor
docker inspect -f '{{.NetworkSettings.IPAddress}}' mi-app
docker logs mi-app                # Ver logs
docker logs -f mi-app             # -f: follow (en tiempo real)
docker logs --tail 50 mi-app      # Últimas 50 líneas
docker stats                       # Uso de CPU/RAM en tiempo real
docker top mi-app                 # Procesos dentro del contenedor

# ── INTERACTUAR ─────────────────────────────────
docker exec -it mi-app bash       # Shell dentro del contenedor
docker exec mi-app env            # Ver variables de entorno
docker exec mi-app cat /etc/os-release
docker cp mi-app:/app/config.json ./config.json  # Copiar archivos

# ── CICLO DE VIDA ───────────────────────────────
docker start mi-app
docker stop mi-app                # SIGTERM + espera 10s + SIGKILL
docker restart mi-app
docker kill mi-app                # SIGKILL inmediato
docker pause mi-app               # Congelar (SIGSTOP)
docker unpause mi-app

# ── LIMPIAR ─────────────────────────────────────
docker rm mi-app                  # Eliminar contenedor detenido
docker rm -f mi-app               # Forzar eliminación (en ejecución)
docker container prune            # Eliminar TODOS los detenidos
docker system prune -a           # Limpiar TODO lo no usado

3.2 Comandos de Imágenes

Bash — Gestión de Imágenes
docker images                     # Listar imágenes locales
docker images -a                  # Incluir capas intermedias
docker pull nginx:alpine          # Descargar imagen
docker pull nginx:1.27            # Versión específica
docker push usuario/mi-imagen:v1  # Subir a registry
docker tag nginx:alpine mi-nginx:prod  # Crear alias/tag
docker rmi nginx:alpine           # Eliminar imagen
docker image prune -a             # Eliminar imágenes sin uso
docker history nginx:alpine       # Ver capas de la imagen
docker image inspect nginx:alpine # Detalles completos
docker save nginx:alpine > nginx.tar     # Exportar a archivo
docker load < nginx.tar           # Importar desde archivo

3.3 Opciones Avanzadas de docker run

FlagDescripciónEjemplo
--memoryLímite de RAM--memory="512m"
--cpusLímite de CPU--cpus="1.5"
-eVariable de entorno-e DB_HOST=localhost
--env-fileArchivo de variables--env-file .env
-vMontar volumen/bind-v /host:/container
--networkRed a conectar--network mi-red
--restartPolítica de reinicio--restart=always
--health-cmdComando healthcheck--health-cmd="curl -f http://localhost"
--userUsuario de ejecución--user 1000:1000
--read-onlyFS de solo lectura--read-only

3.4 Comandos de Sistema y Diagnóstico

Bash — Sistema
docker info                       # Información completa del daemon
docker version                    # Versión cliente y servidor
docker system df                  # Uso de disco por tipo
docker system events              # Stream de eventos en tiempo real
docker system prune --volumes     # Limpieza completa incluyendo volúmenes

# Formatear salida con Go templates
docker ps --format "{{.Names}} → {{.Status}} ({{.Ports}})"

# Filtrar contenedores
docker ps --filter "status=exited"
docker ps --filter "name=web"
docker ps --filter "ancestor=nginx"
Error común: Usar docker system prune -a --volumes en producción sin confirmar. Este comando elimina TODAS las imágenes no usadas y TODOS los volúmenes sin uso, lo que puede borrar datos importantes.
Laboratorio 3.1 — Tu Primer Servidor Web

Objetivo: Ejecutar nginx, mapear puerto, personalizar la página y hacer limpieza.

Bash
# Paso 1: Ejecutar nginx
docker run -d --name web-lab -p 8080:80 nginx:alpine

# Paso 2: Verificar que está corriendo
curl http://localhost:8080

# Paso 3: Crear página personalizada
echo "

Hola Docker 2026!

" > index.html
docker cp index.html web-lab:/usr/share/nginx/html/index.html curl http://localhost:8080 # Paso 4: Ver logs docker logs web-lab # Paso 5: Entrar al contenedor docker exec -it web-lab sh / # cat /etc/os-release / # exit # Paso 6: Limpiar docker stop web-lab && docker rm web-lab
Cuestionario — Capítulo 3
1. ¿Qué combinación de flags ejecuta un contenedor en modo interactivo que se elimina al salir?
-i mantiene stdin abierto, -t asigna un pseudo-TTY para la terminal interactiva, y --rm elimina el contenedor automáticamente cuando termina.
2. ¿Cuál es la diferencia entre docker stop y docker kill?
docker stop envía SIGTERM dando al proceso tiempo para terminar limpiamente (10s por defecto), luego SIGKILL. docker kill envía SIGKILL inmediatamente sin tiempo de limpieza.

Capítulo 4 · Imágenes y Docker Hub

Nivel 1-2 · Principiante-Intermedio

Las imágenes son el corazón de Docker. Entender cómo se construyen, almacenan, versionan y comparten es crítico para cualquier profesional DevOps en 2026.

4.1 Anatomía de una Imagen Docker

Una imagen Docker es un stack de capas inmutables. Cada capa representa una instrucción del Dockerfile. Las capas se identifican por su hash SHA256 y se comparten entre imágenes que las tienen en común.

Bash — Inspeccionar capas
docker history python:3.12-slim
IMAGE          CREATED        CREATED BY                         SIZE
a1b2c3d4e5f6   2 days ago     CMD ["python3"]                    0B
<missing>      2 days ago     ENV PYTHON_VERSION=3.12.0          0B
<missing>      2 days ago     RUN apt-get update && apt-get...   52.3MB
<missing>      2 days ago     ENV LANG=C.UTF-8                   0B
<missing>      2 days ago     /bin/sh -c #(nop) ADD file:...    74.8MB

docker image inspect python:3.12-slim --format '{{.RootFS.Layers}}'
[sha256:a1b2... sha256:c3d4... sha256:e5f6...]

4.2 Docker Hub — El Registry Oficial

Docker Hub (hub.docker.com) es el registry de contenedores más grande del mundo con más de 15 millones de imágenes en 2026.

Imágenes Oficiales
Mantenidas por Docker y comunidades. Sin prefijo de usuario. Ej: nginx, python, postgres.
Imágenes Verificadas
Publicadas por empresas verificadas. Ej: bitnami/postgresql, elastic/elasticsearch.
Imágenes de Comunidad
Publicadas por cualquier usuario. Formato: usuario/imagen:tag. Verificar antes de usar.
Bash — Trabajar con Docker Hub
# Autenticarse en Docker Hub
docker login
docker login -u miusuario -p mitoken  # Con token (mejor práctica)

# Buscar imágenes
docker search nginx
docker search --filter is-official=true python

# Descargar con digest específico (reproducible)
docker pull nginx@sha256:abc123...

# Publicar imagen propia
docker tag mi-app:v1.0 miusuario/mi-app:v1.0
docker push miusuario/mi-app:v1.0
docker push miusuario/mi-app:latest

# Cerrar sesión
docker logout

4.3 Entendiendo los Tags

Los tags identifican versiones específicas de una imagen. La convención en 2026 sigue Semantic Versioning:

TagSignificadoRecomendación
latestÚltima versión (por defecto)❌ Evitar en producción
3.12Versión mayor.menor✅ Buena para dev
3.12.7Versión exacta✅ Ideal para producción
3.12-slimVersión reducida (Debian slim)✅ Recomendada
3.12-alpineBasada en Alpine Linux (~5MB)✅ Mínima, segura
sha256:abc...Digest inmutable✅ Máxima reproducibilidad

4.4 Registries Alternativos en 2026

RegistryProveedorUso típico
Docker HubDocker Inc.Open source, imágenes públicas
Amazon ECRAWSAplicaciones en AWS EKS/ECS
Google Artifact RegistryGCPApps en GKE, Cloud Run
Azure Container RegistryMicrosoftApps en AKS
GitHub Container RegistryGitHubCI/CD con GitHub Actions
HarborOpen Source (CNCF)Registry privado on-premise

4.5 Docker Scout — Seguridad de Imágenes (2026)

Docker Scout es la herramienta de análisis de vulnerabilidades integrada en Docker CLI desde 2023. En 2026 es esencial en todo pipeline de CI/CD.

Bash — Docker Scout
# Analizar vulnerabilidades de una imagen
docker scout cves nginx:alpine
✓ Image stored for indexing
✓ Indexed 82 packages
✗ Detected 2 vulnerabilities
  CVE-2024-xxxxx [MEDIUM] - libssl3

# Comparar dos versiones
docker scout compare nginx:1.25 nginx:1.27

# Recomendaciones de actualización
docker scout recommendations mi-app:latest

# Ver SBOM (Software Bill of Materials)
docker scout sbom nginx:alpine
Laboratorio 4.1 — Publicar tu Primera Imagen

Objetivo: Crear una imagen personalizada y publicarla en Docker Hub.

Bash
# 1. Crear Dockerfile mínimo
mkdir mi-primera-imagen && cd mi-primera-imagen
cat > Dockerfile << 'EOF'
FROM alpine:3.20
LABEL maintainer="tu@email.com"
RUN echo "Mi primera imagen Docker 2026" > /mensaje.txt
CMD ["cat", "/mensaje.txt"]
EOF

# 2. Construir imagen
docker build -t miusuario/primera-imagen:v1.0 .

# 3. Probar localmente
docker run --rm miusuario/primera-imagen:v1.0
Mi primera imagen Docker 2026

# 4. Login y push
docker login
docker push miusuario/primera-imagen:v1.0

# 5. Verificar con scout
docker scout cves miusuario/primera-imagen:v1.0
Cuestionario — Capítulo 4
1. ¿Por qué se recomienda usar tags específicos de versión (ej: python:3.12.7) en lugar de "latest" en producción?
"latest" apunta a la versión más reciente que puede cambiar. En producción necesitas builds reproducibles y "latest" puede cambiar comportamiento al actualizar. Usa tags específicos o digests SHA256.
2. ¿Qué ventaja tienen las imágenes Alpine sobre las Debian slim?
Alpine Linux usa musl libc y BusyBox, resultando en imágenes de ~5MB vs ~80MB de Debian slim. Menos superficie de ataque significa menos vulnerabilidades potenciales.

Capítulo 5 · Dockerfile — Todas las Instrucciones

Nivel 2 · Intermedio

El Dockerfile es el plano de construcción de tus imágenes. En 2026, dominar cada instrucción y sus optimizaciones es diferencial clave para cualquier profesional DevOps o Backend.

5.1 Estructura General

Dockerfile
# Comentario — ignorado por el builder
FROM        python:3.12-slim     # Imagen base
LABEL       maintainer="team@empresa.com"  # Metadatos
ARG         APP_VERSION="1.0"   # Variable de build
ENV         PYTHONDONTWRITEBYTECODE=1  # Variable de entorno
WORKDIR     /app                 # Directorio de trabajo
COPY        requirements.txt .   # Copiar archivo
RUN         pip install -r requirements.txt  # Ejecutar comando
COPY        . .                  # Copiar resto del código
EXPOSE      8000                 # Documentar puerto
HEALTHCHECK CMD curl -f http://localhost:8000/health || exit 1
USER        appuser              # Cambiar usuario
CMD         ["python", "main.py"]  # Comando por defecto

5.2 Todas las Instrucciones en Detalle

FROM

Define la imagen base. Siempre es la primera instrucción (excepto ARG). En builds multi-stage, puede aparecer varias veces.

Dockerfile
FROM ubuntu:24.04
FROM python:3.12-slim AS builder
FROM scratch                    # Imagen vacía, para binarios estáticos
FROM ubuntu:24.04 AS base
FROM base AS development
FROM base AS production

RUN

Ejecuta comandos durante la construcción. Cada RUN crea una nueva capa. La forma preferida es shell form o exec form:

Dockerfile
# ❌ Mal: múltiples RUN = múltiples capas
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get clean

# ✅ Bien: una sola capa, cache limpio
RUN apt-get update && apt-get install -y \
    curl wget git \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

# Exec form (sin shell, para evitar interpretación)
RUN ["/bin/sh", "-c", "echo hello"]

# RUN con heredoc (BuildKit, 2024+)
RUN <<EOF
  set -e
  pip install --upgrade pip
  pip install -r requirements.txt
EOF

COPY vs ADD

InstrucciónFunciónCuándo usar
COPYCopia archivos del contexto al contenedor✅ Siempre que sea posible
ADDCopia + extrae .tar + descarga URLs❌ Solo si necesitas extraer tarballs
Dockerfile
COPY src/ /app/src/
COPY --chown=appuser:appgroup . /app  # Con propietario
COPY --from=builder /app/dist /app    # Multi-stage
ADD  app.tar.gz /app/                 # Extrae automáticamente

ENV y ARG

Dockerfile
# ARG: solo disponible durante build
ARG NODE_VERSION="20"
ARG BUILD_DATE
FROM node:${NODE_VERSION}-alpine

# ENV: disponible en build Y en runtime
ENV NODE_ENV=production
ENV PORT=3000 \
    APP_HOME=/app

# Pasar ARG al build:
docker build --build-arg NODE_VERSION=22 .
⚠️ Seguridad: Nunca uses ARG o ENV para pasar secretos (passwords, tokens). Son visibles con docker history e docker inspect. Usa Docker Secrets o variables de entorno en runtime.

WORKDIR, USER, EXPOSE, VOLUME

Dockerfile
# WORKDIR: establece directorio de trabajo
WORKDIR /app  # Crea el dir si no existe

# USER: evita correr como root
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
USER appuser
# O con UID/GID directo (más portable)
USER 1001:1001

# EXPOSE: documentar puertos (no los abre realmente)
EXPOSE 80/tcp
EXPOSE 443/tcp
EXPOSE 9090/udp

# VOLUME: punto de montaje para datos persistentes
VOLUME ["/data", "/logs"]

CMD vs ENTRYPOINT

Esta es una de las diferencias más importantes a entender:

InstrucciónPropósito¿Se puede overrride?
CMDComando por defecto, fácilmente reemplazable✅ Sí (con argumento en docker run)
ENTRYPOINTPunto de entrada fijo del contenedor--entrypoint flag
ENTRYPOINT + CMDENTRYPOINT = ejecutable, CMD = args por defecto✅ CMD fácilmente
Dockerfile
# Solo CMD (reemplazable)
CMD ["nginx", "-g", "daemon off;"]

# Solo ENTRYPOINT (fijo)
ENTRYPOINT ["/docker-entrypoint.sh"]

# ENTRYPOINT + CMD combinados (patrón profesional)
ENTRYPOINT ["python", "manage.py"]
CMD ["runserver", "0.0.0.0:8000"]
# docker run django-app migrate  ← CMD se reemplaza

HEALTHCHECK

Dockerfile
HEALTHCHECK --interval=30s --timeout=10s --retries=3 --start-period=40s \
  CMD curl -f http://localhost:8000/health || exit 1

# Para apps sin curl (usar wget o nc)
HEALTHCHECK CMD wget -qO- http://localhost/ || exit 1

# Deshabilitar healthcheck heredado
HEALTHCHECK NONE

5.3 Multi-Stage Builds — El Patrón Profesional

Los builds multi-stage permiten construir con todas las herramientas necesarias y copiar solo los artefactos finales a una imagen mínima. Resultado: imágenes de producción hasta 10x más pequeñas.

Dockerfile — Multi-Stage (Go)
# ── STAGE 1: Build ────────────────────────────────────
FROM golang:1.23-alpine AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server ./cmd/server

# ── STAGE 2: Producción mínima ────────────────────────
FROM scratch AS production
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /app/server /server
EXPOSE 8080
USER 65534:65534  # nobody:nogroup
ENTRYPOINT ["/server"]

# Build: docker build --target production -t mi-go-app .
# Resultado: imagen de ~8MB vs ~350MB con Go completo
Dockerfile — Multi-Stage (Node.js React)
# ── STAGE 1: Dependencias ────────────────────────────
FROM node:22-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

# ── STAGE 2: Builder ─────────────────────────────────
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# ── STAGE 3: Producción Nginx ────────────────────────
FROM nginx:1.27-alpine AS production
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

5.4 .dockerignore — El .gitignore de Docker

.dockerignore
# Node
node_modules/
npm-debug.log

# Python
__pycache__/
*.pyc
.venv/
.env
*.egg-info/

# Control de versiones
.git/
.gitignore

# IDE
.vscode/
.idea/

# Tests y docs
tests/
docs/
README.md

# Build artifacts
dist/
build/
*.log

5.5 BuildKit — El Builder Moderno

BuildKit es el motor de build predeterminado desde Docker 23.0. Ofrece caché avanzada, builds paralelos y montajes de secretos.

Dockerfile + BuildKit
# syntax=docker/dockerfile:1.7
FROM python:3.12-slim

# Cache mount para pip (no reinstala si no cambia)
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install -r requirements.txt

# Secret mount (nunca queda en la imagen)
RUN --mount=type=secret,id=mysecret \
    cat /run/secrets/mysecret

# SSH mount para paquetes privados
RUN --mount=type=ssh \
    pip install git+ssh://git@github.com/miorg/paquete-privado.git

# Build con secreto:
# docker build --secret id=mysecret,src=./secreto.txt .
Laboratorio 5.1 — Imagen Python Optimizada

Objetivo: Construir una API FastAPI optimizada con multi-stage, usuario no-root y healthcheck.

Dockerfile
# syntax=docker/dockerfile:1.7
FROM python:3.12-slim AS builder
WORKDIR /build
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install --prefix=/install -r requirements.txt

FROM python:3.12-slim AS production
LABEL org.opencontainers.image.source="https://github.com/mi/app"
WORKDIR /app
COPY --from=builder /install /usr/local
COPY --chown=nobody:nogroup . .
RUN apt-get update && apt-get install -y --no-install-recommends curl \
    && rm -rf /var/lib/apt/lists/*
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=5s CMD curl -f http://localhost:8000/health || exit 1
USER nobody
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Cuestionario — Capítulo 5
1. ¿Cuál es la ventaja principal de los builds multi-stage?
Los builds multi-stage permiten usar herramientas de compilación en stages intermedios y copiar solo los binarios/artefactos finales a la imagen de producción, resultando en imágenes mínimas y seguras.
2. ¿Por qué se recomienda usar USER en el Dockerfile?
Correr como root dentro de un contenedor es un riesgo de seguridad. Si hay una vulnerabilidad y el proceso escapa del contenedor, tendría privilegios de root en el host. USER aplica el principio de mínimo privilegio.

Capítulo 6 · Redes en Docker

Nivel 2 · Intermedio

Las redes de Docker controlan cómo los contenedores se comunican entre sí y con el exterior. Entender los drivers de red es esencial para arquitecturas de microservicios.

6.1 Drivers de Red

DriverDescripciónCaso de uso
bridgeRed virtual privada entre contenedores en el mismo hostDefault para contenedores standalone
hostComparte la red del host directamenteAlto rendimiento, sin aislamiento de red
noneSin red. Solo loopbackMáximo aislamiento
overlayRed distribuida entre múltiples hostsDocker Swarm, múltiples servidores
macvlanAsigna MAC address real al contenedorIntegración con redes físicas legacy
ipvlanSimilar a macvlan, comparte MAC del hostEntornos con restricciones de MAC
BRIDGE NETWORK (user-defined) ┌─────────────────────────────────────────────┐ │ Docker Host │ │ │ │ ┌──────────┐ ┌──────────┐ │ │ │ web │ │ db │ │ │ │172.20.0.2│◄──►│172.20.0.3│ │ │ └────┬─────┘ └─────┬────┘ │ │ │ │ │ │ ─────┴────────────────┴───── │ │ br-mi-red (172.20.0.0/16) │ │ ───────────────────────────────── │ │ │ │ │ eth0 (Host) │ └─────────────────────────────────────────────┘ │ Internet

6.2 Comandos de Redes

Bash
# Listar redes
docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
abc123         bridge    bridge    local
def456         host      host      local
ghi789         none      null      local

# Crear red personalizada
docker network create mi-red
docker network create --driver bridge --subnet 172.20.0.0/16 mi-red

# Conectar contenedor a red
docker run -d --name web --network mi-red nginx
docker run -d --name db --network mi-red postgres:16

# Desde 'web', el contenedor 'db' es alcanzable por nombre
docker exec web ping db
PING db (172.20.0.3): 56 data bytes
64 bytes from 172.20.0.3: icmp_seq=0 ttl=64 time=0.082 ms

# Inspeccionar red
docker network inspect mi-red

# Conectar/desconectar contenedor existente
docker network connect mi-red contenedor-existente
docker network disconnect mi-red contenedor-existente

# Eliminar red
docker network rm mi-red
docker network prune  # Eliminar redes sin uso

6.3 DNS Interno de Docker

Las redes bridge definidas por el usuario incluyen un servidor DNS embebido. Los contenedores en la misma red pueden comunicarse por nombre de contenedor o alias de red:

Bash
# Alias de red (múltiples nombres para un contenedor)
docker run -d --name postgres-primary \
  --network mi-red \
  --network-alias db \
  --network-alias database \
  postgres:16

# Ahora desde otro contenedor en mi-red puedes usar:
# psql -h db -U user mydb
# psql -h database -U user mydb
# psql -h postgres-primary -U user mydb

6.4 Port Mapping — Exponer Contenedores

Bash
# -p hostPort:containerPort
docker run -p 8080:80 nginx           # localhost:8080 → container:80
docker run -p 127.0.0.1:8080:80 nginx # Solo desde localhost
docker run -p 80 nginx                 # Puerto host aleatorio
docker run -P nginx                    # Todos los EXPOSE en puertos aleatorios

# Ver puertos mapeados
docker port mi-contenedor
80/tcp -> 0.0.0.0:8080

6.5 Buenas Prácticas de Red

✅ Buenas Prácticas:
• Usa siempre redes definidas por el usuario (no la bridge default)
• Segmenta servicios en redes separadas (frontend-net, backend-net)
• No expongas puertos de BD directamente al host en producción
• Usa --network host solo cuando sea estrictamente necesario
• Documenta la topología de red de tu aplicación
Laboratorio 6.1 — Red Multi-Servicio

Objetivo: Crear dos redes separadas (frontend y backend) y conectar servicios apropiadamente.

Bash
# Crear redes segmentadas
docker network create frontend-net --subnet 172.21.0.0/24
docker network create backend-net  --subnet 172.22.0.0/24

# Levantar BD (solo backend-net)
docker run -d --name db \
  --network backend-net \
  -e POSTGRES_PASSWORD=secret \
  postgres:16-alpine

# Levantar API (conectada a ambas redes)
docker run -d --name api \
  --network backend-net \
  -e DB_HOST=db \
  mi-api:latest
docker network connect frontend-net api

# Levantar frontend (solo frontend-net)
docker run -d --name frontend \
  --network frontend-net \
  -p 3000:3000 \
  mi-frontend:latest

# Verificar: db no puede ser alcanzado desde frontend directamente
docker exec frontend ping db
ping: db: Name or service not known  ← ✅ Correcto, aislado
Cuestionario — Capítulo 6
1. ¿Qué ventaja tienen las redes bridge definidas por el usuario sobre la bridge default?
Las redes bridge definidas por el usuario incluyen un DNS embebido que resuelve nombres de contenedores automáticamente. La bridge default solo permite comunicación por IP.

Capítulo 7 · Volúmenes y Persistencia de Datos

Nivel 2 · Intermedio

Los contenedores son efímeros por diseño: al eliminarse, pierden sus datos. Docker ofrece tres mecanismos de persistencia. Elegir el correcto es fundamental para aplicaciones con estado.

7.1 Los Tres Tipos de Montaje

TipoOrigenUso idealProd?
VolumeDocker gestiona en /var/lib/docker/volumes/Datos de producción, BDs✅ Sí
Bind MountDirectorio del host especificado por tiDesarrollo local (hot reload)❌ Con cuidado
tmpfsRAM del host (no persiste)Datos sensibles temporales⚠️ Casos específicos
TIPOS DE MONTAJE DOCKER ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ VOLUME │ │ BIND MOUNT │ │ TMPFS │ │ │ │ │ │ │ │ /var/lib/ │ │ /home/user/ │ │ RAM │ │ docker/ │ │ myproject/ │ │ (memoria) │ │ volumes/ │ │ │ │ │ │ myvolume/ │ │ ← tu path │ │ temporal │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │ │ ───────┴───────────────────┴───────────────────┴─────── CONTENEDOR /data /app /tmp

7.2 Docker Volumes (Recomendado)

Bash
# Crear volumen
docker volume create mi-datos
docker volume create --driver local mi-datos

# Listar e inspeccionar
docker volume ls
docker volume inspect mi-datos
[{
  "Name": "mi-datos",
  "Driver": "local",
  "Mountpoint": "/var/lib/docker/volumes/mi-datos/_data",
  "Labels": {}
}]

# Usar volumen con contenedor
docker run -d \
  -v mi-datos:/var/lib/postgresql/data \
  --name postgres \
  postgres:16-alpine

# Sintaxis --mount (más explícita, recomendada)
docker run -d \
  --mount type=volume,source=mi-datos,target=/var/lib/postgresql/data \
  --name postgres \
  postgres:16-alpine

# Backup de volumen
docker run --rm \
  -v mi-datos:/data \
  -v $(pwd):/backup \
  alpine tar czf /backup/backup.tar.gz -C /data .

# Restaurar backup
docker run --rm \
  -v mi-datos:/data \
  -v $(pwd):/backup \
  alpine tar xzf /backup/backup.tar.gz -C /data

# Eliminar volúmenes
docker volume rm mi-datos
docker volume prune  # Eliminar todos sin uso

7.3 Bind Mounts — Desarrollo Local

Bash
# Montar código fuente (hot reload)
docker run -d \
  -v $(pwd)/src:/app/src \
  -p 3000:3000 \
  node:22-alpine node server.js

# Sintaxis --mount
docker run -d \
  --mount type=bind,source=$(pwd)/src,target=/app/src,readonly \
  mi-app

# En Windows (con WSL2)
docker run -v C:/Users/mi/proyecto:/app mi-app
# O desde WSL:
docker run -v /mnt/c/Users/mi/proyecto:/app mi-app

7.4 tmpfs — Datos en RAM

Bash
# tmpfs: datos solo en RAM, no en disco ni en imagen
docker run -d \
  --tmpfs /tmp:rw,size=100m,mode=1777 \
  mi-app

# Ideal para: sesiones, archivos temporales, datos sensibles
docker run -d \
  --mount type=tmpfs,target=/run/secrets,tmpfs-size=10m \
  mi-app

7.5 Errores Comunes con Volúmenes

❌ Error frecuente: Permisos en volúmenes
Cuando el contenedor corre con USER no-root pero el volumen pertenece a root, ocurren errores de permiso. Solución: usa --user $(id -u):$(id -g) o ajusta permisos en el Dockerfile con RUN chown -R appuser:appgroup /data.
⚠️ Volúmenes anónimos: Cuando usas -v /container/path sin nombre, Docker crea un volumen anónimo con hash aleatorio. Son difíciles de gestionar. Siempre nombra tus volúmenes en producción.
Laboratorio 7.1 — PostgreSQL con Persistencia

Objetivo: Demostrar que los datos sobreviven al ciclo de vida del contenedor.

Bash
# Crear volumen nombrado
docker volume create pg-data

# Lanzar PostgreSQL con volumen
docker run -d \
  --name pg \
  -v pg-data:/var/lib/postgresql/data \
  -e POSTGRES_PASSWORD=secret123 \
  -e POSTGRES_DB=testdb \
  postgres:16-alpine

# Insertar datos
docker exec -it pg psql -U postgres -d testdb -c \
  "CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT); \
   INSERT INTO users(name) VALUES('Alice'),('Bob');"

# Eliminar el contenedor (¡datos deberían sobrevivir!)
docker rm -f pg

# Recrear contenedor con el mismo volumen
docker run -d \
  --name pg-nuevo \
  -v pg-data:/var/lib/postgresql/data \
  -e POSTGRES_PASSWORD=secret123 \
  postgres:16-alpine

# Verificar que los datos persisten
docker exec -it pg-nuevo psql -U postgres -d testdb -c "SELECT * FROM users;"
 id | name
────┼───────
  1 | Alice
  2 | Bob   ← ✅ Datos preservados!
Cuestionario — Capítulo 7
1. ¿Cuál es la diferencia principal entre un Volume y un Bind Mount?
Los Volumes son completamente gestionados por Docker (creación, backup, migración). Los Bind Mounts dependen de la estructura de directorios del host y son ideales para desarrollo pero menos portables.

Capítulo 8 · Docker Compose

Nivel 2 · Intermedio

Docker Compose permite definir y gestionar aplicaciones multi-contenedor usando un archivo YAML declarativo. En 2026, Compose V2 está integrado directamente en Docker CLI como docker compose (sin guión).

8.1 Estructura de compose.yaml

YAML — compose.yaml (Full Stack App)
# compose.yaml — Docker Compose V2 (2026)
name: mi-aplicacion

services:

  db:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: appdb
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: ${DB_PASSWORD}   # desde .env
    volumes:
      - pg_data:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
    networks:
      - backend
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7.2-alpine
    restart: unless-stopped
    command: redis-server --requirepass ${REDIS_PASSWORD}
    networks:
      - backend

  api:
    build:
      context: ./api
      dockerfile: Dockerfile
      target: production
      args:
        - APP_VERSION=${APP_VERSION:-1.0}
    restart: unless-stopped
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://appuser:${DB_PASSWORD}@db:5432/appdb
      - REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    volumes:
      - media_files:/app/media
    networks:
      - frontend
      - backend
    deploy:
      resources:
        limits:
          cpus: "1.0"
          memory: 512M

  nginx:
    image: nginx:1.27-alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
      - ./certs:/etc/nginx/certs:ro
      - media_files:/app/media:ro
    depends_on:
      - api
    networks:
      - frontend

volumes:
  pg_data:
    driver: local
  media_files:
    driver: local

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true  # Sin acceso a internet

8.2 Comandos Esenciales de Compose

Bash
# Levantar todos los servicios
docker compose up
docker compose up -d            # En background
docker compose up --build         # Reconstruir imágenes
docker compose up api nginx       # Solo servicios específicos

# Estado
docker compose ps
docker compose logs
docker compose logs -f api        # Follow logs de un servicio
docker compose top               # Procesos en cada servicio

# Escalar servicio
docker compose up -d --scale api=3

# Ejecutar comando en servicio
docker compose exec api bash
docker compose exec db psql -U appuser -d appdb
docker compose run --rm api python manage.py migrate

# Parar y eliminar
docker compose stop              # Parar (preserva contenedores)
docker compose down              # Parar y eliminar contenedores + redes
docker compose down -v           # También eliminar volúmenes
docker compose down --rmi all    # También eliminar imágenes

# Validar configuración
docker compose config            # Ver configuración final
docker compose config --quiet    # Solo validar sin output

8.3 Compose para Múltiples Entornos

El patrón profesional es usar un archivo base y archivos de override por entorno:

proyecto/ ├── compose.yaml ← Base (servicios comunes) ├── compose.dev.yaml ← Override para desarrollo ├── compose.prod.yaml ← Override para producción ├── .env ← Variables de entorno └── .env.example ← Template sin secretos
YAML — compose.dev.yaml
# Override para desarrollo: bind mounts + hot reload
services:
  api:
    build:
      target: development
    volumes:
      - ./api:/app              # Bind mount para hot reload
    environment:
      - DEBUG=true
      - LOG_LEVEL=debug
    ports:
      - "5678:5678"            # Puerto debugger

  db:
    ports:
      - "5432:5432"            # Exponer DB localmente para DBeaver
Bash — Comandos por entorno
# Desarrollo
docker compose -f compose.yaml -f compose.dev.yaml up

# Producción
docker compose -f compose.yaml -f compose.prod.yaml up -d

# Con variable de entorno COMPOSE_FILE
export COMPOSE_FILE=compose.yaml:compose.dev.yaml
docker compose up

8.4 Funciones Avanzadas de Compose 2026

YAML — Funciones avanzadas
# include (incluir otros compose files)
include:
  - path: ./database/compose.yaml
  - path: ./monitoring/compose.yaml

# x-common-env: YAML anchors para reutilizar config
x-common-env: &common-env
  LOG_LEVEL: info
  TZ: America/Mexico_City

services:
  api:
    environment:
      <<: *common-env
      DATABASE_URL: postgres://...

  worker:
    environment:
      <<: *common-env
      QUEUE: celery

# secrets nativos de Compose
secrets:
  db_password:
    file: ./secrets/db_password.txt

services:
  api:
    secrets:
      - db_password  # disponible en /run/secrets/db_password
Laboratorio 8.1 — Stack WordPress + MySQL + Redis

Objetivo: Levantar un stack completo con compose en minutos.

YAML — compose.yaml
name: wordpress-stack
services:
  db:
    image: mysql:8.3
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wpuser
      MYSQL_PASSWORD: wppass
    volumes:
      - mysql_data:/var/lib/mysql
    networks:
      - backend

  wordpress:
    image: wordpress:6.5-apache
    restart: always
    ports:
      - "8080:80"
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_NAME: wordpress
      WORDPRESS_DB_USER: wpuser
      WORDPRESS_DB_PASSWORD: wppass
    depends_on:
      - db
    volumes:
      - wp_content:/var/www/html/wp-content
    networks:
      - frontend
      - backend

volumes:
  mysql_data:
  wp_content:

networks:
  frontend:
  backend:
Bash
docker compose up -d
docker compose ps
NAME                   IMAGE              STATUS
wordpress-stack-db-1   mysql:8.3          Up (healthy)
wordpress-stack-wp-1   wordpress:6.5      Up
# Visita http://localhost:8080
Cuestionario — Capítulo 8
1. ¿Qué hace depends_on con condition service_healthy?
Con condition: service_healthy, Compose espera a que el HEALTHCHECK del servicio dependido retorne success antes de iniciar el servicio dependiente. Esto evita race conditions de conexión.

Capítulo 9 · Docker para Desarrollo

Nivel 2-3 · Intermedio-Avanzado

Docker transforma el flujo de desarrollo: elimina el "funciona en mi máquina", unifica entornos y facilita la colaboración en equipo. En 2026, es el estándar para onboarding de nuevos desarrolladores.

9.1 Docker para Python / FastAPI / Django

Dockerfile — Python FastAPI
# Desarrollo con hot-reload
FROM python:3.12-slim AS dev
WORKDIR /app
RUN pip install uvicorn[standard] fastapi python-multipart
ENV PYTHONPATH=/app
CMD ["uvicorn", "main:app", "--reload", "--host", "0.0.0.0"]

# compose.dev.yaml para hot reload
YAML — compose para FastAPI + PostgreSQL + Redis
services:
  api:
    build: .
    volumes:
      - .:/app          # Bind mount para hot reload
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://user:pass@db:5432/mydb
      - REDIS_URL=redis://redis:6379
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: mydb
    healthcheck:
      test: ["CMD", "pg_isready"]
      interval: 5s

  redis:
    image: redis:7-alpine

9.2 Docker para Node.js

Dockerfile — Node.js Multi-Stage
FROM node:22-alpine AS base
WORKDIR /app
COPY package*.json ./

FROM base AS development
RUN npm install
COPY . .
CMD ["npm", "run", "dev"]

FROM base AS production
RUN npm ci --only=production
COPY . .
USER node
CMD ["node", "server.js"]

9.3 Docker para Java / Spring Boot

Dockerfile — Spring Boot con Maven
# syntax=docker/dockerfile:1.7
FROM eclipse-temurin:21-jdk-alpine AS builder
WORKDIR /build
COPY pom.xml .
RUN --mount=type=cache,target=/root/.m2 \
    mvn dependency:go-offline -q
COPY src ./src
RUN --mount=type=cache,target=/root/.m2 \
    mvn package -DskipTests -q

FROM eclipse-temurin:21-jre-alpine AS production
WORKDIR /app
COPY --from=builder /build/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "--enable-preview", "--add-opens=java.base/java.lang=ALL-UNNAMED", "-jar", "app.jar"]

9.4 Docker para .NET

Dockerfile — .NET 9 API
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS builder
WORKDIR /src
COPY *.csproj .
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /publish --no-restore

FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS production
WORKDIR /app
COPY --from=builder /publish .
USER 1001
EXPOSE 8080
ENTRYPOINT ["dotnet", "MyApi.dll"]

9.5 Docker para Go

Dockerfile — Go con imagen scratch
FROM golang:1.23-alpine AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
    go build -ldflags="-s -w" -o /bin/server .

FROM gcr.io/distroless/static-debian12 AS production
COPY --from=builder /bin/server /server
EXPOSE 8080
USER nonroot:nonroot
ENTRYPOINT ["/server"]
# Imagen final: ~8MB!

9.6 Dev Containers con VS Code

En 2026, los Dev Containers son estándar para onboarding instantáneo. El equipo entero tiene el mismo entorno de desarrollo sin instalar nada localmente.

JSON — .devcontainer/devcontainer.json
{
  "name": "Mi App Dev",
  "dockerComposeFile": "../compose.dev.yaml",
  "service": "api",
  "workspaceFolder": "/app",
  "features": {
    "ghcr.io/devcontainers/features/git:1": {},
    "ghcr.io/devcontainers/features/github-cli:1": {}
  },
  "extensions": [
    "ms-python.python",
    "ms-python.vscode-pylance",
    "eamodio.gitlens"
  ],
  "postCreateCommand": "pip install -r requirements-dev.txt",
  "remoteUser": "vscode"
}
Cuestionario — Capítulo 9
1. ¿Qué ventaja ofrece usar bind mounts en desarrollo vs copiar el código a la imagen?
En desarrollo, el bind mount monta el directorio del host dentro del contenedor. Cuando modificas un archivo en tu editor, el contenedor lo ve inmediatamente. Combinado con --reload, el servidor se reinicia automáticamente.

Capítulo 10 · Docker para Data Engineering

Nivel 3 · Avanzado

Docker es el estándar en Data Engineering. Permite replicar stacks complejos (Spark, Airflow, Kafka) localmente en minutos, lo que era imposible hace 5 años.

10.1 Stack Completo de Data Engineering

┌─────────────────────────────────────────────────────────────┐ │ DATA ENGINEERING STACK CON DOCKER │ │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────────┐ │ │ │ Jupyter │ │ MLflow │ │ Airflow │ │ MinIO │ │ │ │ :8888 │ │ :5000 │ │ :8080 │ │ (S3) :9000 │ │ │ └────┬────┘ └────┬────┘ └────┬────┘ └──────┬──────┘ │ │ │ │ │ │ │ │ ─────┴────────────┴─────────────┴───────────────┴───── │ │ data-net (bridge) │ │ ─────┬────────────┬─────────────┬───────────────┬───── │ │ │ │ │ │ │ │ ┌────▼────┐ ┌────▼────┐ ┌────▼────┐ ┌───────▼─────┐ │ │ │Postgres │ │ Redis │ │ Kafka │ │ Spark │ │ │ │ :5432 │ │ :6379 │ │ :9092 │ │ :4040 │ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────────┘ │ └─────────────────────────────────────────────────────────────┘

10.2 Jupyter Notebook + PySpark

YAML — Jupyter + Spark
name: data-stack
services:
  jupyter:
    image: jupyter/pyspark-notebook:spark-3.5.0
    ports:
      - "8888:8888"
    volumes:
      - ./notebooks:/home/jovyan/work
      - ./data:/home/jovyan/data
    environment:
      - JUPYTER_ENABLE_LAB=yes
      - SPARK_HOME=/usr/local/spark
    networks:
      - data-net

  spark-master:
    image: bitnami/spark:3.5
    environment:
      - SPARK_MODE=master
    ports:
      - "8080:8080"
      - "7077:7077"
    networks:
      - data-net

  spark-worker:
    image: bitnami/spark:3.5
    environment:
      - SPARK_MODE=worker
      - SPARK_MASTER_URL=spark://spark-master:7077
      - SPARK_WORKER_MEMORY=2G
    depends_on:
      - spark-master
    networks:
      - data-net

networks:
  data-net:

10.3 Apache Airflow

YAML — Airflow con LocalExecutor
name: airflow-stack
x-airflow-common: &airflow-common
  image: apache/airflow:2.9.0
  environment:
    AIRFLOW__CORE__EXECUTOR: LocalExecutor
    AIRFLOW__DATABASE__SQL_ALCHEMY_CONN: postgresql+psycopg2://airflow:airflow@postgres/airflow
    AIRFLOW__CORE__FERNET_KEY: 'your-fernet-key'
    AIRFLOW__WEBSERVER__SECRET_KEY: 'your-secret'
  volumes:
    - ./dags:/opt/airflow/dags
    - ./logs:/opt/airflow/logs
  depends_on:
    postgres:
      condition: service_healthy

services:
  postgres:
    image: postgres:16
    environment:
      POSTGRES_USER: airflow
      POSTGRES_PASSWORD: airflow
      POSTGRES_DB: airflow
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "airflow"]
      interval: 5s

  airflow-webserver:
    <<: *airflow-common
    command: webserver
    ports:
      - "8080:8080"

  airflow-scheduler:
    <<: *airflow-common
    command: scheduler

10.4 Apache Kafka + Zookeeper

YAML — Kafka Stack 2026
services:
  # Kafka sin Zookeeper (KRaft mode - 2026 estándar)
  kafka:
    image: apache/kafka:3.7.0
    environment:
      KAFKA_NODE_ID: 1
      KAFKA_PROCESS_ROLES: broker,controller
      KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
      KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
      KAFKA_LOG_DIRS: /var/lib/kafka/data
      CLUSTER_ID: MkU3OEVBNTcwNTJENDM2Qk
    ports:
      - "9092:9092"
    volumes:
      - kafka_data:/var/lib/kafka/data

  kafka-ui:
    image: provectuslabs/kafka-ui:latest
    ports:
      - "9000:8080"
    environment:
      KAFKA_CLUSTERS_0_NAME: local
      KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:9092
    depends_on:
      - kafka

volumes:
  kafka_data:

10.5 MLflow + MinIO + PostgreSQL

YAML — MLflow Stack
services:
  minio:
    image: minio/minio:latest
    command: server /data --console-address :9001
    ports:
      - "9000:9000"    # API S3
      - "9001:9001"    # Web UI
    environment:
      MINIO_ROOT_USER: minioadmin
      MINIO_ROOT_PASSWORD: minioadmin
    volumes:
      - minio_data:/data

  mlflow:
    image: ghcr.io/mlflow/mlflow:v2.11.0
    command: mlflow server
      --backend-store-uri postgresql://mlflow:mlflow@postgres/mlflow
      --default-artifact-root s3://mlflow/
      --host 0.0.0.0
    ports:
      - "5000:5000"
    environment:
      MLFLOW_S3_ENDPOINT_URL: http://minio:9000
      AWS_ACCESS_KEY_ID: minioadmin
      AWS_SECRET_ACCESS_KEY: minioadmin
    depends_on:
      - postgres
      - minio

  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: mlflow
      POSTGRES_PASSWORD: mlflow
      POSTGRES_DB: mlflow

volumes:
  minio_data:

10.6 Stack Completo: PostgreSQL + MongoDB + Redis

YAML — Multi-DB Stack
services:
  postgres:
    image: postgres:16-alpine
    ports: ["5432:5432"]
    environment:
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: appdb
    volumes:
      - pg_data:/var/lib/postgresql/data

  mongodb:
    image: mongo:7.0
    ports: ["27017:27017"]
    environment:
      MONGO_INITDB_ROOT_USERNAME: admin
      MONGO_INITDB_ROOT_PASSWORD: secret
    volumes:
      - mongo_data:/data/db

  redis:
    image: redis:7.2-alpine
    ports: ["6379:6379"]
    command: redis-server --requirepass secret
    volumes:
      - redis_data:/data

  mongo-express:
    image: mongo-express:1.0
    ports: ["8081:8081"]
    environment:
      ME_CONFIG_MONGODB_ADMINUSERNAME: admin
      ME_CONFIG_MONGODB_ADMINPASSWORD: secret
      ME_CONFIG_MONGODB_URL: mongodb://admin:secret@mongodb:27017/
    depends_on:
      - mongodb

volumes:
  pg_data:
  mongo_data:
  redis_data:
Laboratorio 10.1 — Análisis de Datos con Jupyter + PostgreSQL

Objetivo: Levantar un entorno de análisis de datos completo en 2 minutos.

Bash
# 1. Crear directorios
mkdir data-lab && cd data-lab
mkdir notebooks data

# 2. compose.yaml
cat > compose.yaml << 'EOF'
name: data-lab
services:
  jupyter:
    image: jupyter/scipy-notebook:latest
    ports:
      - "8888:8888"
    volumes:
      - ./notebooks:/home/jovyan/work
    environment:
      JUPYTER_TOKEN: mytoken
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: datalab
      POSTGRES_DB: analytics
    ports:
      - "5432:5432"
EOF

# 3. Levantar stack
docker compose up -d
✅ Abre http://localhost:8888?token=mytoken
Cuestionario — Capítulo 10
1. ¿Qué ventaja ofrece KRaft mode en Kafka (sin Zookeeper)?
KRaft (Kafka Raft) es el modo nativo de Kafka desde 3.x que elimina la dependencia de Zookeeper, reduciendo la complejidad operacional, mejorando la latencia de metadatos y simplificando el deployment.

Capítulo 11 · Introducción a Kubernetes

Nivel 3 · Avanzado

Kubernetes (K8s) es el orquestador de contenedores líder de la industria. En 2026, prácticamente toda empresa que usa Docker en producción a escala usa Kubernetes. Es la evolución natural de Docker Compose.

11.1 ¿Por qué Kubernetes?

CapacidadDocker ComposeKubernetes
Escenario1 máquinaMúltiples máquinas (cluster)
Escalado automáticoManualHorizontal Pod Autoscaler (HPA)
Self-healingLimitadoReinicia pods fallidos automáticamente
Rolling updatesNo nativoZero-downtime deployments
Service discoveryDNS básicoDNS + Services + Ingress
SecretsVariables envKubernetes Secrets + Vault
Curva de aprendizajeBajaAlta

11.2 Arquitectura de Kubernetes

┌────────────────────────────────────────────────────────────────┐ │ KUBERNETES CLUSTER │ │ │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ CONTROL PLANE (Master) │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐ │ │ │ │ │API Server│ │Scheduler │ │Controller│ │ etcd │ │ │ │ │ └──────────┘ └──────────┘ └──────────┘ └────────┘ │ │ │ └───────────────────────────────────────────────────────┘ │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Worker 1 │ │ Worker 2 │ │ Worker 3 │ │ │ │ ┌────────┐ │ │ ┌────────┐ │ │ ┌────────┐ │ │ │ │ │ Pod │ │ │ │ Pod │ │ │ │ Pod │ │ │ │ │ │(nginx) │ │ │ │ (api) │ │ │ │ (db) │ │ │ │ │ └────────┘ │ │ └────────┘ │ │ └────────┘ │ │ │ │ kubelet │ │ kubelet │ │ kubelet │ │ │ │ kube-proxy │ │ kube-proxy │ │ kube-proxy │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ └────────────────────────────────────────────────────────────────┘

11.3 Objetos Principales de Kubernetes

Pod
Unidad mínima. Uno o más contenedores que comparten red y almacenamiento.
Deployment
Gestiona réplicas de Pods. Permite rolling updates y rollbacks.
Service
Exposición estable de Pods. ClusterIP, NodePort, LoadBalancer.
Ingress
HTTP/HTTPS routing. Expone múltiples servicios bajo un solo IP/dominio.

11.4 minikube — K8s Local

Bash — Setup local
# Instalar minikube
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube

# Iniciar cluster con Docker driver
minikube start --driver=docker --cpus=4 --memory=8g

# Verificar
kubectl get nodes
NAME       STATUS   ROLES           AGE   VERSION
minikube   Ready    control-plane   60s   v1.30.0

11.5 Tu Primera Aplicación en Kubernetes

YAML — Deployment + Service
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mi-app
  labels:
    app: mi-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: mi-app
  template:
    metadata:
      labels:
        app: mi-app
    spec:
      containers:
        - name: app
          image: mi-app:v1.0
          ports:
            - containerPort: 8000
          resources:
            requests:
              cpu: "100m"
              memory: "128Mi"
            limits:
              cpu: "500m"
              memory: "512Mi"
          livenessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 30
          readinessProbe:
            httpGet:
              path: /ready
              port: 8000
---
apiVersion: v1
kind: Service
metadata:
  name: mi-app-svc
spec:
  selector:
    app: mi-app
  ports:
    - port: 80
      targetPort: 8000
  type: LoadBalancer
Bash — Comandos kubectl esenciales
kubectl apply -f deployment.yaml
kubectl get pods
kubectl get services
kubectl describe pod mi-app-xxxx
kubectl logs mi-app-xxxx -f
kubectl exec -it mi-app-xxxx -- bash
kubectl scale deployment mi-app --replicas=5
kubectl rollout history deployment mi-app
kubectl rollout undo deployment mi-app
kubectl delete -f deployment.yaml

11.6 Kubernetes Managed — Cloud en 2026

ServicioProveedorCostoVentaja principal
GKE AutopilotGoogle Cloud$$/podGestión serverless total del cluster
EKSAWS$$Integración nativa con servicios AWS
AKSAzure$$Integración con Azure AD y DevOps
DOKSDigitalOcean$Más simple y económico para startups
Cuestionario — Capítulo 11
1. ¿Cuál es la relación entre Docker y Kubernetes?
Docker se usa para construir y empaquetar contenedores. Kubernetes usa esos contenedores y los orquesta a escala: scheduling, scaling, healing, networking y storage en clusters de múltiples nodos.

Capítulo 12 · Seguridad y Optimización

Nivel 3 · Avanzado

La seguridad en Docker no es opcional en producción. En 2026, con ataques de supply chain en aumento, cada imagen debe ser escaneada y cada contenedor debe seguir el principio de mínimo privilegio.

12.1 Los 10 Riesgos Principales en Docker

#RiesgoMitigación
1Contenedor ejecutando como rootUSER en Dockerfile, --user en docker run
2Imagen base con vulnerabilidadesdocker scout, Trivy, actualizar regularmente
3Secretos en ENV/ARG del DockerfileDocker Secrets, Vault, --secret BuildKit
4Docker socket expuestoRootless Docker, usar API proxy
5Capabilities excesivas--cap-drop ALL --cap-add [mínimas]
6Red promiscuaRedes segmentadas, --network=none
7Filesystem escribible--read-only + tmpfs para /tmp
8Imágenes de fuentes no confiablesUsar solo imágenes oficiales/verificadas
9Sin límites de recursos--memory --cpus en producción
10Tags mutables (latest)Usar digests SHA256 en producción

12.2 Hardening de Contenedores

Bash — Contenedor endurecido
docker run \
  --read-only \
  --tmpfs /tmp:rw,size=50m \
  --tmpfs /var/run:rw,size=10m \
  --user 1001:1001 \
  --cap-drop ALL \
  --cap-add NET_BIND_SERVICE \
  --security-opt no-new-privileges:true \
  --security-opt seccomp=/etc/docker/seccomp.json \
  --memory="256m" \
  --cpus="0.5" \
  --network mi-red \
  --pids-limit 100 \
  mi-app:v1.0

12.3 Escaneo de Vulnerabilidades

Bash — Trivy (herramienta líder 2026)
# Instalar Trivy
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh

# Escanear imagen
trivy image nginx:alpine
2024-01-15T10:00:00 INFO Detected OS: alpine 3.19
nginx:alpine (alpine 3.19)
Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)

# Escanear Dockerfile
trivy config Dockerfile

# Escanear en CI/CD (falla con CRITICAL)
trivy image --exit-code 1 --severity CRITICAL mi-app:latest

# Docker Scout (integrado en Docker CLI)
docker scout cves mi-app:latest
docker scout quickview mi-app:latest

12.4 Optimización de Imágenes

TécnicaReducción típicaCómo
Base Alpine/Distroless80-90%FROM alpine o FROM distroless
Multi-stage build70-95%Solo copiar artefactos finales
Limpiar cache APT30-50MBrm -rf /var/lib/apt/lists/*
.dockerignoreVariableExcluir node_modules, .git, etc.
Combinar RUN10-20%&& en lugar de múltiples RUN

12.5 Docker Rootless Mode

En 2026, Rootless Docker es la configuración recomendada para producción. El daemon corre como usuario no-root, eliminando el vector de ataque de escalada de privilegios:

Bash — Instalar Docker Rootless
# Instalar docker-ce sin daemon root
curl -fsSL https://get.docker.com/rootless | sh

# Configurar PATH y DOCKER_HOST
export PATH=/home/$USER/bin:$PATH
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock

# Iniciar servicio de usuario
systemctl --user start docker
systemctl --user enable docker

# Verificar
docker info | grep rootless
  rootless: true

12.6 Benchmarking y Profiling

Bash — Análisis de rendimiento
# Estadísticas en tiempo real
docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}"

# Analizar tamaño de imagen por capas
docker history --human=true --format "{{.Size}}\t{{.CreatedBy}}" mi-app | sort -rh | head -10

# dive: herramienta de análisis de capas
dive mi-app:latest  # Instalar: https://github.com/wagoodman/dive
Cuestionario — Capítulo 12
1. ¿Qué hace el flag --cap-drop ALL en Docker?
Linux capabilities son permisos granulares del kernel. --cap-drop ALL elimina todas (incluyendo NET_RAW, SYS_ADMIN, etc.) y luego se añaden solo las mínimas necesarias con --cap-add, aplicando el principio de mínimo privilegio.

Capítulo 13 · Docker en Producción

Nivel 3 · Avanzado

Llevar Docker a producción requiere CI/CD, monitoreo, logging centralizado y estrategias de despliegue sin tiempo de inactividad. En 2026, este es el stack estándar de la industria.

13.1 CI/CD con GitHub Actions

YAML — .github/workflows/docker.yml
name: Docker CI/CD Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build-test-push:
    runs-on: ubuntu-latest
    permissions:
      packages: write
      contents: read

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Docker meta (tags automáticos)
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=sha

      - name: Scan con Trivy
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ghcr.io/${{ github.repository }}:latest
          severity: CRITICAL,HIGH
          exit-code: 1

      - name: Build and Push
        uses: docker/build-push-action@v6
        with:
          context: .
          push: ${{ github.event_name == 'push' }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          platforms: linux/amd64,linux/arm64

13.2 Monitoreo con Prometheus + Grafana

YAML — Stack de Monitoreo
name: monitoring
services:
  prometheus:
    image: prom/prometheus:v2.51.0
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    ports:
      - "9090:9090"
    command:
      - --config.file=/etc/prometheus/prometheus.yml
      - --storage.tsdb.retention.time=15d

  grafana:
    image: grafana/grafana:10.4.0
    ports:
      - "3000:3000"
    environment:
      GF_SECURITY_ADMIN_PASSWORD: admin
    volumes:
      - grafana_data:/var/lib/grafana
      - ./grafana/dashboards:/etc/grafana/provisioning/dashboards
    depends_on:
      - prometheus

  cadvisor:
    image: gcr.io/cadvisor/cadvisor:v0.49.0
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
    ports:
      - "8080:8080"

volumes:
  prometheus_data:
  grafana_data:

13.3 Logging Centralizado — EFK Stack

YAML — Elasticsearch + Fluentd + Kibana
services:
  elasticsearch:
    image: elasticsearch:8.13.0
    environment:
      discovery.type: single-node
      xpack.security.enabled: "false"
      ES_JAVA_OPTS: "-Xms512m -Xmx512m"
    ports: ["9200:9200"]
    volumes:
      - es_data:/usr/share/elasticsearch/data

  kibana:
    image: kibana:8.13.0
    ports: ["5601:5601"]
    environment:
      ELASTICSEARCH_HOSTS: http://elasticsearch:9200
    depends_on:
      - elasticsearch

  fluentd:
    build: ./fluentd
    volumes:
      - ./fluentd/fluent.conf:/fluentd/etc/fluent.conf
    ports:
      - "24224:24224"
      - "24224:24224/udp"
    depends_on:
      - elasticsearch

volumes:
  es_data:
Bash — Usar Fluentd como log driver
docker run -d \
  --log-driver=fluentd \
  --log-opt fluentd-address=localhost:24224 \
  --log-opt tag=mi-app.{{.Name}} \
  mi-app:v1.0

13.4 Despliegue en la Nube

AWS ECS Fargate
Serverless containers. Sin gestionar instancias EC2. Paga por tarea.
Google Cloud Run
Escala a cero. Ideal para APIs y microservicios. Muy económico.
Azure Container Apps
Kubernetes-based serverless. KEDA para event-driven scaling.

13.5 Estrategias de Despliegue Zero-Downtime

EstrategiaDescripciónCuando usar
Rolling UpdateReemplaza instancias gradualmenteDefault en K8s, mínimo downtime
Blue/GreenDos entornos idénticos, swap de tráficoRollback instantáneo
Canary% del tráfico a la nueva versiónValidar con tráfico real
A/B TestingDiferentes versiones por usuario/regiónExperimentos de producto
Cuestionario — Capítulo 13
1. ¿Qué ventaja tiene el caché de GitHub Actions en el pipeline de Docker?
Con cache-from y cache-to type=gha, BuildKit almacena las capas de build en el caché de GitHub Actions. En builds subsecuentes, si el código no cambió, las capas se reutilizan y el build puede pasar de 5 minutos a 30 segundos.

Capítulo 14 · Proyectos Completos de Principio a Fin

Nivel 3 · Avanzado

Los siguientes proyectos integran todos los conceptos del curso. Están diseñados para ser portfolio-ready y representar situaciones reales de la industria en 2026.

Proyecto 1 — API REST Full Stack Producción

Stack: FastAPI + PostgreSQL + Redis + Nginx + Celery + Flower

Objetivo: Sistema de gestión de tareas con autenticación JWT, cache Redis, workers asíncronos y API documentada.

proyecto-api/ ├── compose.yaml ├── compose.prod.yaml ├── .env.example ├── api/ │ ├── Dockerfile │ ├── main.py │ ├── requirements.txt │ └── app/ (routers, models, schemas) ├── nginx/ │ └── nginx.conf ├── worker/ (Celery tasks) └── .github/workflows/ └── ci-cd.yml
YAML — Proyecto 1 compose.yaml
name: fullstack-api
services:
  db:
    image: postgres:16-alpine
    volumes:
      - pg_data:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: ${DB_NAME}
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASS}
    healthcheck:
      test: ["CMD", "pg_isready"]
      interval: 5s

  redis:
    image: redis:7-alpine
    command: redis-server --requirepass ${REDIS_PASS}

  api:
    build:
      context: ./api
      target: production
    environment:
      DATABASE_URL: postgresql://${DB_USER}:${DB_PASS}@db/${DB_NAME}
      REDIS_URL: redis://:${REDIS_PASS}@redis:6379
      SECRET_KEY: ${SECRET_KEY}
    depends_on:
      db:
        condition: service_healthy
    volumes:
      - media:/app/media

  worker:
    build:
      context: ./api
      target: production
    command: celery -A app.worker worker --loglevel=info
    environment:
      REDIS_URL: redis://:${REDIS_PASS}@redis:6379
    depends_on:
      - redis

  nginx:
    image: nginx:1.27-alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
      - media:/app/media:ro
    depends_on:
      - api

volumes:
  pg_data:
  media:

Proyecto 2 — Pipeline de Data Engineering

Stack: Apache Airflow + PostgreSQL + MinIO + Spark + Jupyter

Objetivo: Pipeline ETL que extrae datos de una API pública, los transforma con Spark y los carga en PostgreSQL, con orquestación via Airflow y almacenamiento en MinIO.

Python — DAG de Airflow con Docker
# dags/etl_pipeline.py
from airflow.decorators import dag, task
from airflow.providers.docker.operators.docker import DockerOperator
from datetime import datetime

@dag(schedule="@daily", start_date=datetime(2026, 1, 1))
def etl_pipeline():

    extract = DockerOperator(
        task_id="extract_data",
        image="mi-extractor:latest",
        command="python extract.py --date {{ ds }}",
        docker_url="unix://var/run/docker.sock",
        network_mode="data-stack_data-net",
    )

    transform = DockerOperator(
        task_id="transform_spark",
        image="apache/spark:3.5",
        command="spark-submit /jobs/transform.py",
        environment={"S3_ENDPOINT": "http://minio:9000"},
    )

    extract >> transform

etl_pipeline()

Proyecto 3 — Microservicios con API Gateway

Stack: Traefik (API Gateway) + 3 microservicios Node.js + MongoDB + Redis

Objetivo: Arquitectura de microservicios con service discovery automático, SSL automático y load balancing.

YAML — Traefik como API Gateway
name: microservices
services:
  traefik:
    image: traefik:v3.0
    command:
      - --api.insecure=true
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --entrypoints.web.address=:80
    ports:
      - "80:80"
      - "8080:8080"   # Traefik dashboard
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro

  users-service:
    build: ./services/users
    labels:
      - traefik.enable=true
      - traefik.http.routers.users.rule=PathPrefix(`/api/users`)
      - traefik.http.services.users.loadbalancer.server.port=3000

  products-service:
    build: ./services/products
    labels:
      - traefik.enable=true
      - traefik.http.routers.products.rule=PathPrefix(`/api/products`)
    scale: 3  # 3 réplicas con load balancing automático

  orders-service:
    build: ./services/orders
    labels:
      - traefik.enable=true
      - traefik.http.routers.orders.rule=PathPrefix(`/api/orders`)
Proyecto Final — Sistema de E-Commerce

Stack completo: Next.js + FastAPI + PostgreSQL + Redis + Kafka + Elasticsearch + Kibana + Prometheus + Grafana + Traefik

Funcionalidades: Catálogo de productos, carrito, pagos, notificaciones async via Kafka, búsqueda con Elasticsearch, métricas y logs centralizados.

Desafío: Implementar rolling deployment con GitHub Actions, escaneo de vulnerabilidades con Trivy y despliegue en GKE.

Portfolio Ready: Este proyecto cubre los temas más solicitados en entrevistas DevOps/Backend 2026: contenedores, CI/CD, observabilidad, mensajería y cloud.
Cuestionario Final — Evaluación General
1. ¿Cuál es el orden correcto del flujo CI/CD con Docker en producción?
El flujo correcto es: Build (construir imagen) → Test (tests unitarios/integración) → Scan (vulnerabilidades con Trivy/Scout) → Push (subir al registry) → Deploy (desplegar en producción).
2. ¿Qué característica de Traefik lo hace ideal como API Gateway para microservicios Docker?
Traefik detecta automáticamente los contenedores de Docker mediante labels en compose.yaml. Cuando se escala o despliega un nuevo servicio, Traefik actualiza su routing sin reiniciarse. Esto es ideal para arquitecturas dinámicas de microservicios.
¡Curso Docker Mastery 2026 Completado!

Has completado exitosamente el curso profesional de Docker 2026, cubriendo desde arquitectura básica hasta despliegues en producción con Kubernetes, CI/CD, Data Engineering y proyectos completos de la industria.