Permisos de bind mount de Docker en Windows: la configuración menos dolorosa

¿Te fue útil?

Montas tu proyecto en un contenedor con bind mount. Funciona. Luego deja de funcionar. De repente tu build no puede escribir una caché, tu app no puede crear un archivo de log y todo está o bien propiedad de root o bien muestra “Access is denied.” Bienvenido al valle inquietante de permisos en Windows: parece Unix, huele a Unix y se comporta como un comité.

Si quieres la configuración menos dolorosa, debes elegir una de dos opciones: tratar a Windows como un host prestado (y hacer el trabajo dentro de WSL2), o aceptar la capa de traducción y establecer protecciones. Lo bueno: puedes volverlo aburrido. Lo malo: tienes que ser preciso.

La regla que hace todo más fácil

Mantén los archivos del proyecto dentro del sistema de archivos Linux de WSL2 (por ejemplo, /home/you/project), no en C:\, y haz el bind-mount desde allí.

Eso es todo. Si haces esto, los permisos se comportarán como en Linux porque… es Linux. En el momento en que montas una ruta de Windows en un contenedor Linux, estás pidiendo a dos modelos de permisos, dos semánticas de sistema de archivos y un límite de virtualización que “simplemente se pongan de acuerdo”. No lo harán. Negociarán. Y la negociación es cómo terminas con incidentes a las 3 a. m.

Un chiste corto, como premio: el mapeo de permisos Windows→Linux es como un niño bilingüe traduciendo contratos legales: esfuerzo impresionante, precisión cuestionable.

Si tu equipo insiste en trabajar desde C:\Users\..., aún puedes hacerlo funcional. Simplemente no será lo menos doloroso. Será “dolor con un objetivo de nivel de servicio.”

Cómo se ponen raros los permisos en Windows (qué está pasando)

Los bind mounts no son solo “archivos compartidos dentro de contenedores”

Un bind mount es que el contenedor vea un directorio que físicamente está almacenado en otro lugar. En hosts Linux esto es directo: las semánticas del VFS del kernel coinciden; los UID/GID y los bits de modo significan lo que los contenedores esperan.

En Windows, Docker Desktop ejecuta efectivamente una VM Linux (motor WSL2) y luego presenta los archivos de Windows a esa VM a través de una capa de sistema de archivos compartido. Tu contenedor no está montando NTFS directamente; está montando una traducción de NTFS.

Dónde se nota la discordancia

  • UID/GID vs SIDs/ACLs: Linux usa IDs numéricos de usuario/grupo más bits de modo. Windows usa SIDs y ACLs. El mapeo es con pérdida.
  • Semántica de chmod/chown: En un sistema de archivos “real” de Linux, chmod y chown normalmente hacen lo que indican. En montajes respaldados por Windows pueden aparentar tener éxito pero no persistir, o fallar con “Operation not permitted.”
  • Bit ejecutable: Linux necesita el bit ejecutable para scripts/binarios. Windows no. Así que la capa de traducción lo adivina.
  • Sensibilidad a mayúsculas: Linux es sensible a mayúsculas, Windows normalmente no lo es (aunque Windows moderno puede activar sensibilidad por directorio). Herramientas que asumen un modelo pueden comportarse de forma extraña bajo el otro.
  • Inotify / vigilancia de archivos: Servidores de desarrollo y sistemas de build dependen de eventos del sistema de archivos. Los eventos a través del límite pueden retrasarse o perderse. Eso parece “el hot reload está roto”.

Dos “configuraciones de Windows” distintas que la gente confunde

Configuración A: Trabajar en el sistema de archivos de WSL2 (mejor). Tu código vive bajo /home en WSL. El motor Docker está basado en WSL2. Los contenedores montan rutas de Linux. Dejas de pensar en permisos de Windows, en su mayoría.

Configuración B: Trabajar en el sistema de archivos de Windows (a veces necesario). Tu código vive en C:\. Docker monta /mnt/c/... en los contenedores. Ahora estás en la tierra de la traducción. Necesitas políticas: qué usuario ejecuta en el contenedor, qué directorios son escribibles y cómo evitar artefactos “propiedad de root”.

