Docker Mastery 2026
Contenedores · De Principiante a Avanzado · Casos Reales · 14 Módulos
Capítulo 1 · Introducción a Docker y Arquitectura
Nivel 1 · PrincipianteDocker 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.
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.
1.2 Contenedores vs Máquinas Virtuales
La diferencia fundamental está en el nivel de abstracción:
| Aspecto | Máquina Virtual (VM) | Contenedor Docker |
|---|---|---|
| Virtualización | Hardware completo (Hypervisor) | Kernel del SO host |
| Tiempo de arranque | 1–5 minutos | Milisegundos |
| Tamaño en disco | GBs (incluye SO completo) | MBs (solo capas delta) |
| Consumo de RAM | Alto (SO + App) | Mínimo (solo App) |
| Densidad por host | Decenas | Cientos o miles |
| Aislamiento | Fuerte (kernel separado) | Bueno (namespace + cgroups) |
| Portabilidad | Media | Excelente |
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.
1.4 Conceptos Fundamentales
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:
1.6 Historia y Evolución
| Año | Hito |
|---|---|
| 2013 | Docker 0.1 lanzado por dotCloud (Solomon Hykes). Demostración en PyCon. |
| 2014 | Docker 1.0. Soporte de Google, Red Hat, IBM, Microsoft. |
| 2015 | Estándar OCI (Open Container Initiative). Docker Compose 1.0. |
| 2016 | Docker Swarm nativo. Integración Docker + Windows Server. |
| 2017 | Kubernetes supera a Swarm. Moby Project. |
| 2019 | containerd se dona a CNCF. Docker Desktop para Mac/Win. |
| 2021 | Docker Desktop pasa a ser de pago para empresas grandes. |
| 2023 | Docker Compose V2 es el default. Docker Scout para seguridad. |
| 2025 | Docker Desktop 5.x. Soporte nativo Apple Silicon. Docker Build Cloud GA. |
| 2026 | Docker Engine 28.x. WebAssembly containers. Integración IA nativa. |
1.7 Casos Reales de la Industria
Capítulo 2 · Instalación en Windows, Linux y macOS
Nivel 1 · Principiante2.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.
wsl --install # Reinicia el sistema después de la instalación wsl --set-default-version 2 wsl --status
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.
# 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.
# 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
{
"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"
}
2.5 Docker en Fedora/RHEL/Rocky Linux
# 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
Capítulo 3 · Comandos Básicos y Avanzados
Nivel 1-2 · Principiante-IntermedioDominar 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
# ── 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
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
| Flag | Descripción | Ejemplo |
|---|---|---|
--memory | Límite de RAM | --memory="512m" |
--cpus | Límite de CPU | --cpus="1.5" |
-e | Variable de entorno | -e DB_HOST=localhost |
--env-file | Archivo de variables | --env-file .env |
-v | Montar volumen/bind | -v /host:/container |
--network | Red a conectar | --network mi-red |
--restart | Política de reinicio | --restart=always |
--health-cmd | Comando healthcheck | --health-cmd="curl -f http://localhost" |
--user | Usuario de ejecución | --user 1000:1000 |
--read-only | FS de solo lectura | --read-only |
3.4 Comandos de Sistema y Diagnóstico
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"
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.Objetivo: Ejecutar nginx, mapear puerto, personalizar la página y hacer limpieza.
# 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
Capítulo 4 · Imágenes y Docker Hub
Nivel 1-2 · Principiante-IntermedioLas 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.
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.
# 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:
| Tag | Significado | Recomendación |
|---|---|---|
latest | Última versión (por defecto) | ❌ Evitar en producción |
3.12 | Versión mayor.menor | ✅ Buena para dev |
3.12.7 | Versión exacta | ✅ Ideal para producción |
3.12-slim | Versión reducida (Debian slim) | ✅ Recomendada |
3.12-alpine | Basada en Alpine Linux (~5MB) | ✅ Mínima, segura |
sha256:abc... | Digest inmutable | ✅ Máxima reproducibilidad |
4.4 Registries Alternativos en 2026
| Registry | Proveedor | Uso típico |
|---|---|---|
| Docker Hub | Docker Inc. | Open source, imágenes públicas |
| Amazon ECR | AWS | Aplicaciones en AWS EKS/ECS |
| Google Artifact Registry | GCP | Apps en GKE, Cloud Run |
| Azure Container Registry | Microsoft | Apps en AKS |
| GitHub Container Registry | GitHub | CI/CD con GitHub Actions |
| Harbor | Open 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.
# 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
Objetivo: Crear una imagen personalizada y publicarla en Docker Hub.
# 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
Capítulo 5 · Dockerfile — Todas las Instrucciones
Nivel 2 · IntermedioEl 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
# 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.
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:
# ❌ 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ón | Función | Cuándo usar |
|---|---|---|
COPY | Copia archivos del contexto al contenedor | ✅ Siempre que sea posible |
ADD | Copia + extrae .tar + descarga URLs | ❌ Solo si necesitas extraer tarballs |
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
# 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 .
docker history e docker inspect. Usa Docker Secrets o variables de entorno en runtime.WORKDIR, USER, EXPOSE, VOLUME
# 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ón | Propósito | ¿Se puede overrride? |
|---|---|---|
CMD | Comando por defecto, fácilmente reemplazable | ✅ Sí (con argumento en docker run) |
ENTRYPOINT | Punto de entrada fijo del contenedor | --entrypoint flag |
ENTRYPOINT + CMD | ENTRYPOINT = ejecutable, CMD = args por defecto | ✅ CMD fácilmente |
# 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
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.
# ── 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
# ── 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
# 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.
# 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 .
Objetivo: Construir una API FastAPI optimizada con multi-stage, usuario no-root y healthcheck.
# 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"]
Capítulo 6 · Redes en Docker
Nivel 2 · IntermedioLas 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
| Driver | Descripción | Caso de uso |
|---|---|---|
| bridge | Red virtual privada entre contenedores en el mismo host | Default para contenedores standalone |
| host | Comparte la red del host directamente | Alto rendimiento, sin aislamiento de red |
| none | Sin red. Solo loopback | Máximo aislamiento |
| overlay | Red distribuida entre múltiples hosts | Docker Swarm, múltiples servidores |
| macvlan | Asigna MAC address real al contenedor | Integración con redes físicas legacy |
| ipvlan | Similar a macvlan, comparte MAC del host | Entornos con restricciones de MAC |
6.2 Comandos de Redes
# 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:
# 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
# -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
• 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
Objetivo: Crear dos redes separadas (frontend y backend) y conectar servicios apropiadamente.
# 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
Capítulo 7 · Volúmenes y Persistencia de Datos
Nivel 2 · IntermedioLos 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
| Tipo | Origen | Uso ideal | Prod? |
|---|---|---|---|
| Volume | Docker gestiona en /var/lib/docker/volumes/ | Datos de producción, BDs | ✅ Sí |
| Bind Mount | Directorio del host especificado por ti | Desarrollo local (hot reload) | ❌ Con cuidado |
| tmpfs | RAM del host (no persiste) | Datos sensibles temporales | ⚠️ Casos específicos |
7.2 Docker Volumes (Recomendado)
# 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
# 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
# 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
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.
-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.
Objetivo: Demostrar que los datos sobreviven al ciclo de vida del contenedor.
# 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!
Capítulo 8 · Docker Compose
Nivel 2 · IntermedioDocker 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
# 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
# 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:
# 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
# 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
# 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
Objetivo: Levantar un stack completo con compose en minutos.
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: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
depends_on con condition service_healthy?Capítulo 9 · Docker para Desarrollo
Nivel 2-3 · Intermedio-AvanzadoDocker 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
# 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
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-alpine9.2 Docker para Node.js
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
# 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
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
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.
{
"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"
}Capítulo 10 · Docker para Data Engineering
Nivel 3 · AvanzadoDocker 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
10.2 Jupyter Notebook + PySpark
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
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: scheduler10.4 Apache Kafka + Zookeeper
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
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
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:Objetivo: Levantar un entorno de análisis de datos completo en 2 minutos.
# 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
Capítulo 11 · Introducción a Kubernetes
Nivel 3 · AvanzadoKubernetes (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?
| Capacidad | Docker Compose | Kubernetes |
|---|---|---|
| Escenario | 1 máquina | Múltiples máquinas (cluster) |
| Escalado automático | Manual | Horizontal Pod Autoscaler (HPA) |
| Self-healing | Limitado | Reinicia pods fallidos automáticamente |
| Rolling updates | No nativo | Zero-downtime deployments |
| Service discovery | DNS básico | DNS + Services + Ingress |
| Secrets | Variables env | Kubernetes Secrets + Vault |
| Curva de aprendizaje | Baja | Alta |
11.2 Arquitectura de Kubernetes
11.3 Objetos Principales de Kubernetes
11.4 minikube — K8s 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
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: LoadBalancerkubectl 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
| Servicio | Proveedor | Costo | Ventaja principal |
|---|---|---|---|
| GKE Autopilot | Google Cloud | $$/pod | Gestión serverless total del cluster |
| EKS | AWS | $$ | Integración nativa con servicios AWS |
| AKS | Azure | $$ | Integración con Azure AD y DevOps |
| DOKS | DigitalOcean | $ | Más simple y económico para startups |
Capítulo 12 · Seguridad y Optimización
Nivel 3 · AvanzadoLa 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
| # | Riesgo | Mitigación |
|---|---|---|
| 1 | Contenedor ejecutando como root | USER en Dockerfile, --user en docker run |
| 2 | Imagen base con vulnerabilidades | docker scout, Trivy, actualizar regularmente |
| 3 | Secretos en ENV/ARG del Dockerfile | Docker Secrets, Vault, --secret BuildKit |
| 4 | Docker socket expuesto | Rootless Docker, usar API proxy |
| 5 | Capabilities excesivas | --cap-drop ALL --cap-add [mínimas] |
| 6 | Red promiscua | Redes segmentadas, --network=none |
| 7 | Filesystem escribible | --read-only + tmpfs para /tmp |
| 8 | Imágenes de fuentes no confiables | Usar solo imágenes oficiales/verificadas |
| 9 | Sin límites de recursos | --memory --cpus en producción |
| 10 | Tags mutables (latest) | Usar digests SHA256 en producción |
12.2 Hardening de Contenedores
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.012.3 Escaneo de Vulnerabilidades
# 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écnica | Reducción típica | Cómo |
|---|---|---|
| Base Alpine/Distroless | 80-90% | FROM alpine o FROM distroless |
| Multi-stage build | 70-95% | Solo copiar artefactos finales |
| Limpiar cache APT | 30-50MB | rm -rf /var/lib/apt/lists/* |
| .dockerignore | Variable | Excluir node_modules, .git, etc. |
| Combinar RUN | 10-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:
# 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
# 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
Capítulo 13 · Docker en Producción
Nivel 3 · AvanzadoLlevar 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
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/arm6413.2 Monitoreo con Prometheus + Grafana
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
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:docker run -d \
--log-driver=fluentd \
--log-opt fluentd-address=localhost:24224 \
--log-opt tag=mi-app.{{.Name}} \
mi-app:v1.013.4 Despliegue en la Nube
13.5 Estrategias de Despliegue Zero-Downtime
| Estrategia | Descripción | Cuando usar |
|---|---|---|
| Rolling Update | Reemplaza instancias gradualmente | Default en K8s, mínimo downtime |
| Blue/Green | Dos entornos idénticos, swap de tráfico | Rollback instantáneo |
| Canary | % del tráfico a la nueva versión | Validar con tráfico real |
| A/B Testing | Diferentes versiones por usuario/región | Experimentos de producto |
Capítulo 14 · Proyectos Completos de Principio a Fin
Nivel 3 · AvanzadoLos 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.
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.
# 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.
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`)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.
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.