Docker pull es dolorosamente lento: soluciones DNS, MTU y proxy que funcionan

¿Te fue útil?

Ejecutas docker pull y avanza como si se estuviera descargando por un módem de marcación de 1999 que además está en llamas. Las capas se quedan en pausa. “Waiting” se queda allí sonriendo. Los pipelines de CI caducan. Alguien dice “debe ser Docker Hub” y sientes que tu alma te abandona.

Las descargas lentas rara vez son “simplemente internet lento”. Normalmente son uno de tres villanos aburridos: DNS que miente o se traba, MTU que hace desaparecer paquetes, o proxies que reescriben la realidad. A veces los tres, apilados como una lasaña corporativa.

Guía de diagnóstico rápido (compruébalo primero)

Esta es la secuencia de “deja de adivinar”. Está optimizada para el mundo real: tienes un terminal, nada de paciencia y un pipeline gritándote.

1) Decide: ¿descarga por red o extracción local?

  • Si la pull se queda atascada en “Downloading” o “Waiting”, piensa en DNS/proxy/MTU/CDN.
  • Si avanza rápido en la descarga y luego se queda en “Extracting” o tu disco se pone al máximo, piensa en driver de almacenamiento / sistema de archivos / IO de disco.

2) Comprueba la latencia y corrección del DNS

  • Ejecuta una consulta contra el nombre exacto del registro (y el resolvedor que uses).
  • Si DNS tarda >100ms de forma consistente, o devuelve IP privadas extrañas, arregla el DNS antes de tocar cualquier otra cosa.

3) Comprueba el MTU de la ruta (la prueba del agujero negro)

  • Si los handshakes TLS o las descargas de capas grandes se quedan colgadas, ejecuta una sonda MTU con “no fragmentar”.
  • Los problemas de MTU se ven como “algunas cosas funcionan, transferencias grandes se congelan”. Clásico.

4) Comprueba el proxy en el entorno y la configuración de proxy del daemon

  • La descoordinación entre las variables de proxy de la shell del usuario y la configuración de proxy del daemon causa fallos parciales y rascarse la cabeza.
  • Si tienes un proxy que inspecciona TLS, espera latencia extra y comportamiento ocasionalmente roto en HTTP/2.

5) Comprueba el comportamiento IPv6 (timeouts por dual-stack)

  • IPv6 roto aparece como pausas largas antes de volver a IPv4.
  • No desactives IPv6 permanentemente a menos que seas consciente de las consecuencias. Mejor arregla la ruta/RA/DNS AAAA primero.

6) Solo entonces: limitación del registro y mirrors

  • Los límites de tasa y la selección de borde del CDN pueden ser reales; no los conviertas en tu primera teoría porque dan confort.
  • Un mirror puede ayudar, pero también añade un nuevo dominio de fallos. Elige con prudencia.

Una cita para mantenerte honesto: idea parafraseada de Richard Feynman: “La realidad debe tener prioridad sobre las relaciones públicas.” En ops, los logs y los trazados de paquetes son la realidad.

Qué hace realmente docker pull en la red

docker pull no es una sola descarga. Es una canalización de pequeñas decisiones:

  1. Resolución de nombres: resolver el nombre del registro (DNS; posiblemente múltiples respuestas A/AAAA).
  2. Conexión TCP: establecer una conexión con un nodo de borde (CDN) o servicio del registro.
  3. Handshake TLS: negociar cifrado, validar certificados, quizá hacer comprobaciones OCSP/CRL.
  4. Autenticación: obtener un token desde un endpoint de auth; a veces redirecciones adicionales.
  5. Obtención del manifiesto: descargar manifiestos JSON, elegir plataforma/arquitectura, localizar capas.
  6. Descargas de capas: peticiones HTTP paralelas con rangos para blobs; reintentos; backoff.
  7. Descompresión y extracción: intensivo en CPU + disco; pesado en metadatos; depende del driver de almacenamiento.
  8. Contabilidad del almacén de contenido: containerd/Docker actualizan metadatos locales; recolección de basura después.

La lentitud se esconde en cualquier paso. DNS puede añadir segundos por nombre. Problemas de MTU pueden colgar registros TLS grandes. Los proxies pueden romper keep-alives para que cada capa pague el coste de establecer la conexión de nuevo. Y el almacenamiento puede convertir “descargado” en “todavía esperando” porque la extracción está bloqueada.

Broma #1: “Siempre es DNS” es gracioso porque es verdad—y porque es lo único que evita que gritemos.