Hay un aforismo de fiabilidad que vale la pena tener en el escritorio. Aquí está una idea parafraseada, porque la redacción exacta varía: idea parafraseada: “Los sistemas complejos fallan de formas complejas; la simplicidad es una característica de fiabilidad.” — John Ousterhout (idea parafraseada)

Arquitectura de referencia menos dolorosa (qué hacer)

Decisión 1: Coloca el espacio de trabajo en WSL2

Haz de WSL2 tu “sistema de archivos de estación de trabajo de desarrollador”, incluso si Windows es tu escritorio. Aun así puedes usar VS Code o JetBrains en Windows y editar archivos dentro de WSL mediante su integración WSL. Tu tooling de Git, cadenas de herramientas de lenguajes y permisos de archivos se alinearán.

Decisión 2: Ejecuta contenedores como un usuario no root que coincida con tu UID/GID de WSL

Si tu contenedor escribe archivos en un bind mount, quieres que esos archivos sean propiedad de tu usuario de WSL, no de root. El patrón pragmático:

  • Descubre tu UID/GID en WSL (id).
  • Construye una imagen que cree un usuario con ese UID/GID (o usa lógica en el entrypoint).
  • En Compose, establece user: "UID:GID" para contenedores de desarrollo.

Decisión 3: Usa volúmenes con nombre para directorios con mucha escritura

Los bind mounts son excelentes para código fuente. Pueden ser terribles para cachés de dependencias y bases de datos. Pon estos en volúmenes Docker:

  • Node: node_modules suele ser más rápido en un volumen.
  • Python: .venv, caché de pip.
  • Rust/Go/Java: cachés de compilación y directorios de artefactos.
  • Bases de datos: siempre usa volúmenes a menos que te encanten las historias de corrupción.

Decisión 4: Mantén montajes de Windows mayormente de solo lectura si debes usarlos

Si la política corporativa o herramientas te obligan a mantener el código en C:\, trátalo como una unidad de red compartida: léelo, compila desde allí si hace falta, pero no escribas muchos archivos pequeños a través de él. Redirige cachés a volúmenes o almacenamiento local del contenedor.

Segundo chiste corto: La forma más rápida de arreglar el rendimiento de bind mounts en Windows es dejar de bind-montar Windows. No es elegante, pero es efectivo.

Plan de configuración (paso a paso)

1) Confirma que usas el motor WSL2

Docker Desktop puede ejecutarse con distintos backends según la versión y configuración. Si quieres el camino menos doloroso, necesitas WSL2.

2) Crea un directorio de proyecto en WSL2

Clona tu repositorio dentro de WSL, no bajo /mnt/c. Si ya tienes una copia en Windows, clona de nuevo. Sí, parece redundante. No, no lo es.

3) Añade un usuario de desarrollo a tu imagen (o ejecuta como tu UID)

Para imágenes de desarrollo, prefiero compilar el usuario en la imagen porque hace el debugging predecible. Para rápido y sucio, user: "${UID}:${GID}" en Compose está bien.

4) Separa “bind mounts de código fuente” de “volúmenes con mucha escritura”

Monta el código fuente del proyecto. Pon cachés y estado en volúmenes. Esto reduce sorpresas de permisos y hace que el rendimiento dependa menos del puente Windows–WSL.

5) Decide tu política para la propiedad de archivos

Elige una y hazla cumplir:

  • Política A (recomendada): el contenedor escribe con el mismo UID/GID que tu usuario de WSL.
  • Política B: el contenedor escribe como root, y aceptas que las ediciones/limpieza en el host requieran sudo. Es común y es una fuga lenta de frustración.
  • Política C: el contenedor nunca escribe en bind mounts; escribe solo en volúmenes. Excelente para builds herméticos, menos para algunos flujos de desarrollo.

Tareas prácticas: comandos, salidas y decisiones (12+)

Estas son las tareas que realmente ejecuto cuando alguien dice “los bind mounts están rotos”. Cada una incluye: comando, qué significa la salida y qué hacer después.

Task 1: Comprobar que Docker es accesible y en qué contexto estás

cr0x@server:~$ docker context ls
NAME                DESCRIPTION                               DOCKER ENDPOINT               ERROR
default *           Current DOCKER_HOST based configuration   unix:///var/run/docker.sock
desktop-linux       Docker Desktop                            unix:///var/run/docker.sock

