Bind mounts on Linux are free — the container reads and writes the host's filesystem directly. On Mac and Windows, they go through a virtualized filesystem because Docker Desktop runs Linux in a VM, and your Mac or Windows filesystem isn't natively visible to that VM. The bridge between them is what slows things down.
The good news: it has gotten much better. The default Docker Desktop file-sharing mode (VirtioFS on Mac since 4.22, August 2023) is significantly faster than the gRPC FUSE it replaced. The remaining slowness mostly hits dependency directories like node_modules — the named-volume trick handles those.
Why it's slow
On Linux, when a container writes to /host/path:/path/in/container, the kernel just maps one path to another. Same filesystem, native speed.
On Mac and Windows, the Linux container is inside Docker Desktop's VM. Your project lives on macOS (APFS) or Windows (NTFS). For the container to see your files:
- macOS/Windows kernel reads the file.
- Docker Desktop's file-sharing layer (VirtioFS, gRPC FUSE, or the older osxfs) translates between the host filesystem and the Linux VM's filesystem.
- Linux kernel hands the bytes to the container.
Every file read or write crosses that translation boundary. For a single file, it's a millisecond of overhead. For node_modules with 100,000 small files, those milliseconds add up to seconds or minutes.
Fix 1: confirm you're on VirtioFS (Mac)
Docker Desktop → Settings → General → "Choose file sharing implementation for your containers"
Options (varies by version):
- VirtioFS — default in Docker Desktop 4.22+ (August 2023). Significantly faster.
- gRPC FUSE — older default, slower.
- osxfs (legacy) — much older, very slow.
Pick VirtioFS if you're on a current Docker Desktop. If for some reason it's slower for your workload, fall back to gRPC FUSE.
On Windows, the WSL2 backend handles file sharing via 9P (the WSL filesystem protocol), and there isn't a comparable switch — the path matters more (see below).
Fix 2: named volume for node_modules
The classic trick. Your project source bind-mounts to /app, but node_modules mounts as a named volume:
services:
app:
image: node:22
working_dir: /app
volumes:
- .:/app # bind-mount source for hot reload
- app-node_modules:/app/node_modules # named volume — Docker-managed
command: npm run dev
ports:
- "3000:3000"
volumes:
app-node_modules:Why this works: the bind mount of .:/app overlays your host source onto the container's /app. The named volume of app-node_modules:/app/node_modules then overlays a Docker-managed location (which is in the VM's native filesystem, no translation needed) on top. So inside the container, node_modules is fast.
Trade-off: you can't see node_modules from your host's editor for things like "go to definition" jumping into a dependency. Most people don't care; if you do, install node_modules on the host too (the host's copy and the container's copy are now separate).
Same pattern for __pycache__, vendor/ (Composer, PHP), Bundler's vendor/bundle, Cargo's target/. Any large auto-generated dependency tree.
Fix 3: keep your project in WSL2 (Windows)
On Windows with WSL2-backed Docker Desktop:
- Project in
C:\Users\you\code\project(Windows filesystem): every bind-mount read crosses the 9P translation boundary. Slow. - Project in
\\wsl$\Ubuntu\home\you\code\project(inside WSL2): the container reads the file natively from the WSL2 distro's filesystem. Fast.
The fix on Windows is to move the project into your WSL2 distro:
# Open your WSL2 distro (e.g., Ubuntu)
cd ~
git clone https://github.com/.../my-project.git
cd my-project
docker compose up -dEdit files with VS Code's Remote-WSL extension or another editor that supports WSL paths. Same Windows machine, dramatically faster Docker.
Fix 4: tmpfs for caches
For caches that don't need to survive a container restart, mount tmpfs (RAM-backed):
docker run -d --tmpfs /app/cache:size=200m my-apptmpfs is in-memory, so it's at native CPU speed regardless of platform. Useful for build caches, session stores, temp files.
Fix 5: avoid bind-mounting irrelevant directories
docker run -v $(pwd):/app bind-mounts everything in your project. If .git is 500 MB or you have a coverage directory or a dist from yesterday's build, they're all crossing the filesystem bridge whenever something walks the tree.
The fix: .dockerignore for build, and selective volume mounts for runtime. Instead of bind-mounting the whole project, mount only what the container actually needs:
volumes:
- ./src:/app/src
- ./public:/app/public
- ./package.json:/app/package.json:roTrade-off: more verbose, but you mount only the live-edit directories.
Cached and delegated consistency (older flags)
volumes:
- .:/app:cached # host source is authoritative; container reads may lag
- .:/app:delegated # container's writes may lag the hostThese flags came from the gRPC FUSE era and gave some speedup at the cost of consistency. With VirtioFS as the default, they're less relevant — and Docker Desktop ignores them on VirtioFS. If you find them in older Compose files, you can keep them (no harm) or remove them.
Common pitfalls
- VirtioFS not on. Default since 4.22, but if you updated Docker Desktop from a much older version, the setting might have stayed on the old default. Check Settings → General.
- Mounting the whole project including
node_modules. The named-volume trick is exactly the workaround. - Editing on Windows side, container running in WSL2. That's the slow case. Move the project into WSL2.
- Expecting Linux-level performance. Mac and Windows have a fundamental disadvantage here — Linux containers run in a VM. Even with VirtioFS, bind mounts are slower than native. Optimize what you can; for the absolute fastest dev loop, Linux remains best.
What to do next
- Docker Volumes vs Bind Mounts — the full persistence picture with all the trade-offs.
- Where Are Docker's Files on a Mac? — the VM and its virtual disk that drive all of this.
- How to Dockerize a Node.js App — the named-volume-for-node_modules pattern in context.
FAQ
Sources
Authoritative references this article was fact-checked against.
- Docker Desktop for Mac — settings referencedocs.docker.com
- Docker Desktop with WSL2docs.docker.com