Datos interesantes y breve historia

  • La distribución de imágenes de Docker tomó mucho del pensamiento de Git. Las capas se comportan como commits reutilizables: se tiran una vez, se reutilizan siempre (hasta que no).
  • El protocolo del registro evolucionó. Registry v1 fue depreciado; v2 mejoró la direccionamiento por contenido y permitió mejor caché e integración con CDN.
  • La direccionabilidad por contenido importa. Los registries modernos indexan blobs por digest; la caché funciona porque el mismo digest es el mismo contenido en todas partes.
  • containerd es el caballo de carga. Muchas pulls “de Docker” en sistemas modernos pasan por el almacén de contenido y snapshotters de containerd.
  • Las peticiones HTTP range son comunes. Los blobs grandes pueden descargarse en fragmentos; redes inestables pueden causar fetches parciales repetidos que parecen “lento”.
  • PMTUD ha sido un dolor recurrente desde los 90. Firewalls que descartan ICMP “Fragmentation Needed” todavía rompen cargas de trabajo modernas hoy.
  • Los proxies corporativos cambiaron el perfil de fallos. Inspección TLS, timeouts de inactividad y reescritura de headers causan problemas que parecen “Docker está roto”.
  • Dual-stack IPv6 puede amplificar latencia. Happy Eyeballs ayuda, pero IPv6 roto aún puede añadir segundos por conexión si está mal configurado.
  • La extracción de capas es intensiva en metadatos. Millones de archivos pequeños (comunes en runtimes de lenguajes) castigan filesystems overlay y discos lentos.

Tareas prácticas de diagnóstico (comandos, salidas, decisiones)

Quieres tareas que produzcan evidencia. Aquí hay más de una docena. Cada una incluye: un comando ejecutable, qué significa la salida y qué hacer a continuación.

Tarea 1: Cronometra la pull con logs de depuración

cr0x@server:~$ sudo docker -D pull alpine:3.19
DEBU[0000] pulling image                               image=alpine:3.19
3.19: Pulling from library/alpine
DEBU[0001] resolved tags                               ref=alpine:3.19
DEBU[0002] received manifest                           digest=sha256:...
DEBU[0003] fetching layer                              digest=sha256:...
f56be85fc22e: Downloading [==================>                                ]  1.2MB/3.4MB
DEBU[0015] attempting next endpoint
DEBU[0035] Download complete
DEBU[0036] Extracting
DEBU[0068] Pull complete

Qué significa: La salida de depuración muestra dónde se va el tiempo: resolución, obtención del manifiesto, descarga de blobs, extracción.

Decisión: Si la parada ocurre antes de “fetching layer”, persigue DNS/auth/proxy. Si es “Downloading” con reintentos, persigue MTU/proxy/CDN. Si es “Extracting”, persigue almacenamiento/CPU.

Tarea 2: Confirma versiones del daemon y cliente (cambios de comportamiento)

cr0x@server:~$ docker version
Client: Docker Engine - Community
 Version:           26.1.1
 API version:       1.45
 Go version:        go1.22.2
 OS/Arch:           linux/amd64

Server: Docker Engine - Community
 Engine:
  Version:          26.1.1
  API version:      1.45 (minimum version 1.24)
  Go version:       go1.22.2
  OS/Arch:          linux/amd64
 containerd:
  Version:          1.7.19
 runc:
  Version:          1.1.12

Qué significa: Diferentes versiones de Docker/containerd tienen distintos valores por defecto para redes, concurrencia y comportamiento con registries.

Decisión: Si eres antiguo (o extremadamente nuevo) y ves rarezas, prueba en una versión conocida buena antes de rediseñar la red.

Tarea 3: Identifica qué endpoints de registro se usan

cr0x@server:~$ docker pull --quiet busybox:latest && docker image inspect busybox:latest --format '{{.RepoDigests}}'
sha256:...
[docker.io/library/busybox@sha256:3f2...]

Qué significa: Estás tirando desde Docker Hub (docker.io), que a menudo redirige a endpoints de CDN.

Decisión: Si solo Docker Hub está lento pero otros registries están bien, sospecha enrutamiento del CDN, límites de tasa o una política de proxy específica para esos hostnames.

Tarea 4: Inspecciona ajustes DNS del daemon y la configuración general

cr0x@server:~$ sudo cat /etc/docker/daemon.json
{
  "dns": ["10.10.0.53", "1.1.1.1"],
  "log-level": "info"
}

Qué significa: El daemon de Docker puede usar resolvedores específicos que difieren del resolvedor del host.

Decisión: Si esos servidores DNS son lentos/inaccesibles desde el namespace de red del daemon, arréglalos o elimínalos. Prefiere los resolvedores de caché rápidos y locales de tu organización.

Tarea 5: Mide el tiempo de consulta DNS contra el resolvedor que Docker usa

cr0x@server:~$ dig @10.10.0.53 registry-1.docker.io +stats +tries=1 +time=2
;; ANSWER SECTION:
registry-1.docker.io. 60 IN A 54.87.12.34
registry-1.docker.io. 60 IN A 54.87.98.21

;; Query time: 420 msec
;; SERVER: 10.10.0.53#53(10.10.0.53)
;; WHEN: Tue Jan 02 10:11:07 UTC 2026
;; MSG SIZE  rcvd: 92

Qué significa: 420 ms por consulta es brutal cuando una pull dispara muchas búsquedas (endpoints de auth, servicios de tokens, nombres de CDN).

Decisión: Arregla primero el rendimiento DNS: caché local, ubicación del resolvedor o reducir latencia upstream. No toques MTU todavía.