Significado: Ves qué endpoint de Docker estás usando. En Windows con WSL2, normalmente usas el contexto Desktop Linux.

Decisión: Si no estás en el contexto esperado, cambia: docker context use desktop-linux. Los problemas de “Permiso denegado” a veces resultan ser “estás hablando con otro motor del que crees”.

Task 2: Confirmar distro y kernel de WSL (WSL2 vs WSL1)

cr0x@server:~$ uname -a
Linux cr0x 5.15.133.1-microsoft-standard-WSL2 #1 SMP Wed Oct 25 00:00:00 UTC 2023 x86_64 GNU/Linux

Significado: El kernel WSL2 está en uso (observa “WSL2”).

Decisión: Si no estás en WSL2, soluciona eso primero. WSL1 tiene comportamiento de sistema de archivos distinto y tiende a empeorar escenarios con Docker Desktop.

Task 3: Encontrar dónde vive realmente tu proyecto

cr0x@server:~$ pwd
/mnt/c/Users/cr0x/work/myapp

Significado: Estás en el sistema de archivos montado de Windows. Espera rarezas de permisos y posiblemente bajo rendimiento.

Decisión: Mueve o re-clona dentro de /home/cr0x/work/myapp. Si la política lo impide, continúa con configuración defensiva (tareas posteriores).

Task 4: Identificar tu UID/GID en WSL

cr0x@server:~$ id
uid=1000(cr0x) gid=1000(cr0x) groups=1000(cr0x),27(sudo),44(video)

Significado: Tu usuario es UID 1000, GID 1000.

Decisión: Usa 1000:1000 como usuario del contenedor para bind mounts de desarrollo, o incorpora esto en la imagen.

Task 5: Reproducir el problema de permisos en un contenedor mínimo

cr0x@server:~$ docker run --rm -it -v "$PWD:/work" alpine:3.20 sh -lc 'id; ls -ld /work; touch /work/.permtest && ls -l /work/.permtest'
uid=0(root) gid=0(root) groups=0(root)
drwxrwxrwx    1 root     root          4096 Jan  3 10:31 /work
-rwxrwxrwx    1 root     root             0 Jan  3 10:31 /work/.permtest

Significado: El contenedor corre como root; el archivo creado es propiedad de root. Los bits de modo son muy permisivos, lo cual es común en montajes respaldados por Windows.

Decisión: Si los artefactos propiedad de root son el problema, ejecuta el contenedor con tu UID/GID.

Task 6: Probar ejecutar el contenedor con tu UID/GID

cr0x@server:~$ docker run --rm -it --user 1000:1000 -v "$PWD:/work" alpine:3.20 sh -lc 'id; touch /work/.uidtest && ls -l /work/.uidtest'
uid=1000 gid=1000
-rwxrwxrwx    1 1000     1000            0 Jan  3 10:32 /work/.uidtest

Significado: La propiedad del archivo ahora coincide con UID/GID 1000 dentro del contenedor. En montajes de sistemas Linux esto normalmente mapea limpiamente. En montajes de Windows aún puedes ver bits de modo permisivos y comportamiento raro, pero al menos dejas de producir archivos propiedad de root.

Decisión: Incorpora user: "1000:1000" en Compose para desarrollo, o implementa creación de usuario en Dockerfile.

Task 7: Comprobar si chmod/chown realmente funcionan en tu montaje

cr0x@server:~$ docker run --rm -it -v "$PWD:/work" alpine:3.20 sh -lc 'touch /work/chmodtest; chmod 600 /work/chmodtest; ls -l /work/chmodtest'
-rwxrwxrwx    1 root     root             0 Jan  3 10:33 /work/chmodtest

Significado: chmod no se aplicó (sigue 777-ish). Eso es típico en montajes respaldados por Windows sin soporte de metadata.

Decisión: Deja de confiar en bits de modo para semánticas de seguridad en ese montaje. Usa controles a nivel de aplicación, o mueve el workspace al sistema de archivos WSL donde chmod funciona.

Task 8: Verificar si estás en montaje de Windows (DrvFs) vs sistema de archivos Linux nativo

cr0x@server:~$ df -T .
Filesystem     Type 1K-blocks      Used Available Use% Mounted on
C:\            9p  976762876 123456789 853306087  13% /mnt/c

