Antipatrones de Docker | Nicolás Garzón
Texto
Copiar “funciona ahora”
→ privilegio/acoplamiento/estado oculto
→ falla más costosa despuésNo toda excepción es incorrecta. Se vuelve antipatrón cuando se adopta sin amenaza, límites, verificación ni plan para retirarla.
YAML
Copiar image : my- api: latest
el contenido puede cambiar;
no sabes qué se desplegó;
rollback ambiguo;
nodos pueden tener caches distintas;
incident response pierde trazabilidad.
Texto
Copiar my-api@sha256:...Usa tags como conveniencia y digests como identidad.
Texto
Copiar git pull
→ docker build
→ docker compose up
builder y network del servidor cambian;
secretos de build llegan al host;
staging no probó ese digest;
despliegue consume CPU/disco;
rollback depende de reconstruir.
Texto
Copiar CI build/test/push
→ producción pull digest aprobadoTexto
Copiar build staging con config A
build production con config BYa no pruebas el mismo artefacto.
Corrección: construye una vez y configura en runtime. Solo separa imágenes cuando el producto ejecutado es realmente distinto, no por variables ordinarias.
Bash
Copiar docker exec -it api sh
apt install .. .
vi /app/file.js
cambio no versionado;
desaparece al recrear;
no se replica;
evidencia alterada;
deriva entre instancias.
Corrección: corrige source/Dockerfile, construye nuevo digest, prueba y despliega.
Durante incident response, usa debug tooling externo o image debug controlada. No conviertas el runtime en mascota mutable.
Root interno tiene más capacidad frente a archivos, capabilities y errores de configuración.
docker
Copiar USER 10001:10001Acompaña con ownership explícito y pruebas. No uses root para ocultar permisos mal diseñados.
Bash
Copiar docker run --privileged appPuede ampliar devices, capabilities y desactivar defensas.
identifica el recurso exacto;
concede capability/device/mount mínimo;
revisa seccomp/MAC;
aísla el workload si el privilegio es intrínseco.
“Solo funciona privileged” es el inicio del diagnóstico, no la solución.
Es una capability extremadamente amplia y suele introducir un poder cercano a múltiples privilegios administrativos.
Corrección: rediseña la operación, usa helper aislado o concede la capacidad específica cuando exista.
YAML
Copiar volumes :
- /var/run/docker.sock: /var/run/docker.sockNormalmente entrega control administrativo del daemon/host.
elimina la necesidad;
usa API proxy con operaciones limitadas;
builder remoto;
runner efímero;
rootless BuildKit;
plataforma específica.
Non-root dentro del container no neutraliza el socket.
docker
Copiar COPY .env .env
ARG TOKEN
ENV TOKEN=$TOKENPueden quedar en layers, metadata, cache, registry y equipos que hicieron pull.
BuildKit secret mounts para build;
secret files/manager para runtime;
.dockerignore;
rotación inmediata si se filtraron.
Borrar el archivo en una capa posterior no lo elimina de la capa anterior.
O registrar request headers y connection strings.
Corrección: allowlist de campos, redacción y errores que indiquen el nombre del secret sin contenido.
docker
Copiar COPY . .
RUN npm ciCualquier cambio invalida cache y el context puede incluir secretos/ruido.
docker
Copiar COPY package.json package-lock.json ./
RUN npm ci
COPY src ./srcAcompaña con .dockerignore.
context lento;
secrets enviados al builder;
cache invalidada;
node_modules del host;
.git innecesario;
outputs recursivos.
Corrección: allowlist mental del build context y exclusiones revisadas.
Una image de una etapa conserva compiler, tests, dev dependencies y source.
Corrección: multi-stage y runtime mínimo compatible.
No persigas tamaño a costa de certificados, librerías o diagnóstico. “Mínimo” significa necesario y operable.
Musl y paquetes disponibles pueden romper módulos nativos o aumentar tiempo de compilación.
Corrección: evalúa compatibilidad, CVEs, rendimiento, soporte y operación. slim puede ser mejor aunque pese más.
macOS/Windows/amd64/arm64 y libc pueden diferir.
Corrección: instala dentro del build para la plataforma objetivo. En desarrollo, conserva dependencias Linux en volume.
docker
Copiar CMD ["npm", "start"]Puede añadir proceso intermediario y señales menos claras.
docker
Copiar CMD ["node", "dist/server.js"]Si usas script, garantiza exec y shutdown correcto.
requests cortadas;
jobs duplicados;
pools sin cerrar;
SIGKILL por timeout.
Corrección: exec form, handlers idempotentes, draining y prueba con docker stop.
Ejecutar SSH, cron, web server, database y app bajo un supervisor gigante crea:
lifecycle acoplado;
health ambiguo;
escalado conjunto;
logs mezclados;
privilegios amplios.
Corrección: separa responsabilidades por service cuando tienen lifecycle, escala o seguridad distintos.
Excepción: appliance o entorno edge puede justificar múltiples procesos, pero necesita init/supervisión y modelo explícito.
El principio busca una responsabilidad, no prohibir workers hijos legítimos.
Un servidor puede crear threads/processes. Lo importante es:
PID 1 los supervise/recolecte;
señales correctas;
health coherente;
límites.
Texto
Copiar /app/uploads
/var/lib/postgresql/dataAl recrear, desaparecen. Además crece la capa y dificulta backup.
volume;
object storage;
database externa;
tmpfs para temporales.
Un volume local puede perderse con el host, corrupción o eliminación.
Corrección: backup externo consistente + restore probado + RPO/RTO.
Dificultan ownership, inventario y restore.
Corrección: named volumes con labels y documentación.
No crea cluster y puede corromper datos.
Corrección: replicación/topología soportada por el motor o servicio administrado.
Texto
Copiar postgres:14 → postgres:17El formato puede ser incompatible.
Corrección: procedimiento oficial, backup/restore, ensayo y rollback.
Suelen correr solo con directorio vacío.
Corrección: migrations versionadas y job coordinado.
Produce carreras, locks y fallos de rollout.
Corrección: job único y compatibilidad expand/contract.
YAML
Copiar ports :
- "5432:5432"
- "6379:6379" Amplía superficie y confunde comunicación interna con acceso del host.
Corrección: networks internas; publica solo proxy/puntos de entrada necesarios y fija interfaz.
Corrección: service DNS como db:5432.
Corrección: DNS, aliases y reconexión.
Proxy, API y database pueden alcanzarse sin necesidad.
Corrección: modela edge, backend, data, queue según flujos. Network isolation no reemplaza auth.
Bash
Copiar docker run -p 3000 :3000 apiPuede exponer externamente según host/firewall.
Bash
Copiar docker run -p 127.0 .0.1:3000:3000 apicuando solo proxy/local debe acceder. Verifica desde otra máquina.
Reduce aislamiento, crea conflictos de puertos y cambia port publishing.
Corrección: bridge user-defined salvo requisito medido.
Tiempo fijo no observa condición real y falla en máquinas lentas.
Corrección: health/readiness + retries con backoff y timeout.
Solo coordina parte del arranque. La dependencia puede caer después.
Corrección: reconexión, retries, circuit breaking y degradación.
Si un proveedor externo cae, todas las instancias quedan unhealthy y reinician.
Corrección: separa liveness, readiness y señales de dependencia. Checks baratos y sin efectos secundarios.
Aumenta paquetes y superficie.
Corrección: usa runtime existente o binary pequeño. A veces curl sí está justificado; mide el trade-off.
Un leak o fork bomb domina el host.
Corrección: memory, CPU y PIDs medidos, más backpressure.
Produce OOM o desperdicio.
Corrección: load tests, RSS/p99, capacidad global y margen.
No deja espacio para buffers, native addons y stacks.
Corrección: margen y métricas de RSS/GC.
Puede crear loops y no corregir causa.
Corrección: comprende la plataforma, alerta/retira tráfico y distingue bugs permanentes de fallos transitorios.
Se pierden al recrear y llenan writable layer.
Corrección: stdout/stderr, logging driver, rotación y centralización.
Disk full afecta todos los services.
Corrección: límites locales aunque exista collector remoto.
Filtra PII/secrets y aumenta costes.
Corrección: logging estructurado con allowlist y IDs de correlación.
Crea nombre global, colisiones y dificulta escalado.
Corrección: service name de Compose y comandos docker compose exec/logs.
Corrección: base estable + overrides pequeños/profiles y docker compose config en CI.
restart no recrea configuración, image o mounts.
Bash
Copiar docker compose up -d con digest/config nuevos y verificación.
Aparecen volumes “vacíos” y networks duplicadas.
Corrección: project name explícito e inventario.
Elimina named volumes del proyecto.
Corrección: úsalo solo en entornos efímeros intencionales.
Puede eliminar datos, cache valiosa, evidencia y rollback.
Corrección: políticas por categoría, labels, TTL e inventario.
Compose en un Engine no reschedulea si el host muere.
Corrección: acepta RTO single-host o adopta scheduler/plataforma multi-host.
Añade control plane, networking, RBAC, upgrades y debugging distribuido.
Corrección: usa la plataforma mínima que satisface SLO y coste sostenible.
Rescheduling no recupera volume local ni diseña replication.
Corrección: storage/topología propia del sistema stateful.
Publicar manifest ARM64 no demuestra funcionamiento.
Corrección: smoke, integration y scan por manifest, idealmente hardware real.
Rompe frontera de supply chain.
Corrección: scopes por confianza, runners efímeros y build correcto con cache vacía.
Scanner no encuentra bugs lógicos ni garantiza exploitability.
Corrección: SBOM, provenance, firma, threat modeling, tests y runtime hardening.
La reproducibilidad congela también vulnerabilidades.
Corrección: proceso automatizado de rebuild/upgrade probado.
Corrección: firma/verifica subject digest y provenance.
Se pierden logs, state y causa original.
Corrección: inspect/logs/events/stats primero, salvo mitigación urgente.
Elimina fronteras y oculta ownership.
Corrección: UID/GID, groups, mount mode, SELinux/AppArmor y rootless mapping.
Editar /var/lib/docker puede corromper metadata y snapshots.
Corrección: comandos soportados, backup y procedimientos de migración.
¿qué problema exacto resuelve?;
¿qué privilegio/acoplamiento añade?;
¿qué ocurre al recrear?;
¿qué ocurre al perder el host?;
¿cómo se actualiza?;
¿cómo se observa?;
¿cómo se revierte?;
¿cómo se prueba?;
¿cuál es la alternativa mínima?;
¿cuándo retiraremos la excepción?
YAML
Copiar services :
api :
build : .
container_name : api
privileged : true
ports :
- "3000:3000"
- "5432:5432"
volumes :
- .: /app
- /var/run/docker.sock: /var/run/docker.sock
environment :
DATABASE_PASSWORD : secretYAML
Copiar services :
api :
image : registry.example.com/api@sha256: ...
user : "10001:10001"
read_only : true
cap_drop : [ ALL]
security_opt :
- no- new- privileges: true
ports :
- "127.0.0.1:3000:3000"
secrets :
- db_password
networks : [ backend, data]
db :
image : postgres: 17@sha256: ...
volumes :
- db- data: /var/lib/postgresql/data
networks : [ data]
secrets :
db_password :
file : /run/secure/db- password
volumes :
db-data :
networks :
backend :
data :
internal : true El refactor elimina socket, privileged, bind de source productivo, password en environment, port público de DB y nombre global.
Algunos workloads necesitan:
host networking;
devices;
múltiples procesos;
writable rootfs;
privileged helpers;
bind mounts.
Una excepción sana tiene:
requisito documentado;
scope mínimo;
host aislado cuando corresponde;
amenaza evaluada;
pruebas;
observabilidad;
owner;
fecha de revisión;
alternativa considerada.
Los antipatrones suelen intercambiar comodidad inmediata por riesgo oculto.
Inmutabilidad, reemplazabilidad y configuración externa son el centro del modelo.
Privilegios y mounts pueden anular el aislamiento.
Persistencia, backup y HA son conceptos distintos.
Una excepción puede ser válida si está limitada, probada y revisada.
Pregunta siempre qué pasará al recrear, actualizar, fallar y recuperar.
Comprueba lo aprendido
Audita un Compose con privileged, socket y bind mount del repository.
¿Cuándo múltiples procesos en un container pueden ser aceptables?
Refactoriza un deployment que construye directamente en producción.
Explica por qué latest + restart no es una estrategia de release.
Diseña criterios para aprobar una excepción de seguridad.
Modelo mental completo de Docker , la síntesis que conecta source, build, registry, runtime, estado, seguridad y operación.