Tarea 6: Comprueba desastres con search-domain en DNS

cr0x@server:~$ cat /etc/resolv.conf
search corp.example internal.example
nameserver 10.10.0.53
options ndots:5 timeout:2 attempts:3

Qué significa: ndots:5 hace que muchos hostnames se traten como “relativos” primero, disparando múltiples consultas inútiles por cada búsqueda.

Decisión: En entornos donde tiras de muchos registries externos, considera bajar ndots (a menudo a 1–2) o acotar los dominios de búsqueda. Prueba con cuidado; esto puede romper expectativas de nombres internos.

Tarea 7: Verifica si systemd-resolved está en juego (y mal comportándose)

cr0x@server:~$ resolvectl status
Global
       Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
resolv.conf mode: stub
Current DNS Server: 10.10.0.53
       DNS Servers: 10.10.0.53 1.1.1.1

Qué significa: Estás usando un stub resolver; Docker podría leer una IP stub como 127.0.0.53 y eso puede o no funcionar dependiendo del namespace y la configuración.

Decisión: Si los contenedores o el daemon no pueden alcanzar el stub, establece servidores DNS explícitos en daemon.json o ajusta tu configuración de resolvedor.

Tarea 8: Observa intentos de conexión en vivo (IPv6 vs IPv4, reintentos)

cr0x@server:~$ sudo ss -tpn dst :443 | head
ESTAB 0 0 10.20.30.40:51524 54.87.12.34:443 users:(("dockerd",pid=1321,fd=62))
SYN-SENT 0 1 10.20.30.40:51526 2600:1f18:2148:...:443 users:(("dockerd",pid=1321,fd=63))

Qué significa: Docker está intentando IPv6 y queda en SYN-SENT mientras IPv4 funciona. Ese es el “impuesto por tiempo de espera dual-stack”.

Decisión: Arregla el enrutamiento IPv6/la corrección de AAAA en DNS, o temporalmente prefiere IPv4 para el daemon si necesitas una mitigación inmediata.

Tarea 9: Prueba MTU con una sonda ping sin fragmentar

cr0x@server:~$ ping -M do -s 1472 registry-1.docker.io -c 2
PING registry-1.docker.io (54.87.12.34) 1472(1500) bytes of data.
ping: local error: message too long, mtu=1460
ping: local error: message too long, mtu=1460

--- registry-1.docker.io ping statistics ---
2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 1024ms

Qué significa: Tu path MTU es 1460 (típico cuando hay overhead: VPN, túneles, algunas redes de nube). Intentar enviar tramas de 1500 bytes hace que se produzcan agujeros negros o errores.

Decisión: Ajusta el MTU del bridge de Docker para que coincida con el MTU real de la ruta (o ligeramente por debajo), y/o arregla la red para que PMTUD funcione (ICMP “frag needed” debe pasar).

Tarea 10: Confirma MTU de interfaces y overhead de túneles

cr0x@server:~$ ip -br link
lo               UNKNOWN        00:00:00:00:00:00 <LOOPBACK,UP,LOWER_UP>
eth0             UP             02:42:ac:11:00:02 <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500
tun0             UP             00:00:00:00:00:00 <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420
docker0          UP             02:42:8f:01:23:45 <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500

Qué significa: Tu túnel VPN tiene MTU 1420 pero el bridge de Docker está a 1500. Los contenedores pueden emitir paquetes demasiado grandes para la ruta real, y PMTUD podría no salvarte.

Decisión: Alinea el MTU de Docker con el MTU efectivo más pequeño en la ruta de egress.

Tarea 11: Comprueba la configuración de proxy del daemon (systemd drop-in)

cr0x@server:~$ sudo systemctl cat docker | sed -n '1,120p'
# /lib/systemd/system/docker.service
[Service]
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

# /etc/systemd/system/docker.service.d/proxy.conf
[Service]
Environment="HTTP_PROXY=http://proxy.corp.local:3128"
Environment="HTTPS_PROXY=http://proxy.corp.local:3128"
Environment="NO_PROXY=localhost,127.0.0.1,.corp.local"

Qué significa: El daemon de Docker está configurado para usar un proxy. Eso afecta el acceso a registros, la terminación TLS y a veces el rendimiento dramáticamente.

Decisión: Si el proxy es obligatorio, asegúrate de que sea estable y soporte TLS moderno. Si es opcional, intenta saltarlo para los hostnames de registro mediante NO_PROXY para reducir latencia y complejidad.

Tarea 12: Verifica que las variables de proxy de tu shell no te confundan

cr0x@server:~$ env | egrep -i 'http_proxy|https_proxy|no_proxy'
HTTP_PROXY=http://proxy.corp.local:3128
HTTPS_PROXY=http://proxy.corp.local:3128
NO_PROXY=localhost,127.0.0.1

Qué significa: Tus ajustes de shell difieren de los del daemon. Puedes hacer curl bien en tu shell mientras el daemon de Docker toma una ruta diferente—o al revés.