Significado: El tipo de sistema de archivos es 9p (común para montajes de Windows en WSL2). Este es el límite de traducción.

Decisión: Espera rarezas. Si necesitas permisos Unix correctos, muévete a ext4 dentro de WSL2 (/home), no a /mnt/c.

Task 9: Inspeccionar opciones de montaje desde dentro del contenedor

cr0x@server:~$ docker run --rm -v "$PWD:/work" alpine:3.20 sh -lc 'mount | grep " /work " || true'
/dev/sdd on /work type 9p (rw,relatime,dirsync,aname=drvfs;path=C:\Users\cr0x\work\myapp;uid=0;gid=0;metadata;symlinkroot=/mnt/,mmap,access=client,msize=65536,trans=fd,rfd=8,wfd=8)

Significado: Puedes ver literalmente que estás en un montaje estilo DrvFs/9p, además de flags como metadata que influyen en el comportamiento de permisos.

Decisión: Si falta metadata, chmod/chown son aún menos significativos. Si metadata está presente, el comportamiento de permisos puede mejorar, pero aún no será idéntico a ext4.

Task 10: Comprobación de Compose: ¿qué usuario ejecuta el servicio?

cr0x@server:~$ docker compose exec app sh -lc 'id; umask'
uid=0(root) gid=0(root) groups=0(root)
0022

Significado: Tu servicio de Compose corre como root. Umask es normal, pero irrelevante si los bits de modo no persisten.

Decisión: Establece user: en Compose o arregla la imagen. Para desarrollo, root rara vez vale la pena por el daño colateral.

Task 11: Medir rápidamente el rendimiento del montaje (trabajo con muchos archivos pequeños)

cr0x@server:~$ docker run --rm -v "$PWD:/work" alpine:3.20 sh -lc 'time sh -c "i=0; while [ $i -lt 2000 ]; do echo $i > /work/t_$i; i=$((i+1)); done"'
real    0m6.842s
user    0m0.187s
sys     0m1.214s

Significado: Crear 2000 archivos pequeños tomó varios segundos. En un sistema de archivos Linux nativo, esto suele ser mucho más rápido.

Decisión: Si tu herramienta de build escribe miles de archivos (hola, Node y Java), mueve cachés a volúmenes o mueve todo el proyecto al sistema de archivos WSL.

Task 12: Comparar con un volumen nombrado de Docker (experimento de control)

cr0x@server:~$ docker run --rm -v perfvol:/work alpine:3.20 sh -lc 'time sh -c "i=0; while [ $i -lt 2000 ]; do echo $i > /work/t_$i; i=$((i+1)); done"'
real    0m0.403s
user    0m0.179s
sys     0m0.205s

Significado: El volumen nombrado es dramáticamente más rápido para cargas de trabajo con muchas escrituras. Además: los permisos siguen las semánticas Linux dentro del volumen.

Decisión: Pon cachés de build y directorios de dependencias en volúmenes. Mantén bind mounts para código fuente y configuraciones.

Task 13: Confirmar la propiedad de archivos en el lado del host (WSL) después de escribir desde el contenedor

cr0x@server:~$ ls -ln .uidtest .permtest 2>/dev/null
-rwxrwxrwx 1 1000 1000 0 Jan  3 10:32 .uidtest
-rwxrwxrwx 1    0    0 0 Jan  3 10:31 .permtest

Significado: Existen archivos propiedad de root porque antes se escribió como root; el archivo mapeado por UID es propiedad de 1000.

Decisión: Si hay artefactos de root, corrige el usuario del contenedor y limpia el directorio (puede requerir sudo en WSL).

Task 14: Inspeccionar un volumen y confirmar que no está accidentalmente bind-montado

cr0x@server:~$ docker volume inspect perfvol
[
  {
    "CreatedAt": "2026-01-03T10:34:12Z",
    "Driver": "local",
    "Labels": null,
    "Mountpoint": "/var/lib/docker/volumes/perfvol/_data",
    "Name": "perfvol",
    "Options": null,
    "Scope": "local"
  }
]

Significado: Es un volumen local de Docker dentro de la VM Linux. Bien. Por eso es rápido y con permisos correctos.

