Si ejecutas Docker Desktop en Windows con WSL2 y da la sensación de que tus contenedores trabajan a través de cemento húmedo, no te lo imaginas. “npm install” tarda una eternidad. Los observadores de archivos no detectan cambios o consumen CPU. Un simple git status puede hacerte cuestionar tus decisiones profesionales.
Esto tiene solución. Pero no con buenas intenciones, trucos del registro que encontraste en un foro o marcando casillas al azar hasta que el ventilador deje de chillar. Lo arreglas identificando qué subsistema es realmente lento: el límite del sistema de archivos, el disco VHDX, la planificación de CPU, la presión de memoria, DNS o el comportamiento del caché de compilación.
Guía rápida de diagnóstico
La mayoría de los equipos desperdician días “ajustando Docker” cuando el cuello de botella es un único límite: archivos de Windows en /mnt/c, o un montaje bind dentro de un contenedor, o un VHDX que se ha inflado y ahora hace I/O triste. Aquí está el camino rápido hacia la verdad.
Primero: determina dónde viven los archivos lentos
- Si tu repositorio está bajo
/mnt/c(o cualquier/mnt/<drive>): asume que el I/O de archivos es tu cuello de botella hasta demostrar lo contrario. - Si tu repositorio está dentro del sistema de archivos Linux (
~en WSL2): el I/O de archivos suele estar bien; lo siguiente es comprobar la presión de CPU/memoria o los patrones de montaje de contenedores.
Segundo: identifica la “clase de operación lenta”
- Muchos archivos pequeños (node_modules, directorios vendor, monorepos): los bind mounts y la interoperabilidad con Windows harán daño.
- Cargas de bases de datos (Postgres, MySQL, Elasticsearch): I/O aleatorio + fsync + problemas de aprovisionamiento fino.
- Cargas de compilación (builds de Docker): la ubicación del caché y la transferencia del contexto dominan.
- Cargas de red (descarga de imágenes, registros privados): DNS y configuración de proxy, no “rendimiento de Docker”.
Tercero: ejecuta tres pruebas para localizar el cuello de botella
- Prueba del sistema de archivos WSL: crea y usa estadísticas de un montón de archivos bajo
~en WSL2. - Prueba /mnt: repite bajo
/mnt/c. - Prueba de montaje en contenedor: ejecuta la misma prueba dentro de un contenedor en un bind mount frente a un volumen nombrado.
Si /mnt/c es dramáticamente más lento que ~, detente. Mueve el repositorio. Si los bind mounts de contenedores son más lentos que los volúmenes nombrados, detente. Cambia la estrategia de montaje. Si ambos están bien, probablemente estés limitado por CPU/memoria o por DNS.
Cuarto: comprueba si hay escasez de recursos
Si WSL2 está mal aprovisionado (o sobreaprovisionado de forma que deja a Windows sin recursos), todo se vuelve inestable: observadores de archivos, compiladores, bases de datos. Arregla los límites de memoria y swap, y vuelve a probar.
Por qué es lento: la arquitectura real y sus trampas
Docker Desktop en Windows con WSL2 no es “Docker ejecutándose en Windows”. Es Docker ejecutándose en una VM Linux con mucha tubería para que parezca nativo. Esa tubería es donde el rendimiento se pone a practicar aficiones.
A grandes rasgos:
- WSL2 es una VM ligera que usa Hyper-V por debajo.
- Tu distribución Linux (Ubuntu, Debian, etc.) vive en un sistema de archivos ext4 dentro de un VHDX.
- Docker Desktop suele ejecutar el demonio en una distro WSL2 especial (a menudo
docker-desktop). - Cuando accedes a archivos de Windows desde WSL2 (como
/mnt/c/Users/...), cruzas un límite implementado por una capa de sistema de archivos especial. - Cuando montas por bind una ruta de Windows en un contenedor Linux, a menudo cruzas ese límite de nuevo, más la semántica de overlay y montaje del contenedor.
Los problemas de rendimiento suelen venir de uno de estos modos de fallo:
- Sobrecarga del sistema de archivos de interoperabilidad: las herramientas Linux esperan operaciones de metadatos baratas. Los archivos de Windows accedidos vía la capa de montaje de WSL pueden hacer que
stat()y los recorridos de directorio sean caros. - Demasiados archivos: millones de archivos diminutos son una prueba de estrés para cualquier capa de compartición de archivos entre SO.
- Comportamiento de la imagen de disco: el VHDX crece con facilidad; reducir su tamaño no es trivial; la fragmentación y la disposición del espacio libre importan más de lo que quisieras.
- Presión de memoria: WSL2 hará mucho caching; bajo presión, obtienes tormentas de expulsión y momentos de “¿por qué todo está intercambiando?”.
- Transferencia del contexto de compilación: cuando construyes desde una ruta de Windows, Docker puede pasar tiempo real enviando tu contexto a la VM.
- Inotify y observadores: algunas cadenas de herramientas dependen de eventos de sistema de archivos; cruzar límites puede romper semánticas o desencadenar sondeos.
Una cita para mantenerte honesto: “La esperanza no es una estrategia.”
— idea parafraseada atribuida al general Gordon R. Sullivan, repetida en círculos de confiabilidad. Sustituye “esperanza” por “marcadores aleatorios”, y estarás listo para producción.
Hechos y contexto histórico (breve y útil)
- WSL1 vs WSL2 fue un intercambio de sistema de archivos: WSL1 traducía llamadas al sistema Linux a Windows; WSL2 pasó a un kernel Linux real en una VM. Muchas cosas ganaron compatibilidad, pero el acceso entre archivos se convirtió en un problema mayor.
- Docker Desktop usaba Hyper-V antes de que madurara la integración con WSL2: las primeras configuraciones Docker en Windows ejecutaban VMs LinuxKit; WSL2 redujo la sobrecarga y mejoró la integración, pero no eliminó los límites de la VM.
- ext4 en VHDX es rápido… hasta que lo haces raro: se comporta como un sistema de archivos Linux normal, pero el archivo VHDX de respaldo tiene su propio ciclo de vida (crecimiento, fragmentación, compactación).
- Las cargas con muchos metadatos son el dolor común: los gestores de paquetes y las cadenas de herramientas de lenguajes realizan muchos
stat(),readdir()y escrituras de archivos de corta vida. - Las semánticas de compartición de archivos difieren: sensibilidad a mayúsculas, bits ejecutables, symlinks y mapeo de permisos no son gratis. “Funciona” no significa “funciona rápido”.
- BuildKit cambió el juego de las builds: las builds modernas de Docker pueden cachear más inteligentemente, pero solo si dejas de sabotear el caché con contextos enormes y marcas de tiempo cambiantes.
- Los volúmenes nombrados no son solo para producción: evitan algunas de las capas lentas de compartición de rutas host y pueden ser dramáticamente más rápidos para directorios calientes.
- La red de WSL2 está virtualizada: DNS y enrutamiento pueden ser diferentes a Windows, y las VPN corporativas adoran involucrarse.
Haz estas tareas primero: 12+ comprobaciones con comandos y decisiones
Estas son tareas reales que puedes ejecutar. Cada una incluye: comando, ejemplo de salida, qué significa y qué decisión tomar.
Tarea 1: Confirma dónde vive realmente tu repositorio
cr0x@server:~$ pwd
/home/cr0x/work/myapp
Significado: Esto está dentro del sistema de archivos Linux (bien). Si en su lugar ves /mnt/c/..., ese es un sospechoso principal.
Decisión: Si tu proyecto está en /mnt/c, planea moverlo a ~ y usa un editor que soporte acceso remoto a WSL.
Tarea 2: Compara el rendimiento con archivos pequeños: inicio WSL vs /mnt/c
cr0x@server:~$ mkdir -p ~/io-test && cd ~/io-test && time bash -lc 'rm -rf a && mkdir a && for i in $(seq 1 20000); do echo x > a/f$i; done; find a -type f | wc -l'
20000
real 0m3.412s
user 0m1.102s
sys 0m2.101s
cr0x@server:~$ mkdir -p /mnt/c/Users/Public/io-test && cd /mnt/c/Users/Public/io-test && time bash -lc 'rm -rf a && mkdir a && for i in $(seq 1 20000); do echo x > a/f$i; done; find a -type f | wc -l'
20000
real 1m12.883s
user 0m2.004s
sys 0m8.991s
Significado: Si tu ejecución en /mnt/c es un orden de magnitud más lenta, has encontrado el problema central.
Decisión: Mantén el código fuente y los directorios de dependencias en el sistema de archivos WSL. Trata /mnt/c como un montaje por conveniencia, no como una granja de compilación.
Tarea 3: Comprueba el contexto de Docker y detalles del motor
cr0x@server:~$ docker context ls
NAME DESCRIPTION DOCKER ENDPOINT
default * Current DOCKER_HOST based configuration npipe:////./pipe/docker_engine
Significado: En Windows, el CLI puede hablar con Docker Desktop vía una pipe nombrada de Windows, aunque el motor se ejecute en WSL2.
Decisión: Si mezclas CLIs de Windows y WSL, elige un flujo de trabajo. Prefiere ejecutar el CLI de Docker dentro de WSL para menos cruces de límite.
Tarea 4: Verifica que construyas desde rutas Linux (el contexto importa)
cr0x@server:~$ docker build -t myapp:ctxcheck .
[+] Building 9.6s (10/10) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> [internal] load .dockerignore 0.0s
=> [internal] load metadata for docker.io/library/node:20-alpine 0.9s
=> [internal] load build context 6.8s
=> => transferring context: 412.34MB 6.6s
...
Significado: “transferring context” que tarda segundos es algo normal para contextos grandes; si son decenas de segundos, estás pagando un impuesto de límite o enviando demasiado.
Decisión: Añade un .dockerignore real y construye desde dentro de WSL. Si tu contexto es enorme, deja de enviar node_modules al demonio.
Tarea 5: Comprueba si BuildKit está habilitado (debería)
cr0x@server:~$ docker buildx version
github.com/docker/buildx v0.12.1
Significado: Buildx existe; BuildKit está disponible.
Decisión: Usa funciones de BuildKit como los cache mounts para gestores de paquetes. Si aún usas el builder heredado, espera dolor.
Tarea 6: Mide el rendimiento bind mount vs volumen nombrado en contenedores
cr0x@server:~$ docker volume create voltest
voltest
cr0x@server:~$ mkdir -p ~/bindtest
cr0x@server:~$ time docker run --rm -v ~/bindtest:/work alpine sh -lc 'cd /work; rm -rf a; mkdir a; for i in $(seq 1 20000); do echo x > a/f$i; done; find a -type f | wc -l'
20000
real 0m18.911s
user 0m0.211s
sys 0m0.404s
cr0x@server:~$ time docker run --rm -v voltest:/work alpine sh -lc 'cd /work; rm -rf a; mkdir a; for i in $(seq 1 20000); do echo x > a/f$i; done; find a -type f | wc -l'
20000
real 0m4.022s
user 0m0.203s
sys 0m0.380s
Significado: Si los volúmenes nombrados son mucho más rápidos, la ruta del bind mount es el cuello de botella.
Decisión: Coloca directorios calientes (deps, cachés, datos de BD) en volúmenes nombrados. Monta por bind solo el código que editas activamente.
Tarea 7: Comprueba memoria, swap y presión en WSL
cr0x@server:~$ free -h
total used free shared buff/cache available
Mem: 7.6G 6.8G 220M 120M 650M 390M
Swap: 2.0G 1.4G 600M
Significado: Estás cerca del precipicio. “available” bajo y swap activo significa picos de latencia en todas partes.
Decisión: Aumenta la memoria de WSL2, reduce la paralelización o deja de ejecutar cinco bases de datos más un navegador y Teams. Elige dos.
Tarea 8: Identifica I/O caliente y procesos dentro de WSL
cr0x@server:~$ sudo apt-get update -qq && sudo apt-get install -y -qq iotop
...
cr0x@server:~$ sudo iotop -o -b -n 3
Total DISK READ: 12.34 M/s | Total DISK WRITE: 45.67 M/s
PID PRIO USER DISK READ DISK WRITE SWAPIN IO> COMMAND
2421 be/4 postgres 0.00 B/s 9.12 M/s 0.00 % 85.12 % postgres: checkpointer
9911 be/4 cr0x 0.00 B/s 5.44 M/s 0.00 % 62.10 % node dev-server
Significado: Puedes ver quién está golpeando el disco. Un checkpointer de Postgres con escrituras sostenidas suele significar fsync + configuración de checkpoint o desajuste de carga de trabajo.
Decisión: Si las bases de datos dominan, mueve sus directorios de datos a volúmenes nombrados y revisa la configuración de la BD para desarrollo (compromisos de seguridad/latencia, no producción).
Tarea 9: Comprueba las opciones de montaje del sistema de archivos para /mnt/c (comportamiento de metadatos)
cr0x@server:~$ mount | grep -E ' /mnt/c '
C: on /mnt/c type 9p (rw,noatime,dirsync,aname=drvfs;path=C:\;uid=1000;gid=1000;metadata;symlinkroot=/mnt/)
Significado: Las opciones drvfs/9p impactan el comportamiento. metadata puede mejorar semánticas de permisos pero tener sobrecarga; dirsync suele ser costoso.
Decisión: No ajustes banderas de montaje a ciegas. Si tu repo está en /mnt/c, la solución es moverlo. Si debes quedarte, considera ajustar opciones de automontaje, pero trátalo como último recurso.
Tarea 10: Comprueba si tu VHDX es enorme y estás pagando por ello
cr0x@server:~$ df -h /
Filesystem Size Used Avail Use% Mounted on
/dev/sdd 251G 201G 38G 85% /
Significado: Uso alto y un gran sistema de archivos ext4 pueden correlacionarse con operaciones más lentas, especialmente si estás churnando dependencias constantemente.
Decisión: Limpia cachés, poda artefactos de Docker y considera compactar/reducir el VHDX periódicamente (con cuidado, y con copias de seguridad).
Tarea 11: Mide el comportamiento de DNS desde dentro de un contenedor
cr0x@server:~$ docker run --rm alpine sh -lc 'apk add -q bind-tools; time nslookup registry-1.docker.io'
real 0m1.882s
user 0m0.012s
sys 0m0.008s
Server: 192.168.65.1
Address: 192.168.65.1:53
Non-authoritative answer:
Name: registry-1.docker.io
Address: 54.161.123.11
Significado: Búsquedas DNS de varios segundos harán que pulls/builds “se sientan lentos” aun cuando el disco esté bien.
Decisión: Si DNS es lento, arregla DNS (generación de resolv.conf de WSL, DNS corporativo, enrutamiento por VPN). No culpes a overlay2 por tu resolvedor.
Tarea 12: Confirma que no estás acumulando imágenes/volúmenes patológicos
cr0x@server:~$ docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 42 8 18.4GB 12.7GB (68%)
Containers 19 2 1.2GB 900MB (75%)
Local Volumes 31 6 54.0GB 40.5GB (75%)
Build Cache 0 0 0B 0B
Significado: Volúmenes ocupando 54GB es normal solo si ejecutas conjuntos de datos reales. En desarrollo, a menudo son bases de datos abandonadas y cachés.
Decisión: Podar lo que puedas, pero con intención. Si los volúmenes guardan estado importante, migra o haz copia de seguridad antes de destruirlos.
Tarea 13: Verifica si un contenedor está haciendo demasiados fsync (bases de datos)
cr0x@server:~$ docker exec -it my-postgres sh -lc 'psql -U postgres -c "SHOW synchronous_commit; SHOW fsync;"'
synchronous_commit
-------------------
on
(1 row)
fsync
-------
on
(1 row)
Significado: En dev, synchronous_commit=on y fsync=on es seguro pero puede ser lento en rutas de almacenamiento virtualizadas.
Decisión: Para velocidad solo en dev, considera relajar esas opciones (aceptando pérdida de datos en fallos). Para cualquier cosa que se parezca a pruebas de producción, conserva los valores por defecto y optimiza la ruta de almacenamiento en su lugar.
Tarea 14: Detecta observadores que recurren al sondeo (consumo de CPU)
cr0x@server:~$ ps aux | grep -E 'watch|chokidar|webpack|nodemon' | head
cr0x 18821 65.2 3.1 1245320 251212 ? Sl 10:11 5:44 node node_modules/.bin/webpack serve
cr0x 18844 22.7 1.2 981244 96120 ? Sl 10:11 2:03 node node_modules/chokidar/index.js
Significado: Alta CPU de observadores suele significar que no reciben eventos nativos y escanean repetidamente.
Decisión: Mantén los árboles observados en ext4 de WSL, no en /mnt/c. Reduce el alcance de la observación. Prefiere configuraciones de herramientas que usen inotify de manera efectiva.
Soluciones que realmente mueven la aguja
1) Pon tu código en el sistema de archivos de WSL. Sí, en serio.
Esta es la solución de mayor ROI para cargas de trabajo de desarrollador con muchos archivos pequeños. Mantén repositorios bajo ~/src dentro de WSL. Accede a ellos desde Windows mediante herramientas conscientes de WSL en lugar del camino inverso.
- Haz:
git clonedentro de WSL. - No hagas: mantener el repo en NTFS y “simplemente montarlo”. Así es como obtienes recorridos de directorio de 70 segundos.
Broma #1: Si insistes en ejecutar node_modules desde /mnt/c, no estás ajustando el rendimiento: estás recreando una película de desastre a cámara lenta.
2) Usa volúmenes nombrados para datos calientes, bind mounts para el ciclo de edición
El modelo mental que te salva: los bind mounts son para código fuente; los volúmenes nombrados son para churn. Bases de datos, cachés de gestores de paquetes, directorios de dependencias, cualquier cosa que cree miles de archivos rápidamente: dale un volumen.
Estrategia de ejemplo:
- Bind mount:
./app:/app - Volumen nombrado:
node_modules:/app/node_modules - Volumen nombrado:
pgdata:/var/lib/postgresql/data
Por qué ayuda: los volúmenes nombrados viven directamente en la capa de sistema de archivos de la VM Linux, evitando la lenta compartición de archivos entre OS.
3) Deja de enviar basura en tu contexto de build de Docker
Si tu contexto de build tiene cientos de MB, estás pagando por un tarball + transferencia + extracción antes de que la primera línea del Dockerfile importe. Tu .dockerignore debe ser agresivo: ignora directorios de dependencias, salidas de build, artefactos de prueba, cachés locales.
La mayoría de los equipos lo hace a medias: ignoran node_modules pero olvidan .git, dist, .next, coverage y cachés específicos de lenguaje. Luego se preguntan por qué “load build context” domina.
4) Usa cache mounts de BuildKit para gestores de paquetes
BuildKit puede cachear directorios entre builds sin hornearlos en capas. Eso significa rebuilds más rápidos e imágenes más pequeñas. Si haces builds repetidas en WSL2, esto importa.
Patrones de ejemplo (conceptualmente): cachea la caché de descargas del gestor de paquetes, no la salida de la aplicación. Mantén la build determinista.
5) Mantén el CLI de Docker dentro de WSL cuando sea posible
Usar el CLI de Windows para controlar un demonio en WSL2 no siempre es terrible, pero añade límites, diferencias de entorno y confusión de rutas. Ejecutar el CLI en WSL reduce fricción y hace las rutas coherentes.
6) Dimensiona correctamente los recursos de WSL2 (y no dejes a Windows sin recursos)
WSL2 felizmente consumirá RAM para cache, luego la liberará… a veces… eventualmente. Si no haces nada, podrías tener buen rendimiento hasta que no lo tengas. Añade una política de recursos para que tu portátil siga siendo un portátil.
Estrategia típica: limitar memoria, establecer swap razonable y evitar dar a WSL todos los núcleos si Windows necesita mantenerse interactivo.
7) Sé deliberado con el antivirus y el indexado
Cuando Windows Defender (o protección endpoint corporativa) analiza los mismos archivos que estás golpeando vía interoperabilidad WSL2, obtienes lentitud “misteriosa”. Las exclusiones pueden ayudar, pero hazlas con tu equipo de seguridad, no en pánico a las 2 a.m.
8) Trata la configuración de durabilidad de las bases de datos como un contrato dev/prod
Si relajas la durabilidad (fsync off, commit asíncrono), tu BD de dev será rápida y también capaz de perder datos cuando la VM estornude. Eso está bien para desarrollo local; es inaceptable para pruebas de integración que pretendan imitar producción.
Decide qué haces y configura en consecuencia.
9) Arregla DNS si los pulls y las conexiones son lentos
En redes corporativas, DNS puede ser el villano oculto. WSL2 tiene su propio setup de resolvers, Docker tiene su propio DNS interno y los clientes VPN adoran parchear cosas en tiempo de ejecución.
Regla: si nslookup es lento dentro de un contenedor, arregla DNS primero. No toques perillas de almacenamiento hasta que la resolución de nombres esté por debajo de 100ms para dominios comunes.
10) Mantén los watchers donde inotify funcione
Los observadores de archivos multiplican el rendimiento: inotify significa cambios basados en eventos; el sondeo significa “escanear el planeta repetidamente”. Coloca los árboles observados en ext4 dentro de WSL2, reduce el alcance y configura tus herramientas para evitar observar directorios de dependencias.
11) Podar y compactar intencionalmente, no por ritual
Los artefactos de Docker se acumulan. Las imágenes de disco de WSL2 crecen. Podar puede ayudar, pero podar sin cuidado destruye estado y hace perder tiempo reconstruyendo. Establece una cadencia: podar cache de builds semanalmente, limpiar volúmenes abandonados mensualmente y compactar imágenes de disco cuando hayan crecido masivamente por churn.
Broma #2: “Docker prune” no es una estrategia de rendimiento; es una confesión de que no sabes qué está usando el disco.
Tres mini-historias corporativas desde el campo
1) Incidente causado por una suposición errónea: “Está en mi SSD, así que es rápido”
Un equipo con el que trabajé cambió de Mac a portátiles Windows por razones de compra. Docker Desktop + WSL2 parecía la forma más fácil de mantener su flujo de trabajo de desarrollo “igual”. Clonaron repositorios en C:\Users\..., los abrieron en su IDE de Windows y dejaron que WSL2 y los contenedores hicieran el resto.
En una semana, los síntomas estaban por todas partes: tests con timeout, modo watch que no reconstruía y un aumento de tickets de “Docker está roto”. Sus logs estaban limpios. Sus contenedores saludables. Pero cada desarrollador describía lo mismo: operaciones pequeñas lentas, y la lentitud empeoraba durante el día de forma no lineal.
La suposición errónea fue simple: “el código está en un SSD, así que el I/O de archivos no puede ser el cuello de botella.” Olvidaron que el código no lo accedían como NTFS crudo por los procesos Linux. Se accedía a través de un límite, con traducción y semánticas de metadatos encima.
Corrimos una prueba tonta: crear 20,000 archivos pequeños en /mnt/c y en ~. La diferencia fue tan grande que nadie discutió. Movieron repos a WSL, usaron integración de editor consciente de WSL y montaron por bind desde la ruta Linux hacia los contenedores.
La parte dramática no fue la solución. Fue lo rápido que la narrativa “Docker es lento” se desvaneció una vez que el flujo de trabajo dejó de hacer round-trips por las semánticas del sistema de archivos de Windows para cada stat().
2) Optimización que se volvió en contra: “Pongamos todo en volúmenes”
Otra organización vio los bind mounts como “la parte lenta”, lo cual a menudo es cierto. Así que lo hicieron todo: todo se movió a volúmenes nombrados, incluido el código fuente. Su archivo Compose creó un volumen por servicio, luego usaban docker cp para sincronizar código al volumen y ejecutaban la app desde allí.
El rendimiento mejoró inmediatamente. Las builds fueron más rápidas. Las instalaciones de Node dejaron de hacer timeout. Se dieron vueltas de victoria.
Luego el contraefecto: la experiencia de desarrollador se degradó. Las ediciones incrementales no siempre se propagaban de forma predecible. Depurar se volvió incómodo porque la fuente de la verdad ahora era “código en un volumen”, no “código en un repo”. Algunos desarrolladores tenían código obsoleto en volúmenes y pasaron horas persiguiendo bugs fantasma que “nadie más podía reproducir”.
Peor aún, el escaneo de seguridad y las herramientas de licencias esperaban inspeccionar el repo en disco. Ahora necesitaban una nueva canalización para escanear el contenido del volumen, que nadie quería asumir. El rendimiento ganó, pero el flujo de trabajo se volvió frágil.
El compromiso estable fue aburrido: bind mount del código (desde ext4 en WSL), mantener directorios de churn (dependencias, cachés, datos BD) en volúmenes nombrados y conservar una única fuente de la verdad: tu checkout de git.
3) Práctica aburrida pero correcta que salvó el día: “Mide el límite, luego estandariza”
Un equipo de plataforma que soportaba múltiples equipos de producto vio quejas recurrentes: “Docker es lento en Windows”. Podrían haber escrito una página wiki y rezado. En su lugar crearon un script diagnóstico mínimo y lo hicieron parte del onboarding.
Hacía tres cosas: un benchmark de creación de archivos pequeños bajo ~ y bajo /mnt/c, un benchmark de bind-mount en contenedor y una medición de tiempo de búsqueda DNS. Imprimía resultados con umbrales y una acción recomendada (“mover repo”, “usar volumen nombrado”, “comprobar DNS de VPN”).
Esta práctica no fue glamorosa. Pero previno meses de pérdida de productividad de bajo grado. Los nuevos contratados aprendían el flujo esperado en el día uno: mantener repos en WSL, usar herramientas remotas, no bind-mount rutas de Windows y no culpar a Docker por DNS.
Cuando regresiones de rendimiento ocurrían tras actualizaciones de Windows, tenían resultados base para comparar. Así evitas tratar el rendimiento como folclore.
Errores comunes: síntoma → causa raíz → solución
1) “npm install tarda una eternidad”
Síntoma: instalaciones que tardan minutos; CPU baja; actividad de disco visible pero no saturada.
Causa raíz: el árbol de dependencias vive en /mnt/c o está bind-mounted desde una ruta de Windows; las operaciones de metadatos son caras.
Solución: mueve el repo a ext4 en WSL; coloca node_modules en un volumen nombrado; asegura que el contexto de build ignore dependencias.
2) “La observación de archivos es poco fiable o consume CPU”
Síntoma: hot reload no detecta cambios; ventiladores acelerados; watchers muestran alta CPU.
Causa raíz: los eventos de inotify no fluyen a través del límite; la herramienta cae a sondeo de árboles enormes.
Solución: mantén los directorios observados en el sistema de archivos WSL; reduce el alcance de la observación; evita observar directorios de dependencias; configura tus herramientas para WSL.
3) “Docker build es lento incluso con caché”
Síntoma: “load build context” domina; el caché falla con frecuencia.
Causa raíz: contexto enorme, mal .dockerignore, build desde rutas de Windows; marcas de tiempo o archivos generados que cambian capas.
Solución: .dockerignore agresivo; construir desde rutas WSL; usar cache mounts de BuildKit; estabilizar inputs.
4) “La base de datos en un contenedor es dolorosamente lenta”
Síntoma: alta latencia en consultas simples; muchas escrituras en disco; paradas ocasionales.
Causa raíz: directorio de datos de BD en bind mount que cruza el límite de Windows; configuraciones de durabilidad amplifican el coste de I/O; presión de memoria provoca churn de checkpoint.
Solución: almacena los datos de la BD en un volumen nombrado; asigna más memoria; para dev solo, considera durabilidad relajada (con conocimiento de causa).
5) “Las descargas de imágenes son lentas; builds se cuelgan en apt/apk/npm”
Síntoma: pasos de red se quedan; reintentos; funciona en Wi‑Fi de casa, falla en VPN.
Causa raíz: latencia DNS o resolvedor roto en WSL2/Docker; interacciones con proxy/VPN corporativa.
Solución: mide DNS desde dentro del contenedor; ajusta la configuración de DNS; coordina con IT sobre split DNS/policies de proxy.
6) “Todo iba bien y luego se volvió lento con el tiempo”
Síntoma: degradación gradual; uso de disco aumenta; podar “ayuda” temporalmente.
Causa raíz: crecimiento del VHDX, imágenes/volúmenes acumulados, churn de dependencias; posible fragmentación y poco espacio libre.
Solución: limpia con intención (volúmenes/imágenes/cache de build); mantén espacio libre; compacta VHDX como operación de mantenimiento programada.
Listas de verificación / plan paso a paso
Lista A: Arregla el flujo de trabajo de desarrollo (victorias rápidas)
- Mueve los repos a WSL:
~/srces tu base. - Ejecuta el CLI de Docker en WSL: reduce confusión de rutas y cruces de límite.
- Bind mount desde rutas WSL solamente: nunca montes por bind rutas
C:\en contenedores Linux si te importa la velocidad. - Pon el churn en volúmenes: datos BD, directorios de dependencias, cachés de paquetes.
- Endurece
.dockerignore: reduce transferencia de contexto e invalidación de caché. - Mide de nuevo: vuelve a ejecutar los benchmarks de archivos pequeños y de montajes para probar la mejora.
Lista B: Estabiliza el comportamiento de recursos de WSL2
- Elige un límite de memoria que mantenga Windows responsivo y WSL productivo.
- Configura swap intencionalmente (no cero, no infinito). Swap oculta problemas hasta que deja de hacerlo.
- Vigila la memoria “available” durante builds y tests; evita intercambio sostenido.
- Deja de ejecutar stacks duplicados: no ejecutes la misma BD en Windows y en contenedores “por si acaso”.
Lista C: Haz builds rápidos y predecibles
- Habilita BuildKit y usa cache mounts donde corresponda.
- Minimiza el contexto; si el contexto es enorme, pagas impuesto en cada build.
- Separa dependencias del código de la app en tu Dockerfile para maximizar aciertos de caché.
- No bind mount de nuevo los outputs de build a Windows a menos que los necesites allí; mantén artefactos calientes en rutas Linux.
Lista D: Depura la lentitud de red sin adivinar
- Mide DNS dentro del contenedor (
nslookuptiming). - Confirma settings de proxy dentro de pasos de build (proxy en tiempo de build != proxy en tiempo de ejecución).
- Vuelve a probar con/sin VPN para aislar efectos de la red corporativa.
- Arregla la configuración del resolvedor antes de cambiar ajustes de almacenamiento de Docker.
Preguntas frecuentes
1) ¿Debo usar WSL2 o backend Hyper-V para Docker Desktop?
Usa WSL2 salvo que tengas una razón de compatibilidad específica para no hacerlo. Los grandes problemas de rendimiento normalmente no son “WSL2 vs Hyper-V”, sino “rutas de Windows vs rutas de Linux”.
2) ¿Es seguro mantener mi repo dentro de WSL? ¿Lo perderé?
Es tan seguro como cualquier entorno de desarrollo local: haz copias y push al remoto. ext4-in-VHDX de WSL es estable, pero no trates “solo local” como plan de durabilidad.
3) ¿Por qué /mnt/c es mucho más lento para herramientas de desarrollo?
Porque cruzas una capa de traducción con semánticas de metadatos diferentes. Las herramientas Linux hacen muchas llamadas pequeñas; el límite hace que cada una cueste más.
4) ¿Necesito desactivar Windows Defender?
No. Puede que necesites exclusiones específicas para directorios de desarrollo si tu política de seguridad lo permite. Coordina con IT/seguridad; desactivaciones aleatorias son cómo se generan tickets emocionantes de incidentes.
5) ¿Son los volúmenes nombrados siempre más rápidos que los bind mounts?
No siempre. Los bind mounts desde ext4 en WSL pueden estar bien. Los bind mounts desde rutas de Windows suelen ser los lentos. Los volúmenes nombrados tienden a ser consistentemente rápidos para directorios con mucho churn.
6) ¿Por qué la transferencia del contexto de mi build de Docker es enorme?
Porque estás enviando demasiado: directorios de dependencias, salidas de build y, a veces, incluso todo el historial .git. Arregla .dockerignore y construye desde rutas WSL.
7) Mi base de datos en contenedor es lenta; ¿debería instalarla en Windows?
En ocasiones es un parche pragmático, pero aumenta la divergencia con producción y complica las herramientas. Prueba primero: volumen nombrado para datos + memoria adecuada + evita montajes desde rutas Windows.
8) ¿Por qué el rendimiento se degrada con el tiempo?
Las imágenes de disco crecen, las cachés se acumulan, los volúmenes se amontonan y el espacio libre disminuye. Además, las cadenas de herramientas cambian y generan más archivos. Trata limpieza y compactación como mantenimiento, no como botón de pánico.
9) ¿Debo ejecutar el IDE en Windows o dentro de WSL?
Cualquiera puede funcionar, pero lo crucial es dónde residen los archivos. IDE de Windows con soporte remoto WSL es un punto dulce común: interfaz Windows, sistema de archivos Linux.
10) ¿Es “desactivar el uso compartido de archivos” una solución?
Es una forma de dejar de usar rutas lentas por accidente. La solución real es estructurar tu flujo de trabajo para no necesitar compartición entre OS para rutas calientes.
Próximos pasos
Si quieres que Docker en Windows con WSL2 se sienta razonable, haz esto en orden:
- Ejecuta los benchmarks (Tarea 2 y Tarea 6). No discutas; mide.
- Mueve el repo a WSL si
/mnt/ces lento. Esto resuelve la mayor clase de problemas. - Cambia directorios calientes a volúmenes nombrados (datos BD, directorios de dependencias, cachés).
- Arregla el bloat del contexto de build y usa el caché de BuildKit intencionalmente.
- Comprueba DNS si los pasos de pulls/builds se quedan; arregla el resolvedor antes de tocar perillas de almacenamiento.
- Dimensiona recursos de WSL y mantén espacio libre sano; al rendimiento le gusta respirar.
Haz eso, y el meme “Docker es lento en Windows” se convierte en lo que debe ser: una queja ocasional, no una característica definitoria de tu día.