Decisión: Trata la configuración del daemon como autorizada para las pulls. Alinea las variables de proxy entre daemon, runners de CI y el host.

Tarea 13: Prueba el tiempo del handshake TLS a endpoints probables

cr0x@server:~$ time bash -lc 'echo | openssl s_client -connect registry-1.docker.io:443 -servername registry-1.docker.io -brief 2>/dev/null | head -n 3'
CONNECTION ESTABLISHED
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_128_GCM_SHA256

real    0m2.418s
user    0m0.032s
sys     0m0.012s

Qué significa: 2.4 segundos para un handshake es sospechoso a menos que estés en un enlace de alta latencia. Intercepción TLS por proxy e IPv6 roto comúnmente causan esto.

Decisión: Si el handshake es lento, verifica la ruta de resolución DNS, el camino del proxy y los intentos IPv6 antes de culpar a Docker.

Tarea 14: Observa ICMP “frag needed” o retransmisiones durante pulls

cr0x@server:~$ sudo tcpdump -ni eth0 '(icmp and (icmp[0]=3 and icmp[1]=4)) or (tcp and port 443 and (tcp[tcpflags] & (tcp-rst) != 0))' -c 20
tcpdump: listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
IP 10.20.30.40 > 54.87.12.34: ICMP 10.20.30.40 unreachable - need to frag (mtu 1460), length 556
IP 10.20.30.40.51524 > 54.87.12.34.443: Flags [R], seq 123, win 0, length 0

Qué significa: Ver “need to frag” en realidad es bueno: PMTUD está funcionando y te dice el MTU. Ver RSTs podría indicar resets de proxy, timeouts de inactividad o comportamiento de un middlebox.

Decisión: Si ves “need to frag” pero las descargas aún cuelgan, algo sigue bloqueando ICMP en parte de la ruta. Si ves resets, céntrate en políticas de proxy/firewall y timers TCP de inactividad.

Tarea 15: Comprueba el cuello de botella de extracción: disco y CPU