Decisión: Usa volúmenes para rutas críticas. Si alguien insiste “el bind mount está bien”, muéstrales los números de Task 11 vs Task 12.

Guía rápida de diagnóstico

Cuando algo está lento o “permiso denegado”, no necesitas una semana de teoría. Necesitas tres comprobaciones que aíslen el dominio.

Primero: ¿Estás montando archivos de Windows o de Linux?

  • Ejecuta pwd y df -T . en WSL.
  • Si estás bajo /mnt/c y el tipo de sistema de archivos parece 9p, estás en tierra de traducción.
  • Decisión: Si el problema es corrección de permisos o rendimiento, mueve el workspace a /home (ext4) como la solución por defecto.

Segundo: ¿Qué usuario está escribiendo en el bind mount?

  • Ejecuta docker compose exec app id (o docker run ... id).
  • Si es root, producirás artefactos propiedad de root y a veces fallos de escritura dependiendo de la capa de montaje.
  • Decisión: Fuerza UID/GID vía user: en Compose o crea un usuario coincidente en la imagen.

Tercero: ¿Es un problema de corrección o de rendimiento?

  • Ejecuta el benchmark de archivos pequeños (Task 11) en el bind mount y compáralo con un volumen (Task 12).
  • Decisión: Si el volumen es mucho más rápido, no “tuneas permisos”. Cambias el layout de almacenamiento: bind mount para código, volumen para churn.

Comprobación extra: ¿Se espera que chmod/chown funcionen aquí?

  • Prueba el test de chmod (Task 7). Si no funciona, deja de diseñar flujos de trabajo alrededor de bits POSIX en ese montaje.
  • Decisión: Muévete al sistema de archivos WSL o mantiene archivos sensibles fuera de montajes de Windows.

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

1) “Permission denied” al escribir en un bind mount

Síntoma: La app falla al crear archivos bajo /work o /app con EACCES.

Causa raíz: El contenedor corre como un usuario no root, pero el directorio bind-monteado mapea a una ACL de Windows que no permite escrituras como esperas; o el directorio es propiedad de root porque ejecuciones anteriores lo crearon como root.

Solución: Mueve el proyecto al sistema de archivos WSL. Si no es posible, ejecuta el contenedor con tu UID/GID de WSL y limpia los archivos propiedad de root (en WSL) con sudo.

2) “chmod: Operation not permitted” o chmod parece no hacer nada

Síntoma: chmod devuelve error, o ls -l nunca cambia.

Causa raíz: Los montajes respaldados por Windows no soportan completamente semánticas de metadata POSIX (o no está habilitado el soporte de metadata).

Solución: No confíes en chmod en montajes de Windows. Almacena scripts/binarios en el sistema de archivos WSL. Si necesitas fidelidad Unix, usa ext4 en WSL2 o un volumen nombrado.

3) Hot reload/vigilancia de archivos no detecta cambios

Síntoma: Node/webpack, recargadores de Python o live reload de Go no se activan al guardar.

Causa raíz: La traducción de eventos de archivos entre Windows ↔ WSL2 ↔ contenedor es inconsistente, especialmente en /mnt/c.

Solución: Mantén el workspace en el sistema de archivos WSL. Si estás atado a montajes de Windows, configura watchers por sondeo (más lentos pero fiables) o mueve solo los directorios vigilados a WSL.

4) El build es dolorosamente lento y la CPU parece inactiva

Síntoma: Instalar dependencias o compilar tarda una eternidad; el uso de CPU es bajo.

Causa raíz: I/O de muchos archivos pequeños está limitado por la capa de sistema de archivos compartido; cada stat/open/write cruza límites.

Solución: Pon rutas con mucha escritura en volúmenes; mueve el proyecto a WSL filesystem; evita montar directorios de dependencias desde Windows.

5) “Todo es propiedad de root” y Git empieza a quejarse

Síntoma: Ves propiedad root, y Git se queja sobre ownership dudoso o se niega a operar en algunas configuraciones.

Causa raíz: El contenedor corrió como root y escribió en el bind mount; la propiedad persistió. En entornos mixtos, checks de seguridad se activan.

Solución: Deja de ejecutar como root para desarrollo. Limpia el workspace y haz determinística la propiedad mediante mapeo UID/GID.

