Docker resuelve construir y ejecutar contenedores. Todo lo demás —ver qué pasa sin bucear en docker ps, saber por qué una imagen pesa de más, detectar vulnerabilidades antes de publicar, montar un registro propio, probar contra una base de datos real— lo resuelve el ecosistema que hay alrededor. Esta lección no es un catálogo: es el equipamiento seleccionado con criterio, y el criterio para seleccionarlo tú.
Contenido
- Los tres niveles de extensibilidad
- Construye tu propio
docker aurora - Plugins del daemon y extensiones del escritorio
- Interfaces de gestión
- Inspección y optimización de imágenes
- Seguridad
- Registros propios
- Desarrollo y pruebas
- Construcción sin Dockerfile
- Limpieza y mantenimiento
- Red y diagnóstico
- Criterios para adoptar una herramienta
- El kit mínimo recomendado
- Los tres niveles de extensibilidad
| Nivel | Mecanismo | Alcance | Riesgo si falla |
|---|---|---|---|
| CLI | Binario docker-<nombre> en el PATH o en ~/.docker/cli-plugins/ |
Tu terminal | Bajo: no arranca el subcomando |
| Daemon | Plugins de volumen, red o logging (API de plugins) | Todo el host | Alto: puede tumbar contenedores |
| Escritorio | Extensiones de Docker Desktop (contenedor + UI) | Tu máquina | Medio: código de terceros con acceso al daemon |
La regla que se deduce: experimenta libremente en el nivel de la CLI, sé conservador en el del daemon. Un plugin de logging mal escrito bloquea el arranque de todos los contenedores del host.
- Construye tu propio
docker aurora
docker auroraEl mecanismo de los plugins de CLI es asombrosamente simple: cualquier ejecutable llamado docker-algo en ~/.docker/cli-plugins/ se convierte en docker algo. Así funcionan docker compose, docker buildx y docker scout. El único requisito formal es responder al subcomando oculto docker-cli-plugin-metadata.
#!/usr/bin/env bash
# ~/.docker/cli-plugins/docker-aurora — atajos del equipo de Aurora Libros
set -euo pipefail
if [ "${1:-}" = "docker-cli-plugin-metadata" ]; then
cat <<'JSON'
{ "SchemaVersion": "0.1.0", "Vendor": "Aurora Libros S.L.",
"Version": "1.0.0", "ShortDescription": "Atajos de la plataforma Aurora" }
JSON
exit 0
fi
shift # el primer argumento siempre es "aurora"
case "${1:-ayuda}" in
arriba) docker compose -f compose.yaml -f compose.dev.yaml up -d --wait ;;
abajo) docker compose down ;;
logs) docker compose logs -f --tail=100 "${2:-api}" ;;
psql) docker compose exec -it aurora-db psql -U aurora -d aurora_libros ;;
redis) docker compose exec -it aurora-cache redis-cli ;;
salud) curl -fsS localhost:8080/salud/listo | jq . ;;
libros) curl -fsS localhost:8080/libros | jq -r '.libros[] | "\(.id) \(.titulo)"' ;;
reset) docker compose down -v && docker compose up -d --wait ;;
*) echo "Uso: docker aurora {arriba|abajo|logs|psql|redis|salud|libros|reset}" ;;
esacchmod +x ~/.docker/cli-plugins/docker-aurora
docker aurora arriba
docker aurora libros
# 1 El jardín de senderos que se bifurcan
# 2 Rayuela
# 3 Cien años de soledad
# ...
docker --help | grep aurora
# aurora* Atajos de la plataforma Aurora (Aurora Libros S.L. 1.0.0)Cuarenta líneas y el equipo entero deja de memorizar comandos largos. Guárdalo en el repositorio con un make install-plugin y forma parte del onboarding. El asterisco en la ayuda indica que es un plugin, no un comando nativo.
- Plugins del daemon y extensiones del escritorio
Los plugins del daemon amplían Docker por debajo. Se instalan con docker plugin install y se gestionan por separado:
docker plugin install grafana/loki-docker-driver:latest --alias loki
docker plugin ls
docker run --log-driver=loki --log-opt loki-url="http://loki:3100/loki/api/v1/push" ...| Tipo | Ejemplos | Cuándo se justifica |
|---|---|---|
| Volumen | rexray, local-persist, NFS/CIFS |
Almacenamiento en red desde Compose |
| Red | weave, calico |
Redes multi-host fuera de Swarm/K8s |
| Logging | loki, fluentd, gelf |
Centralizar logs sin sidecar (05-06) |
El aviso serio: un plugin de logging que se bloquea puede impedir que los contenedores arranquen, porque el daemon espera a que acepte el flujo. Antes de ponerlo en producción, pruébalo con el destino caído a propósito y comprueba que mode: non-blocking está configurado.
Las extensiones de Docker Desktop son contenedores con interfaz que se integran en el panel. Cómodas y con la misma precaución que cualquier código de terceros con acceso al socket del daemon: si controla el socket, controla el host (05-03).
- Interfaces de gestión
| Herramienta | Tipo | Fuerte en | Flojo en | Licencia |
|---|---|---|---|---|
lazydocker |
TUI en terminal | Ver logs, reiniciar, estadísticas sin teclear | Solo local | MIT |
ctop |
TUI, tipo top |
Ver consumo de la flota de un vistazo | Sin gestión avanzada | MIT |
| Portainer | Web, servidor | Equipos, permisos, varios hosts, Swarm/K8s | Pesado; CE recortado frente a BE | Zlib (CE) |
| Dockge | Web, servidor | Gestionar pilas de Compose editando el YAML | Solo Compose, proyecto joven | MIT |
| Docker Desktop | GUI local | Integrado, builds con tiempos | Solo escritorio, licencia | Propietaria |
# lazydocker: cero instalación permanente
docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock \
-v ~/.config/lazydocker:/.config/jesseduffield/lazydocker \
lazyteam/lazydockerCon la pila de Aurora Libros arriba, lazydocker te da en una pantalla los cuatro contenedores, sus logs en vivo y su consumo, y permite reiniciar aurora-api con una tecla. Para depurar en local es más rápido que cualquier GUI.
Portainer solo se justifica cuando hay varias personas operando varios hosts y hacen falta permisos por usuario. Para un servidor y un equipo pequeño con Compose, Dockge es más ligero y no esconde el YAML: sigues teniendo tus ficheros y los editas desde el navegador.
Precaución común a todas: exponen el socket de Docker. Portainer o Dockge accesibles desde Internet sin autenticación fuerte equivalen a regalar el host.
- Inspección y optimización de imágenes
| Herramienta | Qué hace | Cuándo usarla |
|---|---|---|
dive |
Recorre la imagen capa a capa y calcula el desperdicio | Antes de dar por buena una imagen |
slim (antes docker-slim) |
Ejecuta la app, observa qué usa y descarta el resto | Imágenes heredadas que no puedes reescribir |
container-diff |
Compara dos imágenes: paquetes, ficheros, tamaños | Auditar qué cambió entre 2.0.0 y 2.1.0 |
docker history |
Tamaño por capa, sin instalar nada | Primer vistazo rápido |
dive ghcr.io/auroralibros/aurora-api:2.0.0
# Image Details: Total Image size: 104 MB
# Potential wasted space: 1.8 MB
# Image efficiency score: 98%Ese 98 % es el resultado del trabajo de 05-04: la multietapa dejó fuera las dependencias de compilación y no hay ficheros duplicados entre capas. dive en modo integración devuelve código de salida distinto de cero si el proyecto baja del umbral, y eso encaja directo en el pipeline:
# container-diff: qué cambió realmente entre dos versiones
container-diff diff --type=apt --type=file --type=size \
daemon://aurora-api:2.0.0 daemon://aurora-api:2.1.0Sobre slim: adelgaza automáticamente ejecutando la aplicación y quedándose solo con lo que tocó. Puede llevar una imagen de 400 MB a 30 MB, y también puede romperla de forma silenciosa el día que se ejecute una ruta de código que no se probó durante el análisis. Úsalo con una batería de pruebas completa detrás, y prefiere siempre arreglar el Dockerfile si tienes acceso a él.
- Seguridad
| Herramienta | Qué analiza | Momento | Licencia |
|---|---|---|---|
hadolint |
El Dockerfile, antes de construir | Editor y CI | GPL-3.0 |
| Trivy | Vulnerabilidades, secretos, IaC, SBOM | CI y registro | Apache 2.0 |
| Grype | Vulnerabilidades (pareja de Syft) | CI, segunda opinión | Apache 2.0 |
| Docker Scout | Vulnerabilidades + recomendaciones | Escritorio y CI | Propietaria |
| Docker Bench | Configuración del host y del daemon | Auditoría periódica | Apache 2.0 |
| Falco | Comportamiento en tiempo de ejecución | Producción, continuo | Apache 2.0 |
| Cosign | Firma y verificación de imágenes | Publicación y admisión | Apache 2.0 |
hadolint es el más barato de adoptar y el que más problemas evita, porque actúa antes de construir nada. Sobre una versión temprana del Dockerfile de la API:
docker run --rm -i hadolint/hadolint < Dockerfile
# Dockerfile:1 DL3006 warning: Always tag the version of an image explicitly
# Dockerfile:4 DL3009 info: Delete the apt-get lists after installing something
# Dockerfile:7 DL3016 warning: Pin versions in npm. npm install <package>@<version>
# Dockerfile:9 DL3020 error: Use COPY instead of ADD for files and folders
# Dockerfile:12 DL3002 warning: Last USER should not be root
# Dockerfile:14 SC2086 info: Double quote to prevent globbing and word splittingSeis avisos que son exactamente el temario de los módulos 2 y 5: fijar la base, limpiar cachés de paquetes, fijar versiones, COPY en vez de ADD, no terminar como root y entrecomillar variables en RUN. Sobre el Dockerfile actual de aurora-api:2.0.0 no sale ninguno, porque la base está fijada por digest y el USER final no es root. Integrarlo cuesta cuatro líneas:
# .github/workflows/ci.yml (fragmento)
- name: Lint del Dockerfile
uses: hadolint/hadolint-action@v3
with: { dockerfile: aurora-api/Dockerfile, failure-threshold: warning }Falco es el que cubre el hueco que nadie más cubre: los demás analizan artefactos parados; Falco vigila syscalls en vivo y alerta cuando un contenedor hace algo que no debería —abrir una shell dentro de aurora-api, escribir en /etc, conectarse a una IP desconocida—. Es la última línea cuando la vulnerabilidad no estaba en ninguna base de datos.
# Regla de Falco a medida para Aurora Libros
- rule: Shell en aurora-api
desc: La API de producción nunca debe abrir una shell
condition: spawned_process and container.image.repository contains "aurora-api"
and proc.name in (sh, bash, ash)
output: "Shell inesperada en aurora-api (usuario=%user.name cmd=%proc.cmdline)"
priority: CRITICALSobre las tres primeras de la tabla: no hace falta elegir una sola. Trivy en el pipeline como puerta que falla el build, Scout en el escritorio para la comprobación temprana, y Grype como segunda opinión cuando un resultado sorprende. Cada escáner usa fuentes distintas y no coinciden siempre.
- Registros propios
| Opción | Fuerte en | Coste | Cuándo |
|---|---|---|---|
registry:2 |
Trivial de levantar, oficial | Ninguno | Laboratorio, caché local |
| Zot | Solo OCI, ligero, firma y búsqueda | Bajo | Registro propio moderno |
| Harbor | Usuarios, políticas, escaneo, replicación, cuotas | Alto: es una plataforma | Empresa con gobernanza |
| ghcr.io y similares | Cero mantenimiento | Suscripción | El caso por defecto |
Un uso que compensa casi siempre y casi nadie aplica: un espejo de Docker Hub en la red local. Ahorra ancho de banda, esquiva los límites de descarga y mantiene el pipeline funcionando cuando Hub tiene una mala tarde.
# compose.registro.yaml — espejo de solo lectura de Docker Hub
services:
espejo:
image: registry:2
ports: ["5000:5000"]
environment:
REGISTRY_PROXY_REMOTEURL: https://registry-1.docker.io
volumes: ["registro-datos:/var/lib/registry"]
volumes: { registro-datos: }Harbor solo se justifica cuando hay varios equipos, políticas de retención, replicación entre regiones y necesidad de auditoría. Es una plataforma con base de datos, Redis y varios componentes: alguien tiene que mantenerla.
- Desarrollo y pruebas
| Herramienta | Para qué | Encaja con |
|---|---|---|
| Testcontainers | Levantar dependencias reales desde el test | Cualquier lenguaje; CI |
| Tilt | Bucle interno con Kubernetes: guarda y ves el cambio | Equipos con clúster |
| Skaffold | Build+deploy continuo hacia K8s | Similar, más opinado |
| devcontainer | Entorno de desarrollo reproducible en el editor | VS Code y compatibles |
docker init |
Andamiaje inicial de Dockerfile y Compose | Proyectos nuevos |
Testcontainers es la que más cambia la vida y la que menos se conoce. En 06-02 montaste servicios de PostgreSQL y Redis en el workflow de CI; Testcontainers hace que el propio test los levante, con lo que las pruebas se ejecutan igual en tu portátil y en el pipeline sin configuración duplicada:
// aurora-api/test/libros.test.js — Node 22, node:test nativo
import { test, before, after } from 'node:test';
import assert from 'node:assert/strict';
import { PostgreSqlContainer } from '@testcontainers/postgresql';
import { GenericContainer, Wait } from 'testcontainers';
import { crearApp } from '../src/app.js';
let pg, redis, app;
before(async () => {
pg = await new PostgreSqlContainer('postgres:16-alpine')
.withDatabase('aurora_libros').withUsername('aurora').withPassword('secreto')
.withCopyFilesToContainer([{ source: './db/init.sql',
target: '/docker-entrypoint-initdb.d/init.sql' }])
.start();
redis = await new GenericContainer('redis:7-alpine')
.withExposedPorts(6379)
.withWaitStrategy(Wait.forLogMessage('Ready to accept connections'))
.start();
app = crearApp({
dbUrl: pg.getConnectionUri(),
redisUrl: `redis://${redis.getHost()}:${redis.getMappedPort(6379)}`
});
});
after(async () => { await pg.stop(); await redis.stop(); });
test('GET /libros devuelve los nueve títulos del catálogo', async () => {
const res = await app.inject({ method: 'GET', url: '/libros' });
assert.equal(res.statusCode, 200);
const { libros } = res.json();
assert.equal(libros.length, 9);
assert.ok(libros.some(l => l.titulo === 'Rayuela'));
});
test('la segunda lectura de un libro viene de la caché', async () => {
await app.inject({ method: 'GET', url: '/libros/3' }); // calienta
const res = await app.inject({ method: 'GET', url: '/libros/3' });
assert.equal(res.json().origen, 'cache'); // cache-aside
});El segundo test es el que justifica la herramienta: comprobar que el cache-aside devuelve origen: cache exige un Redis de verdad. Con un doble de prueba habrías validado tu propio simulacro, no el comportamiento real. Testcontainers necesita un daemon accesible; en CI ya lo tienes, y funciona igual contra Podman con el socket compatible (07-05).
Tilt y Skaffold resuelven otro problema: cuando desarrollas contra un clúster, el ciclo build-push-deploy son minutos. Ambos lo reducen a segundos sincronizando ficheros dentro del Pod. Solo compensan si tu bucle interno es Kubernetes; si desarrollas con docker compose watch (04-07), no los necesitas.
- Construcción sin Dockerfile
| Herramienta | Cómo construye | Fuerte en | Límite |
|---|---|---|---|
Buildpacks (pack) |
Detecta el lenguaje y aplica buildpacks | Cero Dockerfile, parches de base automáticos | Imágenes mayores, menos control |
| Jib | Plugin de Maven/Gradle, sin daemon | Java: rapidísimo, capas óptimas | Solo JVM |
ko |
Compila y empaqueta binarios Go | Go: segundos, imagen mínima | Solo Go |
| Nixpacks | Deduce el entorno con Nix | Muy automático, usado por PaaS | Menos conocido, reproducible pero opaco |
pack build aurora-api:pack --builder paketobuildpacks/builder-jammy-base
docker images aurora-api
# aurora-api pack 412MB ← frente a los 104 MB de tu multietapa
# aurora-api 2.0.0 104MBAhí está el compromiso en una línea. Buildpacks te ahorra escribir y mantener el Dockerfile, y a cambio produce una imagen cuatro veces mayor. ¿Cuándo compensa?
| Compensa no escribir Dockerfile | Compensa escribirlo |
|---|---|
| Muchos servicios pequeños y parecidos | Pocas imágenes, muy afinadas |
| Nadie del equipo domina Docker | Hay conocimiento (tú ya lo tienes) |
| Se valora el parcheo automático de la base | Se controla la base por digest |
| El tamaño y el arranque dan igual | El tamaño importa (edge, escalado) |
| Plataforma interna que estandariza | Requisitos de seguridad específicos |
Para Aurora Libros, con una imagen bien optimizada, multiarquitectura y firmada, no compensa. Para una plataforma interna con cuarenta microservicios, probablemente sí.
- Limpieza y mantenimiento
El disco se llena solo. Automatízalo antes de que te despierte una alerta:
# /etc/cron.weekly/docker-limpieza — en cada host de Aurora Libros
#!/bin/sh
docker image prune -a --filter "until=168h" -f # imágenes de más de 7 días
docker builder prune --filter "until=168h" -f # caché de BuildKit
docker container prune --filter "until=24h" -f # contenedores parados
# NUNCA --volumes aquí: aurora-datos vive en un volumen
docker system df >> /var/log/docker-limpieza.log| Herramienta | Qué aporta | Cuidado |
|---|---|---|
docker system prune |
Nativo, suficiente para casi todo | --volumes destruye datos |
docker builder prune --keep-storage |
Techo fijo a la caché de BuildKit | — |
docker-gc / docker-cleanup |
Políticas más finas, contenedores | Proyectos con mantenimiento desigual |
| Política de retención del registro | Evita miles de etiquetas viejas | Configúrala en ghcr.io o Harbor |
El filtro until es la diferencia entre una limpieza segura y un pull completo la mañana siguiente. Y la línea del comentario es la más importante del script.
- Red y diagnóstico
netshoot es una imagen con todas las herramientas de red que tus imágenes de producción, correctamente, no llevan: dig, curl, tcpdump, ss, iperf3, nmap.
# Depurar el DNS interno y la conectividad de Aurora Libros (05-01)
docker run --rm -it --network aurora-libros_trasera nicolaka/netshoot \
sh -c 'dig +short aurora-db; nc -zv aurora-cache 6379'
# Meterse en el namespace de red del propio contenedor de la API
docker run --rm -it --network container:aurora-api nicolaka/netshoot \
ss -tnpLa segunda forma es la potente: --network container: comparte el namespace de red de aurora-api (05-07), así que ves exactamente sus conexiones y sus puertos sin instalar nada dentro ni modificar la imagen. Es lo mismo que hace docker debug, con una imagen pública y sin suscripción.
dockviz dibuja el árbol de imágenes y contenedores; útil para entender un host heredado con cuarenta imágenes de origen desconocido.
- Criterios para adoptar una herramienta
| Criterio | Pregunta concreta | Señal de alarma |
|---|---|---|
| Mantenimiento | ¿Commits e issues cerradas en los últimos 6 meses? | Último release hace dos años |
| Licencia | ¿Es compatible con el uso comercial que le vas a dar? | GPL en algo que redistribuyes; «gratis por ahora» |
| Procedencia | ¿Quién la publica? ¿La imagen está firmada? | Imagen de un usuario anónimo de Hub |
| Criticidad | Si desaparece mañana, ¿se para el despliegue? | Un binario de un solo autor en la ruta crítica |
| Comprensión | ¿Sabe el equipo qué hace por dentro? | «La copié de un blog y funciona» |
| Salida | ¿Cuánto cuesta quitarla? | Formatos propietarios, sin exportación |
| Superficie | ¿Necesita el socket de Docker o privilegios? | Sí, y encima expuesta a la red |
| Solapamiento | ¿Hace algo que ya haces con lo que tienes? | Tres escáneres que dicen lo mismo |
Dos reglas de oro. Primera: cuanto más cerca del pipeline, más exigente. Una TUI local que se abandone se sustituye en cinco minutos; un plugin que firma tus imágenes y deja de recibir parches es un problema de seguridad y de despliegue a la vez. Segunda: valida con el responsable de seguridad cualquier herramienta que entre en la cadena de construcción o que necesite el socket del daemon, y comprueba su licencia con quien corresponda antes de integrarla en un producto comercial. Añadir una dependencia al pipeline es una decisión de arquitectura, no una preferencia personal.
- El kit mínimo recomendado
| Perfil | Imprescindible | Recomendable | Prescindible |
|---|---|---|---|
| Aprendiendo | lazydocker, dive |
hadolint |
Portainer, Tilt |
| Desarrollador de aplicación | hadolint, dive, Testcontainers |
lazydocker, netshoot |
Buildpacks, Falco |
| DevOps / plataforma | Trivy, Cosign, hadolint, netshoot |
Harbor o Zot, Falco, Portainer | slim, Nixpacks |
| Operando en producción | Trivy, Falco, Docker Bench, ctop |
Portainer, dockviz |
dive, docker init |
| Equipo con Kubernetes | Trivy, Cosign, Tilt o Skaffold | k9s, Harbor |
Dockge, lazydocker |
Si solo pudieras adoptar tres para el resto de tu carrera: hadolint (evita errores antes de construir), Trivy (impide publicar algo vulnerable) y Testcontainers (hace que tus pruebas signifiquen algo). Las tres son de código abierto, están mantenidas y no atan a nadie.
Errores Comunes y Consejos
- Exponer Portainer o Dockge a Internet. Controlan el socket del daemon: quien entra es root en el host. VPN o red interna, siempre, y con autenticación fuerte.
- Instalar un plugin de logging sin probar el fallo del destino. Si el destino no responde y el modo es bloqueante, los contenedores no arrancan. Prueba con Loki caído a propósito.
- Confiar solo en
slim. Adelgaza observando una ejecución; lo que no se ejecutó, desaparece. Sin una batería de pruebas completa detrás, es una bomba de relojería. - Acumular tres escáneres que dicen lo mismo. Uno como puerta del pipeline, otro como segunda opinión puntual. Más solo genera ruido que nadie lee.
- Añadir
--volumesa la limpieza programada. Es la forma más rápida de perderaurora-datosun domingo por la noche. - Adoptar una herramienta porque salió en una conferencia. Aplica la tabla de §12 antes: mantenimiento, licencia, criticidad y coste de salida.
- Consejo: mete
hadolinten el pipeline hoy mismo. Cuesta cuatro líneas y evita media docena de malas prácticas por Dockerfile. - Consejo: guarda el plugin
docker auroraen el repositorio. Los atajos del equipo dejan de vivir en el historial de bash de cada uno. - Consejo: aprende
netshootcon--network container:. Resuelve el 90 % de las depuraciones de red sin tocar la imagen.
Ejercicios
Ejercicio 1 — Amplía docker aurora. Parte del plugin de §2 y añádele tres subcomandos: escanear, que ejecute Trivy sobre la imagen local de la API y falle si hay vulnerabilidades críticas; pesar, que muestre el tamaño de la imagen y ejecute dive --ci con un umbral de eficiencia del 95 %; y red, que lance netshoot en el namespace de red de aurora-api y compruebe que resuelve aurora-db y alcanza aurora-cache. El plugin debe seguir funcionando sin instalar nada más que Docker.
Ejercicio 2 — Lint y corrección de un Dockerfile. Escribe a propósito un Dockerfile malo para aurora-api (base sin etiqueta, apt-get sin limpiar, ADD en lugar de COPY, npm install sin versión fijada, sin USER), pásale hadolint y anota cada código de aviso. Corrígelos uno a uno hasta que el lint quede limpio, explicando en un comentario qué problema real evita cada corrección. Añade después la comprobación al pipeline con umbral warning.
Ejercicio 3 — Evaluación de adopción. Un compañero propone añadir al pipeline una herramienta que optimiza automáticamente las imágenes antes de publicarlas. La publica un desarrollador individual en Docker Hub, tiene 900 estrellas en GitHub, el último commit es de hace once meses y la imagen no está firmada. Aplica la tabla de criterios de §12 y escribe una respuesta razonada: tu recomendación, los tres riesgos concretos que identificas, y qué condiciones tendrían que cumplirse para que la aceptaras.
Soluciones
Solución 1.
# Añadir dentro del case de ~/.docker/cli-plugins/docker-aurora
escanear)
IMG="${2:-ghcr.io/auroralibros/aurora-api:2.0.0}"
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
-v "$HOME/.cache/trivy:/root/.cache/trivy" \
aquasec/trivy:latest image --severity CRITICAL,HIGH \
--exit-code 1 --ignore-unfixed "$IMG"
;;
pesar)
IMG="${2:-ghcr.io/auroralibros/aurora-api:2.0.0}"
docker image inspect "$IMG" --format 'Tamaño: {{.Size}} bytes ({{len .RootFS.Layers}} capas)'
docker run --rm -e CI=true -v /var/run/docker.sock:/var/run/docker.sock \
wagoodman/dive:latest --ci --lowestEfficiency 0.95 "$IMG"
;;
red)
docker run --rm --network container:aurora-api nicolaka/netshoot sh -c '
echo "DNS aurora-db -> $(dig +short aurora-db)"
nc -zv aurora-cache 6379 && echo "cache alcanzable"
ss -tnp | head -20'
;;Tres decisiones importan más que el resto. El montaje del socket es imprescindible porque tanto Trivy como dive inspeccionan imágenes locales del daemon, no remotas; montar ~/.cache/trivy evita descargar la base de vulnerabilidades entera en cada ejecución, que es lo que hace que la primera vez tarde un minuto y las siguientes tres segundos. El --exit-code 1 convierte el escaneo en una puerta: sin él, Trivy informa y devuelve 0, y el pipeline pasaría con vulnerabilidades críticas. Y --ignore-unfixed evita el ruido de fallos que aún no tienen parche disponible, que no puedes accionar. En red, --network container:aurora-api entra en el namespace de red del contenedor real (05-07), de modo que lo que ves es su resolución DNS y sus conexiones, no las de una red cualquiera.
Solución 2. Dockerfile malo y los códigos que dispara:
FROM node # DL3006: sin etiqueta ni digest
RUN apt-get update && apt-get install -y curl # DL3009/DL3015: sin limpiar listas
ADD . /app # DL3020: ADD para ficheros locales
WORKDIR /app
RUN npm install # DL3016: sin versiones fijadas
CMD npm start # DL3025: CMD en forma shell| Código | Aviso | Problema real que evita |
|---|---|---|
| DL3006 | Etiqueta la imagen base | node es latest: el build no es reproducible (05-04) |
| DL3009 | Borra las listas de apt | ~40 MB de basura permanente en la capa |
| DL3015 | Usa --no-install-recommends |
Paquetes que nadie pidió, más superficie de ataque |
| DL3020 | COPY en vez de ADD |
ADD descomprime y descarga URLs: comportamiento inesperado |
| DL3016 | Fija versiones de npm | Un build hoy y otro mañana instalan cosas distintas |
| DL3025 | CMD en forma exec |
En forma shell, el proceso no recibe SIGTERM: adiós apagado ordenado (06-01) |
| DL3002 | El último USER no debe ser root |
Root en el contenedor es root en el kernel (05-03) |
Versión corregida:
FROM node:22-alpine@sha256:1f8c... AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev # reproducible desde el lockfile
FROM node:22-alpine@sha256:1f8c...
WORKDIR /app
COPY --from=build /app/node_modules ./node_modules
COPY --chown=node:node src ./src
USER node
CMD ["node", "src/index.js"] # forma exec: PID 1 recibe las señalesEl aviso que más se subestima es DL3025: con CMD npm start, PID 1 es un shell que no propaga SIGTERM, y todo el apagado ordenado que montaste en 06-01 deja de funcionar sin que ningún test lo detecte. Se manifiesta como peticiones cortadas durante los despliegues, y cuesta días encontrarlo si no sabes dónde mirar.
Solución 3. Recomendación: no adoptarla en el pipeline en su estado actual.
| Criterio (§12) | Evaluación |
|---|---|
| Mantenimiento | Último commit hace 11 meses: si aparece un fallo, nadie lo arregla |
| Procedencia | Autor individual, imagen sin firmar en Hub: no puedes verificar qué ejecutas |
| Criticidad | Estaría en la ruta de publicación: si falla, no se despliega |
| Licencia | Sin comprobar en el enunciado; hay que verificarla antes de nada |
| Comprensión | «Optimiza automáticamente» es una caja negra sobre el artefacto que va a producción |
Los tres riesgos concretos: (1) cadena de suministro —una imagen sin firmar de un autor anónimo, ejecutándose en CI con acceso a tus imágenes y probablemente al socket, es exactamente el vector que Cosign y las attestations de 06-02 vinieron a cerrar—; (2) rotura silenciosa, porque una optimización automática puede eliminar un fichero que solo se usa en una ruta poco frecuente, y lo descubrirías en producción; (3) abandono, ya que once meses sin commits en una herramienta que toca el artefacto final significa que el día que rompa con una versión nueva de BuildKit, el despliegue se para y el arreglo es tuyo.
Condiciones para aceptarla: que se ejecute fuera de la ruta crítica, como informe comparativo y no como paso que modifica la imagen publicada; que la imagen esté firmada y fijada por digest; que exista una batería de pruebas de integración que valide la imagen optimizada antes de publicarla; y que el responsable de seguridad valide la licencia y la procedencia. Contrapropuesta razonable: el objetivo del compañero —imágenes más pequeñas— ya está cubierto por la multietapa y dive --ci con umbral, que son parte del pipeline, no dependen de terceros y ya dan un 98 % de eficiencia sobre aurora-api:2.0.0. La conversación no es «no», es «este problema ya está resuelto».
Conclusión
Ya tienes el equipamiento que rodea a Docker, y más importante, el criterio para elegirlo. Entiendes los tres niveles de extensibilidad y su asimetría de riesgo: en la CLI experimenta libremente, en el daemon sé conservador, porque un plugin de logging bloqueado impide arrancar contenedores. Y has construido tu propio docker aurora con cuarenta líneas de bash, descubriendo que el mecanismo de docker compose y docker buildx es simplemente un ejecutable en el PATH que responde a docker-cli-plugin-metadata.
Del recorrido por categorías te llevas selección, no catálogo: lazydocker y ctop para ver la flota sin teclear, Portainer solo cuando hay varias personas y varios hosts, Dockge cuando lo que gestionas son pilas de Compose; dive confirmando el 98 % de eficiencia que ganaste en 05-04 y sirviendo de puerta en CI con --ci --lowestEfficiency, container-diff para auditar qué cambió entre versiones y slim con su advertencia seria; hadolint señalando en seis avisos todo el temario de los módulos 2 y 5 —incluido el DL3025 que rompe el apagado ordenado sin que ningún test lo note—, Trivy como puerta, Scout como comprobación temprana y Falco vigilando syscalls cuando la vulnerabilidad no estaba en ninguna base de datos; el espejo de Docker Hub con registry:2 que casi nadie monta y casi siempre compensa; Testcontainers levantando PostgreSQL y Redis desde el propio test para comprobar que el cache-aside devuelve origen: cache de verdad; Buildpacks con su compromiso medido —412 MB frente a tus 104— y la tabla de cuándo compensa no escribir un Dockerfile; la limpieza programada con until y sin --volumes; y netshoot con --network container: entrando en el namespace de red de aurora-api sin tocar la imagen.
Y te llevas los criterios de adopción, que valen más que cualquier lista de herramientas: mantenimiento activo, licencia compatible, procedencia verificable, criticidad en el pipeline, comprensión real del equipo, coste de salida y superficie de ataque añadida. Con las dos reglas de oro: cuanto más cerca del pipeline, más exigente, y valida con el responsable de seguridad —y con quien lleve las licencias— cualquier herramienta que entre en la cadena de construcción o pida el socket del daemon. Si tuvieras que quedarte con tres para el resto de tu carrera: hadolint, Trivy y Testcontainers.
En la lección siguiente damos un paso atrás fundamental: Podman, containerd y el estándar OCI. Vas a descubrir por qué las imágenes que llevas construyendo todo el curso no son «de Docker», qué normaliza exactamente la Open Container Initiative, y cómo ghcr.io/auroralibros/aurora-api:2.0.0 se ejecuta igual en Podman, en containerd o en un nodo de Kubernetes sin cambiar un solo byte.
Docker: De Principiante a Avanzado
Módulo 1: Introducción a Docker
- ¿Qué es Docker?
- Instalando Docker
- Arquitectura de Docker
- Comandos Básicos de Docker
- Entendiendo las Imágenes de Docker
- Creando tu Primer Contenedor Docker
- El Proyecto del Curso: la Plataforma Aurora Libros
Módulo 2: Trabajando con Imágenes Docker
- Docker Hub y Repositorios
- Construyendo Imágenes Docker
- Conceptos Básicos de Dockerfile
- Instrucciones Avanzadas del Dockerfile
- Gestionando Imágenes Docker
- Etiquetado y Publicación de Imágenes
Módulo 3: Contenedores Docker
- Ejecutando Contenedores
- Ciclo de Vida del Contenedor
- Gestionando Contenedores
- Inspección y Depuración de Contenedores
- Redes en Docker
- Persistencia de Datos con Volúmenes
- Límites de Recursos y Políticas de Reinicio
Módulo 4: Docker Compose
- Introducción a Docker Compose
- Definiendo Servicios en Docker Compose
- Comandos de Docker Compose
- Aplicaciones Multi-Contenedor
- Variables de Entorno en Docker Compose
- Perfiles, Overrides y Múltiples Entornos
- Desarrollo Local con Docker Compose
Módulo 5: Conceptos Avanzados de Docker
- Profundización en Redes Docker
- Opciones de Almacenamiento Docker
- Mejores Prácticas de Seguridad en Docker
- Optimizando Imágenes Docker
- Builds Avanzadas con BuildKit y Buildx
- Registro y Monitoreo en Docker
- El Runtime por Dentro: Namespaces, Cgroups y Capas
Módulo 6: Docker en Producción
- Preparar una Imagen para Producción
- CI/CD con Docker
- Orquestando Contenedores con Docker Swarm
- Introducción a Kubernetes
- Desplegando Contenedores Docker en Kubernetes
- Escalado y Balanceo de Carga
- Estrategias de Despliegue y Rollback