cr0x@server:~$ sudo iostat -xz 1 3
Linux 6.5.0 (server)  01/02/2026  _x86_64_  (8 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          12.00    0.00    6.00   42.00    0.00   40.00

Device            r/s     rkB/s   rrqm/s  %rrqm r_await rareq-sz     w/s     wkB/s   w_await  aqu-sz  %util
nvme0n1          5.00    80.00     0.00   0.00   10.00    16.00  450.00  62000.00   35.00   18.00  98.00

Qué significa: Alto iowait y utilización de disco cercana al 100% significa que la extracción está limitada por almacenamiento. La “lentitud” de la pull no es tu red.

Decisión: Aborda la elección de filesystem/snapshotter, rendimiento del disco y composición de la imagen (demasiados archivos pequeños). Considera mover Docker root a almacenamiento más rápido.

Modos de fallo DNS que hacen que las pulls sean “lentas”

El DNS es el primer dominó. DNS lento se parece a descargas lentas, porque cada nuevo endpoint o redirección empieza con una búsqueda. Y los registries no escatiman en hostnames: servicios de token, endpoints de registro, bordes de CDN, a veces respuestas basadas en geolocalización.

Patrones comunes de DNS que perjudican las pulls

  • Resolvedores recursivos lentos: servidores DNS centralizados a través de una WAN; cada consulta paga RTT.
  • Caché roto: resolvedores que no cachean eficazmente (mala configuración, caché pequeña, política de flush agresiva).
  • Amplificación por search domain: ndots y listas largas de búsqueda causando múltiples consultas por nombre.
  • Sorpresas split-horizon: DNS interno que devuelve IPs privadas o interceptadas para registries públicos.
  • Latencia por DNSSEC/validación: fallos de validación que llevan a reintentos y comportamientos de fallback.

Qué hacer (opinión)

  • Usa un resolvedor de caché cercano para nodos de build. Si tu “resolvedor central” está al otro lado del continente, eso es un error de diseño.
  • No hardcodees resolvedores públicos por reflejo. Pueden ser rápidos, pero también violar enrutamiento corporativo, romper DNS dividido o ser bloqueados.
  • Arregla ndots/search domains intencionalmente. La gente copia y pega configs de resolvedor como si fueran inofensivas. No lo son.

Solución concreta: define explícitamente DNS para el daemon de Docker

Si los contenedores (no el host) sufren retrasos DNS, configura el DNS del daemon. Esto no siempre afecta directamente a las pulls del registro (esas las hace el daemon), pero evita dolores secundarios cuando los contenedores arrancan.

cr0x@server:~$ sudo tee /etc/docker/daemon.json >/dev/null <<'EOF'
{
  "dns": ["10.10.0.53", "10.10.0.54"],
  "log-level": "info"
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

Decisión: Si el reinicio lo arregla, tenías deriva de resolvedores o el daemon estaba usando un stub que no podía alcanzar de forma fiable.

MTU y PMTUD: el misterio silencioso del asesinato de paquetes

Los problemas de MTU no suelen fallar ruidosamente. Fallan como una mala reunión: técnicamente todos están presentes, y nada avanza.

El patrón: las solicitudes pequeñas funcionan, las transferencias grandes se estancan. Puedes hacer ping. Puedes obtener un manifiesto pequeño. Entonces empieza a descargarse un blob y se detiene en un contador de bytes consistentemente sospechoso, o cuelga para siempre con reintentos.

Cómo MTU rompe las pulls

  • PMTUD que desaparece en agujero negro: ICMP “Fragmentation Needed” está bloqueado por firewall/middlebox, así que los endpoints siguen enviando paquetes que nunca llegan.
  • Overhead de overlay y túnel: VPNs, GRE, VXLAN, WireGuard, IPSec reducen el MTU efectivo.
  • MTU del bridge desajustada: redes de Docker configuradas a 1500 mientras que la ruta real es más pequeña.

Solución: establece deliberadamente el MTU de la red de Docker

Puedes fijar el MTU por defecto para las redes bridge de Docker vía la configuración del daemon. Elige un valor que encaje con tu ruta de egress. Si tu túnel es 1420, pon Docker a 1400–1420 para dejar margen.

cr0x@server:~$ sudo tee /etc/docker/daemon.json >/dev/null <<'EOF'
{
  "mtu": 1420,
  "log-level": "info"
}
EOF
sudo systemctl restart docker

Qué significa: Las redes nuevas usarán este MTU; las redes existentes pueden necesitar recreación.

Decisión: Si las pulls se estabilizan inmediatamente tras alinear el MTU (especialmente sobre VPN), has encontrado al culpable. Luego arregla PMTUD correctamente para no estar parcheando la red.

Solución: permitir ICMP para PMTUD

Si controlas los firewalls, permite los tipos ICMP específicos necesarios para PMTUD (IPv4 “frag needed”, IPv6 Packet Too Big). Bloquearlos es una superstición heredada.

Broma #2: Algunas redes bloquean ICMP “por seguridad”, lo cual es como quitar las señales de tráfico para evitar que la gente corra.

Proxies corporativos: cuando “útil” se vuelve dañino

Los proxies son o bien una herramienta de cumplimiento necesaria o bien un recaudador de impuestos de rendimiento. A veces ambas cosas. Las pulls de Docker estresan a los proxies en varias formas: muchas conexiones concurrentes, flujos TLS grandes, redirecciones y mezcla de endpoints.

Comportamientos de proxy que ralentizan las pulls

  • Timeouts de inactividad cortos: las descargas de blobs se pausan, el proxy cierra la conexión, el cliente reintenta.
  • Intercepción TLS: añade overhead al handshake, rompe la reutilización de sesión y puede interactuar mal con HTTP/2 o cifrados modernos.
  • Límites de concurrencia: el proxy estrangula descargas paralelas para que cada blob vaya a paso de tortuga.
  • NO_PROXY incorrecto: el daemon envía tráfico del registro a través del proxy cuando no debería.

Solución: configura el proxy del daemon correctamente (y por completo)

Establece el proxy para el daemon de Docker vía systemd drop-in, no solo en el entorno de tu shell.

cr0x@server:~$ sudo mkdir -p /etc/systemd/system/docker.service.d
sudo tee /etc/systemd/system/docker.service.d/proxy.conf >/dev/null <<'EOF'
[Service]
Environment="HTTP_PROXY=http://proxy.corp.local:3128"
Environment="HTTPS_PROXY=http://proxy.corp.local:3128"
Environment="NO_PROXY=localhost,127.0.0.1,::1,registry.corp.local,.corp.local"
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

Qué significa: El daemon ahora usa consistentemente las variables de proxy. Esto elimina la división “funciona en curl, falla en docker”.

Decisión: Si las pulls mejoran, tu estado previo estaba inconsistente. Si empeoran, tu proxy es el cuello de botella—omite el proxy para registries cuando la política lo permita.

Solución: añade un mirror de registro local (con precaución)

Un mirror puede eliminar el tránsito por proxy y reducir tráfico WAN. También puede convertirse en tu nuevo punto único de fallo. Si montas un mirror, trátalo como infraestructura de producción: monitorización, capacidad de almacenamiento, higiene TLS y una política de expulsión predecible.

IPv6, dual-stack y el largo timeout

IPv6 roto es un tipo especial de dolor: todo parece “mayormente bien”, pero cada nueva conexión paga una penalización mientras los clientes prueban IPv6 primero, esperan y luego hacen fallback.

Diagnostica la demora por dual-stack

Busca SYN-SENT en v6, o huecos largos antes del primer byte. También revisa si DNS devuelve registros AAAA que no enrutan desde tus hosts.

Mitigaciones (elige la menos mala)

  • Arregla IPv6 correctamente: rutas correctas, RA, reglas de firewall y respuestas AAAA en DNS.
  • Prefiere IPv4 temporalmente: como medida de emergencia si el CI está caído y necesitas las imágenes ya.

Si eliges la preferencia temporal por IPv4, sé explícito y márcalo como deuda técnica. Las configuraciones “temporales” tienden a convertirse en arqueología.

Limitación del registro, límites de tasa y rarezas del CDN

Sí, a veces realmente es el registro. Límites de tasa, throttling o un borde de CDN desafortunado pueden convertir pulls en alquitrán. Pero diagnostica primero. No uses “internet lento” como tu estrategia de monitorización.

Señales de que te están limitando

  • Las pulls son rápidas fuera de horas y lentas en horario laboral.
  • Los errores mencionan demasiadas solicitudes, o ves comportamiento similar a 429 en herramientas de más alto nivel.
  • Solo un registro es lento; los demás funcionan normalmente.

Soluciones que suelen valer la pena

  • Autentica las pulls cuando sea aplicable para obtener límites más altos.
  • Usa una caché/mirror interna para flotas de CI, especialmente si reconstruyes con frecuencia.
  • Reduce el churn de imágenes: fija las imágenes base, evita reconstrucciones que invaliden cada capa.

Almacenamiento y extracción: cuando la red no es el cuello de botella

Un secreto sucio: muchas “pulls lentas” son descargas rápidas seguidas de extracción lenta. Especialmente en runners compartidos, discos thin-provisioned o situaciones de overlay sobre overlay.

La extracción es un benchmark de almacenamiento disfrazado

  • Muchos archivos pequeños: churn de metadatos e inodos.
  • Compresión: tiempo de CPU, luego escrituras.
  • Comportamiento del filesystem overlay: penalizaciones copy-up, presión de dentry y amplificación de escritura.

Soluciones que importan

  • Mueve el data root de Docker a almacenamiento más rápido (/var/lib/docker en NVMe supera a discos en red).
  • Mantén imágenes más pequeñas y reduce el recuento de archivos en capas (multi-stage builds, imágenes base slim, limpiar cachés de paquetes).
  • En hosts ocupados, programa pulls para evitar contención de IO (o aisla nodos de build).

Tres microhistorias corporativas desde el frente

Microhistoria 1: El incidente causado por una suposición errónea

La compañía tenía una línea base de red “segura por defecto”. Un equipo desplegó nuevas reglas de firewall para “apretar” la comunicación entre los workers de build y la internet. Las pulls no fallaban por completo; simplemente se volvían dolorosamente lentas y a veces se quedaban estancadas. Eso es peor, porque parece una falla aleatoria en lugar de una interrupción clara.

La suposición era simple y equivocada: “ICMP es opcional”. Las reglas bloqueaban ICMP tipo 3 código 4 (IPv4 fragmentation needed) y el equivalente en IPv6. La mayoría de la navegación web seguía funcionando. Pequeñas solicitudes HTTPS estaban bien. ¿Docker pulls? Los registros TLS grandes y los blobs grandes alcanzaban el MTU efectivo, desaparecían en un agujero negro y se quedaban allí hasta que los reintentos y el backoff hacían que el pipeline pareciera embrujado.

El on-call pasó horas persiguiendo “problemas en Docker Hub”, luego “problemas de proxy”, luego “quizá nuestros runners están sobrecargados”. Alguien finalmente ejecutó una sonda MTU y encontró la pistola humeante: el path MTU era menor que el MTU de interfaz, y PMTUD no podía señalizarlo.

La solución no fue heroica. Permitieron los tipos ICMP correctos en la ruta de salida y establecieron un MTU conservador en Docker en los runners que atravesaban túneles. Los tiempos de pull volvieron a la normalidad. El action item del postmortem fue aún más valioso: cada cambio de red que toque egress ahora tiene una comprobación MTU/PMTUD como puerta estándar.

Microhistoria 2: La optimización que salió mal

Un equipo de plataforma quiso builds más rápidas, así que introdujeron un mirror de registro en cada región. Idea sensata: localizar tráfico, reducir ancho de banda WAN, mantener CI ágil. Lo desplegaron rápido y al principio parecía genial.

Luego el boomerang. El mirror tenía poco presupuesto de disco y una expulsión agresiva. Cuando varios equipos comenzaron a publicar imágenes más grandes (runtimes de lenguaje, dependencias ML, símbolos de depuración—elige tu veneno), el mirror churneaba constantemente. Un blob se cacheaba, se expulsaba y se volvía a descargar en el mismo día. El proxy delante del mirror también tenía un timeout de inactividad corto; las descargas parciales se reiniciaban, causando reintentos, aumentando la carga y provocando más resets. Un pequeño bucle de retroalimentación de miseria.

El síntoma era engañoso: a veces las pulls eran rápidas y a veces catastróficamente lentas, incluso dentro de la misma región. Los ingenieros culpaban al registro, luego a Docker, luego a sus propias imágenes. La causa real fue la “optimización”: una caché demasiado pequeña para ser estable, más timeouts afinados para páginas web, no para blobs de cientos de megabytes.

La solución eventual fue aburrida: dimensionar el almacenamiento, ajustar timeouts, añadir monitorización de hit rate de caché y desactivar el comportamiento de proxy que rompía transferencias largas. También establecieron política: los mirrors son servicios de producción, no proyectos secundarios en VMs sobrantes.

Microhistoria 3: La práctica aburrida pero correcta que salvó el día

Una entidad financiera tenía controles de salida estrictos. También tenía un hábito que desearía que todos tuvieran: cada worker de build y cada nodo de Kubernetes ejecutaban los mismos diagnósticos básicos al arrancar y publicaban los resultados internamente.

Un lunes, los desarrolladores se quejaron de que las pulls estaban lentas “en todas partes”. El SRE de guardia no abrió debates en Slack sobre si Docker Hub tenía un mal día. Abrió el dashboard de diagnósticos de los nodos. Los tiempos de consulta DNS se habían triplicado en un subconjunto de subredes. Las sondas MTU estaban normales. El IO de disco normal. Era DNS, limpia y sin romanticismo.

El culpable fue un failover de resolvedor que técnicamente funcionaba pero movía tráfico a un sitio más lejano. La latencia subió. Las cachés estaban frías. Las pulls del registro de repente pagaban milisegundos extras docenas de veces por pull. No suficiente para gritar “incidente”, pero sí para fundir el rendimiento del CI.

Cambiaron la preferencia de resolvedor, calentaron cachés y la queja desapareció. La práctica salvadora no fue una herramienta ingeniosa; fue la medición baseline consistente. Aburrido. Correcto. Increíblemente efectivo cuando la sala se llena de teorías.

Errores comunes (síntomas → causa raíz → solución)

  • Síntoma: La pull se cuelga en “Downloading” alrededor del mismo porcentaje cada vez.
    Causa raíz: Agujero negro MTU/PMTUD; paquetes grandes descartados, sin feedback ICMP.
    Solución: Sondea MTU con ping -M do, permite frag-needed/Packet Too Big, ajusta MTU de Docker para coincidir con el camino del túnel.
  • Síntoma: Pausa larga antes de cualquier progreso, luego de repente acelera.
    Causa raíz: IPv6 roto con fallback lento a IPv4; existen registros AAAA pero no enrutan.
    Solución: Corrige enrutamiento/firewall/DNS IPv6; como mitigación, prefiere IPv4 para el daemon o elimina AAAA defectuosos en el resolvedor si lo controlas.
  • Síntoma: curl al registro es rápido, pero docker pull es lento o caduca.
    Causa raíz: Split-brain de proxy: las variables de proxy de la shell difieren de la configuración del daemon.
    Solución: Configura el proxy mediante systemd drop-in para el daemon de Docker; alinea NO_PROXY.
  • Síntoma: La pull es rápida en laptops, lenta en servidores.
    Causa raíz: Los servidores usan distintos resolvedores/domains de búsqueda, o atraviesan VPN/túneles con MTU reducido.
    Solución: Compara resolvedor y MTU; alinea DNS y MTU del daemon con la ruta real del servidor.
  • Síntoma: “Downloading” es rápido, “Extracting” tarda una eternidad, CPU ok pero IO saturado.
    Causa raíz: Disco lento, almacenamiento thin-provisioned, overhead de filesystem overlay, demasiados archivos pequeños.
    Solución: Mueve Docker root a almacenamiento más rápido; optimiza imágenes; reduce recuento de archivos; considera aislar runners.
  • Síntoma: Solo un registro es lento; otros normales.
    Causa raíz: Selección de borde CDN, limitación de tasa o política de proxy aplicada a ciertos dominios.
    Solución: Autentica las pulls; usa un mirror; ajusta bypass de proxy; valida respuestas DNS (geo, split horizon).
  • Síntoma: La velocidad de pull oscila salvajemente minuto a minuto.
    Causa raíz: Resets de conexiones del proxy, churn del cache del mirror, o congestión de red con reintentos.
    Solución: Ajusta timeouts de inactividad del proxy, dimensiona el mirror correctamente, mide hit rate de caché, reduce pulls concurrentes en ventanas de congestión.

Listas de verificación / plan paso a paso

Checklist A: Triage en un nodo (15 minutos)

  1. Ejecuta docker -D pull y anota dónde se va el tiempo (resolve/auth/download/extract).
  2. Mide tiempo DNS para el hostname del registro con dig +stats.
  3. Revisa /etc/resolv.conf por ndots y domains de búsqueda que amplifiquen búsquedas.
  4. Comprueba desajuste de MTU: ip -br link y sonda ping -M do.
  5. Verifica settings de proxy del daemon con systemctl cat docker.
  6. Comprueba intentos de conexión IPv6 con ss -tpn.
  7. Si la extracción es lenta, confirma con iostat y deja de culpar a la red.

Checklist B: Arreglo a escala de flota (qué escala más allá de un host)

  1. Estandariza la configuración del daemon: servidores DNS, MTU, proxy, nivel de logs.
  2. Pruebas baseline en boot: latencia DNS, sonda MTU a endpoints clave, sanity de IO de disco.
  3. Observabilidad central: duraciones de pull, tasas de fallo y en qué se gasta el tiempo (descarga vs extracción).
  4. Introduce mirrors deliberadamente: trátalos como producción—capacidad, monitorización, TTL/política de expulsión, timeouts.
  5. Higiene de políticas de red: permite explícitamente tipos ICMP necesarios para PMTUD; documenta para que no se “endurezca” y se pierda.

Checklist C: Higiene de imágenes (esto lo controlas tú)

  1. Mantén imágenes base fijadas y consistentes para maximizar la reutilización de capas.
  2. Usa multi-stage builds para reducir tamaño y número de archivos.
  3. Evita incrustar cachés de gestores de paquetes dentro de capas.
  4. Prefiere capas menos numerosas y más significativas en lugar de docenas de microcapas.

Preguntas frecuentes

1) ¿Por qué docker pull se siente más lento que descargar un archivo grande?

Porque no es un solo archivo. Son múltiples búsquedas DNS, llamadas de auth, redirecciones, descargas paralelas de blobs y luego descompresión + extracción. Cualquier eslabón débil se convierte en “Docker es lento”.

2) Puedo resolver DNS rápido en el host. ¿Por qué Docker aún sería lento?