6) Contenedor de base de datos funciona hasta que deja de hacerlo

Síntoma: Postgres/MySQL arranca y luego crashea con problemas de fsync o errores de permisos; o el rendimiento se desploma.

Causa raíz: La base de datos está bind-monteada a un sistema de archivos Windows; las garantías POSIX y semánticas de fsync no son las mismas.

Solución: Usa siempre un volumen nombrado para datos de bases de datos, especialmente en hosts Windows.

7) “Pero funciona en mi máquina” en un equipo con muchos Windows

Síntoma: El entorno de un dev está bien; otro ve errores de permisos y lentitud.

Causa raíz: Los workspaces están en lugares distintos (WSL vs /mnt/c), diferentes ajustes de Docker Desktop y distintos usuarios por defecto en imágenes.

Solución: Estandariza: ubicación del workspace, usuario en Compose y estrategia de volúmenes. Trata el “entorno de dev” como infra parecida a producción con una especificación.

Tres micro-historias corporativas (cómo falla en la vida real)

Micro-historia 1: El incidente causado por una suposición equivocada

Un equipo tenía un setup de desarrollo multiplataforma: laptops Linux, macOS y un gran contingente Windows. Lanzaron un nuevo entorno de desarrollo containerizado con un bind mount del repo y un contenedor ejecutándose como root “para evitar problemas de permisos.” A nadie le gustaba, pero funcionaba lo suficiente—hasta que dejó de funcionar.

La suposición equivocada fue sutil: asumieron “root en el contenedor siempre puede escribir en el bind mount.” En hosts Linux eso suele ser verdadero (salvo SELinux/AppArmor). En montajes respaldados por Windows, root no anula mágicamente las decisiones de ACL de Windows. La capa de traducción puede bloquear operaciones, y a veces lo hace de forma inconsistente según cómo esté configurada la compartición.

Se manifestó como fallos intermitentes en un generador de código que escribía en el repo. La mitad de los desarrolladores Windows vio “permission denied”, la otra mitad no, y el generador tenía un manejo de errores pobre. Reintentó agresivamente y dejó salidas parciales. De repente el repo contenía una mezcla de archivos generados con propiedades y timestamps inconsistentes. Los builds fallaban, luego tenían éxito, luego fallaban otra vez.

Lo trataron como una herramienta inestable. No lo era. Era semántica de almacenamiento. La solución fue aburrida: mover el workspace a WSL2, ejecutar contenedores como el usuario de WSL y almacenar artefactos generados en un volumen con un paso de export controlado. El generador dejó de “fallar aleatoriamente”, porque ya no estaba negociando con NTFS a través de un tubo estrecho.

Micro-historia 2: La optimización que salió mal

Un grupo quería instalaciones más rápidas para un monorepo JavaScript. Alguien propuso bind-montar node_modules desde el host para que las dependencias persistieran entre rebuilds del contenedor. Se planteó como “caché gratis.” A la gerencia le encantó lo gratis.

En máquinas Linux fue decente. En máquinas Windows fue un accidente a cámara lenta. La instalación de dependencias implicaba crear y borrar enorme cantidad de archivos pequeños, además de symlinks. La capa de montaje respaldada por Windows lo manejó, pero con alto overhead. Las instalaciones se volvieron más lentas que instalaciones limpias dentro del filesystem del contenedor, y los watchers de archivos se volvieron poco fiables.

Peor: los permisos se convirtieron en un desastre. Algunos desarrolladores tenían contenedores corriendo como root, otros como usuario. El mismo directorio de dependencias terminó con propiedad mixta. Las herramientas comenzaron a fallar con EPERM y los desarrolladores “lo arreglaban” borrando el directorio e instalando de nuevo—una y otra vez.

Finalmente revirtieron la “optimización” y la reemplazaron por un volumen nombrado para cachés de dependencias, y un volumen separado para node_modules cuando era necesario. El repo quedó bind-montado, pero la churn vivió en volúmenes. El equipo dejó de pagar un impuesto de I/O a la capa de montaje de Windows. La lección quedó: cachear no es gratis si lo cacheas en el lugar equivocado.

Micro-historia 3: La práctica aburrida que salvó el día

Un equipo empresarial regulado tenía una práctica sorprendentemente sensata: todo cambio en el entorno de desarrollo requería una pequeña actualización tipo “ops” en una lista de verificación. No una novela burocrática—solo una página con comandos estándar para verificar corrección, rendimiento y propiedad.

Cuando adoptaron Docker Desktop con WSL2, añadieron tres comprobaciones: (1) la ruta del proyecto está bajo /home, (2) docker compose exec app id coincide con el UID del desarrollador, y (3) el benchmark de archivos pequeños en un bind mount no supera un umbral aproximado. Si lo hacía, la checklist indicaba mover cachés a volúmenes.

Meses después, una actualización de Docker Desktop cambió comportamiento en un subconjunto de máquinas. La gente empezó a quejarse de builds lentos y comportamiento raro de chmod. La checklist dejó claro: el workspace había migrado de nuevo a /mnt/c porque un nuevo contratado siguió una guía antigua de onboarding que asumía rutas nativas de Windows.

La práctica que salvó el día fue aburrida: ubicación del workspace forzada y un rito diagnóstico corto. Arreglaron la doc de onboarding, re-clonaron repos en WSL y el incidente murió en silencio. Nada heroico. Ese es el mejor tipo de resultado operativo.

Hechos interesantes y contexto histórico (8 puntos)

  1. El modelo original de Docker asumía un kernel Linux. El soporte en Windows llegó después y requirió o bien contenedores Windows o bien una VM Linux para contenedores Linux.
  2. Docker Desktop en Windows suele ejecutar contenedores Linux dentro de WSL2. Por eso las semánticas Linux son “reales” dentro de la VM pero se vuelven “traducidas” cuando tocas archivos de Windows.
  3. WSL1 y WSL2 son fundamentalmente diferentes. WSL1 era una capa de traducción de syscalls; WSL2 es un kernel Linux real en una VM ligera, lo que cambia dramáticamente el comportamiento del sistema de archivos.
  4. Los permisos NTFS son basados en ACL, no en bits de modo. El mapeo a bits rwx es aproximado, y herramientas que asumen semánticas POSIX pueden confundirse.
  5. La sensibilidad a mayúsculas en Windows ha evolucionado. Windows puede habilitar sensibilidad por directorio, pero muchas toolchains aún asumen el comportamiento por defecto insensible de NTFS.
  6. Vigilar archivos es una trampa de portabilidad. Inotify es nativo de Linux; Windows tiene APIs distintas; cruzar eventos entre capas es inherentemente imperfecto y ha mejorado con el tiempo pero sigue siendo una molestia común.
  7. Los bind mounts no son “volúmenes pero más simples.” Heredan la semántica del sistema de archivos host, lo cual es genial en Linux y complicado en Windows/macOS.
  8. Los volúmenes nombrados de Docker suelen ser más rápidos porque permanecen dentro del filesystem Linux. Cruzar menos límites significa menos sorpresas y menos acantilados de rendimiento.

Listas de verificación / plan paso a paso

Lista A: Nuevo portátil Windows, configuración de desarrollo Docker menos dolorosa

  1. Habilitar WSL2 e instalar una distro Linux.
  2. Instalar Docker Desktop y asegurar que use el motor WSL2.
  3. En WSL, crear un directorio de workspace: mkdir -p ~/work.
  4. Clonar repos en ~/work, no en /mnt/c.
  5. Decidir política UID/GID: el contenedor corre como tu UID/GID para desarrollo.
  6. Actualizar Compose: bind mount del código, volúmenes para cachés y datos.
  7. Añadir una prueba rápida: que el contenedor cree un archivo en el bind mount y la propiedad sea correcta.

Lista B: Patrones de archivo Compose que reducen dolor

  • Bind mount: solo el código del repo y la configuración mínima.
  • Volúmenes: directorios de dependencias, salidas de build, datos de DB, cachés de paquetes.
  • Mapeo de usuario: establecer user: "${UID}:${GID}" para servicios de dev que escriben en montajes.
  • Entrypoints: evitar scripts que asuman que chmod funciona en bind mounts.
  • Health checks: si tu app escribe estado, verifica que puede escribir al iniciar y falla con claridad si no puede.