El daemon puede usar ajustes DNS distintos a los de tu shell o tus contenedores. Revisa /etc/docker/daemon.json, el comportamiento de systemd-resolved y si el daemon está apuntando a un stub resolver que no puede usar de forma fiable.

3) ¿Cuál es la forma más rápida de confirmar un problema de MTU?

Usa una sonda ping “no fragmentar” y una búsqueda binaria del tamaño de payload. Si paquetes grandes fallan pero paquetes más pequeños funcionan, y especialmente si estás en VPN/túneles, trata el MTU como sospechoso hasta demostrar lo contrario.

4) ¿Debería poner MTU de Docker a 1400 en todas partes?

No. Eso es un instrumento contundente. Puede reducir rendimiento en redes limpias y ocultar defectos reales de PMTUD/firewall. Ajusta MTU según las restricciones reales de la ruta y arregla el manejo de ICMP para no necesitar superstición.

5) ¿Por qué desactivar IPv6 “arregla” el problema?

Si IPv6 está roto, los clientes pierden tiempo intentando IPv6 antes de hacer fallback. Desactivar IPv6 fuerza IPv4 de inmediato, evitando el impuesto por timeout. Mejor solución: haz que IPv6 funcione o deja de publicar registros AAAA rotos.

6) Mi proxy es obligatorio. ¿Qué debo pedir al equipo de red?

Pide: timeouts de inactividad más largos para descargas grandes, soporte de TLS moderno sin romper la reutilización de sesiones, capacidad para muchas conexiones concurrentes y una lista de bypass clara para hostnames de registro si la política lo permite.

7) La pull es lenta solo en nodos de Kubernetes. ¿Por qué?

Los nodos suelen tener rutas de salida diferentes (gateways NAT, overlays CNI, firewalls más estrictos, DNS distinto). Además, kubelet usa containerd directamente, así que las configuraciones de proxy/DNS pueden diferir de tus suposiciones sobre el “host Docker”.

8) ¿Cómo diferencio “descarga lenta” de “extracción lenta” sin adivinar?

Usa docker -D pull y observa dónde se queda; luego confirma con métricas de disco. Si IO está al máximo durante “Extracting”, tu red no es el cuello de botella.

9) ¿Vale siempre la pena un mirror de registro?

Vale la pena cuando tienes muchos nodos tirando repetidamente de imágenes comunes, especialmente en CI. No vale la pena si no puedes operarlo de forma fiable. Un mirror poco fiable es una forma creativa de inventar interrupciones.

10) ¿Por qué las pulls a veces se vuelven más lentas después de “optimizar” DNS?

Porque “optimizar” a menudo significa cambiar resolvedores sin entender DNS dividido, comportamiento de caché o respuestas geo. Menor RTT del resolvedor no ayuda si devuelve un peor borde de CDN o rompe el enrutamiento corporativo.

Conclusión: siguientes pasos que rinden

Si docker pull es dolorosamente lento, trátalo como un incidente de producción: reúne evidencia, aísla la etapa y arregla el cuello de botella real. Empieza por latencia y corrección DNS, luego MTU/PMTUD, luego comportamiento del proxy, después IPv6 y solo entonces discute con el registro.

Pasos prácticos siguientes:

  1. Ejecuta la guía de diagnóstico rápido en un nodo afectado y en uno conocido bueno. Compara resultados.
  2. Estandariza la configuración del daemon Docker en tu flota (DNS, MTU, proxy) y reinicia de forma intencionada.
  3. Añade comprobaciones baseline de DNS/MTU al aprovisionamiento de nodos para detectar esto antes de que CI se convierta en arte performativo.
  4. Si la extracción es tu cuello de botella, mueve el almacenamiento de Docker a discos más rápidos y limpia tus imágenes. El ajuste de red no salvará un host ligado al disco.
← Anterior
Redes Docker: bridge vs host vs macvlan — elige la que no te dará problemas después
Siguiente →
ZFS Readonly: el truco anti-ransomware que puedes implementar hoy

Deja un comentario