Lista C: Cuando debes mantener el repo en C:\

  1. Acepta que chmod/chown pueden ser poco fiables; diseña alrededor de eso.
  2. Ejecuta contenedores como no-root cuando sea posible para evitar artefactos propiedad de root.
  3. Mueve todas las rutas con mucha escritura a volúmenes.
  4. Si la vigilancia de archivos falla, usa watchers por sondeo y documenta la compensación.
  5. Mantén tus “tests de corrección de bind mount” en CI o scripts de onboarding para detectar la deriva temprano.

Preguntas frecuentes

1) ¿Por qué chmod no funciona en mi bind mount desde Windows?

Porque el sistema de archivos subyacente es NTFS expuesto a través de una capa de traducción. Los bits de modo no son un concepto nativo allí, por lo que pueden no persistir o ser emulados.

2) ¿Ejecutar el contenedor como root es la solución más simple?

Es la forma más simple de crear otro conjunto de problemas. Obtendrás artefactos propiedad de root, limpieza confusa y a veces los mismos fallos de escritura en montajes respaldados por Windows.

3) ¿Cuál es la forma más limpia de evitar archivos propiedad de root?

Ejecuta el contenedor con tu UID/GID de WSL y mantén el workspace en el sistema de archivos WSL. Esa combinación es lo más cercano a “se comporta como Linux”.

4) ¿Debo usar bind mounts o volúmenes para bases de datos en Windows?

Volúmenes. Siempre. Las bases de datos asumen ciertas garantías del sistema de archivos y características de rendimiento que los bind mounts respaldados por Windows no proveen de forma fiable.

5) Necesito editar archivos con apps de Windows. ¿Puedo mantener el repo en WSL?

Sí, siempre que tu editor soporte integración WSL o acceso remoto al sistema de archivos. Ese es el camino moderno y soportado para desarrollo serio en Windows con contenedores Linux.

6) Mi build es lento, pero los permisos parecen correctos. ¿Qué hago ahora?

Haz el benchmark de I/O de archivos pequeños en bind mount vs volumen (Tasks 11 y 12). Si el volumen es mucho más rápido, mueve cachés y directorios de dependencias a volúmenes.

7) ¿Por qué falla la vigilancia de archivos/hot reload solo en Windows?

Porque las APIs de eventos de archivos difieren y los eventos deben cruzar capas (Windows ↔ WSL2 ↔ contenedor). Cuanto más lejos estén tus archivos del filesystem nativo del contenedor, más probable es que fallen los watchers.

8) ¿Puedo “arreglar” esto con un único ajuste de Docker Desktop?

No de forma fiable. Puedes mejorar cosas con configuración, pero la solución más efectiva es arquitectónica: mantener el código en el filesystem de WSL y usar volúmenes para rutas con mucha escritura.

9) ¿Y si mi equipo usa sistemas operativos mixtos?

Estandariza el comportamiento del usuario del contenedor y el layout de almacenamiento. Tu Compose no debería asumir semánticas solo-Linux si los hosts Windows son de primera clase. Documenta el requisito del workspace en WSL.

Conclusión: próximos pasos que puedes hacer hoy

Si quieres lo “menos doloroso”, deja de negociar con NTFS por semánticas Linux. Pon el repo en el sistema de archivos Linux de WSL2, haz el bind mount desde allí y ejecuta los contenedores de desarrollo con tu UID/GID. Luego usa volúmenes nombrados para cualquier cosa que escriba mucho.

Pasos concretos:

  1. Comprueba la ruta de tu repo. Si está bajo /mnt/c, re-clona en ~/work.
  2. Configura user: "1000:1000" (o tu UID/GID real) en Compose para servicios de desarrollo que escriben en montajes.
  3. Mueve directorios de dependencias y cachés a volúmenes y vuelve a ejecutar tu build. Compara los números de Task 11 vs Task 12.
  4. Añade un pequeño script de prueba en el onboarding: crea un archivo en el bind mount y verifica la propiedad y la capacidad de escritura.

No intentas ganar una discusión con Windows. Intentas ejecutar flujos de desarrollo de grado de producción en un portátil. Haz el layout de almacenamiento aburrido, y el resto seguirá.

← Anterior
Por qué dos GPUs suelen ser peores que una (las razones reales)
Siguiente →
ZFS zfs list -o space: La vista que explica ‘¿A dónde se fue?’

Deja un comentario