Docker Bind Mount Permissions on Windows: The Least Painful Setup

Was this helpful?

You bind-mount your project into a container. It works. Then it doesn’t. Suddenly your build can’t write a cache, your app can’t create a log file, and everything is either owned by root or “Access is denied.” Welcome to the Windows file permission uncanny valley: it looks like Unix, smells like Unix, and behaves like a committee.

If you want the least painful setup, you need to pick one: either treat Windows as a host you’re borrowing (and do the work inside WSL2), or accept the translation layer and build guardrails. The good news: you can make it boring. The bad news: you have to be precise.

The one rule that makes everything easier

Keep your project files inside the WSL2 Linux filesystem (e.g., /home/you/project), not on C:\, and bind-mount from there.

That’s it. If you do this, permissions behave like Linux because… it is Linux. The moment you bind-mount a Windows path into a Linux container, you’re asking two permission models, two filesystem semantics, and one virtualization boundary to “just agree.” They won’t. They’ll negotiate. And negotiation is how you get 3 a.m. incidents.

One short joke, as a treat: Windows-to-Linux permission mapping is like a bilingual toddler translating legal contracts—impressive effort, questionable precision.

If your team insists on working from C:\Users\..., you can still make it functional. It just won’t be the least painful. It will be “pain with a service-level objective.”

How permissions get weird on Windows (what’s actually happening)

Bind mounts aren’t just “files shared into containers”

A bind mount is the container seeing a directory that is physically stored somewhere else. On Linux hosts, this is straightforward: kernel VFS semantics match; UID/GID and mode bits mean what containers think they mean.

On Windows, Docker Desktop is effectively running a Linux VM (WSL2 engine) and then presenting Windows files to that VM through a shared filesystem layer. Your container isn’t mounting NTFS directly; it’s mounting a translation of NTFS.

Where the mismatch shows up

  • UID/GID vs SIDs/ACLs: Linux uses numeric user/group IDs plus mode bits. Windows uses SIDs and ACLs. Mapping is lossy.
  • chmod/chown semantics: On a “real” Linux filesystem, chmod and chown usually do what they say. On Windows-backed mounts, they may appear to succeed but not persist, or fail with “Operation not permitted.”
  • Executable bit: Linux needs an executable bit for scripts/binaries. Windows doesn’t. So the translation layer guesses.
  • Case sensitivity: Linux is case-sensitive, Windows usually isn’t (though modern Windows can do per-directory case sensitivity). Tools that assume one model can behave oddly under the other.
  • Inotify / file watching: Dev servers and build systems depend on filesystem events. Cross-boundary file events can be delayed or dropped. That looks like “hot reload is broken.”

Two distinct “Windows setups” people confuse

Setup A: Work in WSL2 filesystem (best). Your code lives under /home in WSL. Docker engine is WSL2-based. Containers mount Linux paths. You mostly stop thinking about Windows permissions.

Setup B: Work in Windows filesystem (sometimes necessary). Your code lives under C:\. Docker mounts /mnt/c/... into containers. Now you are in translation land. You need policies: which user runs in the container, which directories are writable, and how to avoid “root-owned” artifacts.

There’s a reliability aphorism worth keeping on your desk. Here’s a paraphrased idea, because exact wording varies in retellings: paraphrased idea: “Complex systems fail in complex ways; simplicity is a reliability feature.” — John Ousterhout (paraphrased idea)

Least painful reference architecture (what to do)

Decision 1: Put the workspace in WSL2

Make WSL2 your “developer workstation filesystem,” even if Windows is your desktop. You can still use VS Code or JetBrains on Windows and edit files inside WSL via their WSL integration. Your Git tooling, language toolchains, and file permissions will align.

Decision 2: Run containers as a non-root user that matches your WSL UID/GID

If your container writes files into a bind mount, you want those files to be owned by your WSL user, not root. The pragmatic pattern:

  • Figure out your WSL UID/GID (id).
  • Build an image that creates a user with that UID/GID (or use entrypoint logic).
  • In Compose, set user: "UID:GID" for dev containers.

Decision 3: Use named volumes for heavy-write directories

Bind mounts are great for source code. They can be terrible for dependency caches and databases. Put these in Docker volumes:

  • Node: node_modules often faster in a volume.
  • Python: .venv, pip cache.
  • Rust/Go/Java: build caches and artifact directories.
  • Databases: always use volumes unless you love corruption stories.

Decision 4: Keep Windows mounts read-mostly if you must use them

If corporate policy or tooling forces you to keep code on C:\, then treat it like a shared network drive: read it, compile from it if you must, but don’t write lots of tiny files through it. Redirect caches to volumes or container-local storage.

Second short joke: The fastest way to fix bind mount performance on Windows is to stop bind-mounting Windows. It’s not elegant, but it is effective.

Setup plan (step-by-step)

1) Confirm you’re using the WSL2 engine

Docker Desktop can run with different backends depending on version and configuration. If you want the least painful path, you want WSL2.

2) Create a project directory in WSL2

Clone your repository inside WSL, not under /mnt/c. If you already have a Windows clone, re-clone. Yes, it feels redundant. No, it’s not.

3) Add a dev user to your image (or run as your UID)

For development images, I prefer building the user into the image because it makes debugging predictable. For quick-and-dirty, user: "${UID}:${GID}" in Compose is fine.

4) Separate “source bind mounts” from “write-heavy volumes”

Mount your project source. Put caches and state into volumes. This reduces permission surprises and makes performance less dependent on the Windows-WSL bridge.

5) Decide your policy for file ownership

Pick one and enforce it:

  • Policy A (recommended): container writes as the same UID/GID as your WSL user.
  • Policy B: container writes as root, and you accept that host edits/cleanup require sudo. This is common, and it’s a slow leak of frustration.
  • Policy C: container never writes to bind mounts; it writes only to volumes. This is great for hermetic builds, less great for some dev loops.

Practical tasks: commands, outputs, and decisions (12+)

These are the tasks I actually run when someone says “bind mounts are broken.” Each one includes: command, what output means, and what you do next.

Task 1: Check Docker is reachable and which context you’re on

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

Meaning: You see which Docker endpoint you’re hitting. On Windows with WSL2, you typically use the Desktop Linux context.

Decision: If you’re not on the expected context, switch: docker context use desktop-linux. “Permission denied” problems sometimes turn out to be “you’re talking to a different engine than you think.”

Task 2: Confirm WSL distro and kernel (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

Meaning: WSL2 kernel is in play (note “WSL2”).

Decision: If you’re not on WSL2, fix that first. WSL1 has different filesystem behavior and tends to make Docker Desktop scenarios worse.

Task 3: Find where your project actually lives

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

Meaning: You’re in the Windows-mounted filesystem. Expect permission oddities and potentially poor performance.

Decision: Move or re-clone into /home/cr0x/work/myapp. If policy blocks that, continue with defensive configuration (later tasks).

Task 4: Identify your UID/GID in WSL

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

Meaning: Your user is UID 1000, GID 1000.

Decision: Use 1000:1000 as the container user for dev bind mounts, or build this into the image.

Task 5: Reproduce the permission problem in a minimal container

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

Meaning: Container runs as root; created file is root-owned. Mode bits are wide-open, which is common on Windows-backed mounts.

Decision: If root-owned artifacts are the pain point, run the container as your UID/GID.

Task 6: Test running container as your 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

Meaning: File ownership now matches UID/GID 1000 inside the container. On Linux filesystem mounts, this usually maps cleanly. On Windows mounts, you may still see permissive mode bits and quirky behavior, but at least you’ve stopped spamming root-owned files.

Decision: Bake user: "1000:1000" into Compose for dev, or implement user creation in Dockerfile.

Task 7: Check whether chmod/chown actually works on your mount

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

Meaning: chmod didn’t stick (still 777-ish). That’s typical on Windows-backed mounts without metadata support.

Decision: Stop relying on mode bits for security semantics on that mount. Use app-level controls, or move the workspace to WSL filesystem where chmod works.

Task 8: Verify if you are on Windows mount (DrvFs) vs native Linux filesystem

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

Meaning: The filesystem type is 9p (common for Windows mounts into WSL2). This is the translation boundary.

Decision: Expect quirks. If you need correct Unix permissions, move to ext4 inside WSL2 (/home), not /mnt/c.

Task 9: Inspect mount options from inside the container

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)

Meaning: You can literally see you’re on a DrvFs/9p-style mount, plus flags like metadata that influence permission behavior.

Decision: If metadata is absent, chmod/chown are even less meaningful. If metadata is present, permission behavior may be better, but still not identical to ext4.

Task 10: Compose sanity check: what user is the service running as?

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

Meaning: Your Compose service runs as root. Umask is normal, but irrelevant if mode bits don’t persist.

Decision: Set user: in Compose or fix the image. For dev, root is rarely worth the collateral damage.

Task 11: Measure mount performance quickly (tiny-file workload)

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

Meaning: Creating 2000 tiny files took multiple seconds. On native Linux filesystem, this is often much faster.

Decision: If your build tool writes thousands of files (hello, Node and Java), move caches to volumes or move the whole project into WSL filesystem.

Task 12: Compare with a Docker named volume (control experiment)

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

Meaning: Named volume is dramatically faster for write-heavy workloads. Also: permissions are fully Linux semantics inside the volume.

Decision: Put build caches and dependency directories in volumes. Keep bind mounts for source code and configs.

Task 13: Confirm file ownership on the host side (WSL) after container writes

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

Meaning: Root-owned files exist because earlier you wrote as root; the UID-mapped file is owned by 1000.

Decision: If root-owned artifacts exist, fix the container user and clean the directory (may require sudo in WSL).

Task 14: Inspect a volume and confirm it’s not accidentally bind-mounted

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"
  }
]

Meaning: It’s a local Docker volume inside the Linux VM. Good. That’s why it’s fast and permission-correct.

Decision: Use volumes for hot paths. If someone insists “bind mount is fine,” show them Task 11 vs Task 12 numbers.

Fast diagnosis playbook

When something is slow or “permission denied,” you don’t need a week of theory. You need three checks that isolate the domain.

First: Are you mounting Windows files or Linux files?

  • Run pwd and df -T . in WSL.
  • If you’re under /mnt/c and filesystem type looks like 9p, you’re in translation land.
  • Decision: If the problem is permissions correctness or performance, move the workspace into /home (ext4) as the default fix.

Second: What user is writing into the bind mount?

  • Run docker compose exec app id (or docker run ... id).
  • If it’s root, you’ll get root-owned artifacts and sometimes write failures depending on the mount layer.
  • Decision: Force UID/GID via Compose user: or build a matching user in the image.

Third: Is this a correctness problem or a performance problem?

  • Run the tiny-file benchmark (Task 11) on the bind mount and compare to a volume (Task 12).
  • Decision: If volume is much faster, you don’t “tune permissions.” You change storage layout: bind mount for source, volume for churn.

Bonus check: Are chmod/chown expected to work here?

  • Try chmod test (Task 7). If it doesn’t stick, stop designing workflows around POSIX mode bits for that mount.
  • Decision: Move to WSL filesystem or keep sensitive files off Windows mounts.

Common mistakes: symptoms → root cause → fix

1) “Permission denied” when writing to a bind mount

Symptom: App fails creating files under /work or /app with EACCES.

Root cause: Container runs as non-root user, but bind-mounted directory maps to a Windows ACL that doesn’t allow writes the way you expect; or the directory is owned by root because earlier runs created it as root.

Fix: Move project into WSL filesystem. Otherwise, run container as your WSL UID/GID and clean root-owned files (in WSL) with sudo.

2) “chmod: Operation not permitted” or chmod appears to do nothing

Symptom: chmod returns error, or ls -l never changes.

Root cause: Windows-backed mounts do not fully support POSIX metadata semantics (or metadata support isn’t enabled).

Fix: Don’t rely on chmod on Windows mounts. Store scripts/binaries in WSL filesystem. If you need Unix permission fidelity, use ext4 in WSL2 or a named volume.

3) Hot reload/file watcher doesn’t detect changes

Symptom: Node/webpack, Python reloaders, or Go live reload doesn’t trigger on save.

Root cause: File event translation across Windows ↔ WSL2 ↔ container is inconsistent, especially on /mnt/c.

Fix: Keep workspace in WSL filesystem. If stuck on Windows mounts, configure polling watchers (slower but reliable) or move only the watched directories into WSL.

4) Build is painfully slow, CPU looks idle

Symptom: Installing dependencies or compiling takes ages; CPU usage is low.

Root cause: Small-file I/O is bottlenecked by the shared filesystem layer; every stat/open/write crosses boundaries.

Fix: Put write-heavy paths in volumes; move project into WSL filesystem; avoid mounting dependency directories from Windows.

5) “Everything is owned by root” and Git starts whining

Symptom: You see root ownership, and Git complains about dubious ownership or refuses to operate in some configurations.

Root cause: Container ran as root and wrote into the bind mount; ownership persisted. On mixed environments, safety checks trigger.

Fix: Stop running as root for dev. Clean the workspace, and make ownership deterministic via UID/GID mapping.

6) Database container works until it doesn’t

Symptom: Postgres/MySQL starts, then crashes with fsync issues or permission errors; or performance tanks.

Root cause: Database is bind-mounted to Windows filesystem; POSIX guarantees and fsync semantics aren’t the same.

Fix: Always use a named volume for database data directories, especially on Windows hosts.

7) “But it works on my machine” across a Windows-heavy team

Symptom: One dev’s environment is fine; another sees permission errors and slowness.

Root cause: Workspaces are in different places (WSL vs /mnt/c), different Docker Desktop settings, and different default users in images.

Fix: Standardize: workspace location, Compose user, and volume strategy. Treat “dev environment” as production-like infra with a spec.

Three corporate mini-stories (how this fails in real life)

Mini-story 1: The incident caused by a wrong assumption

A team had a cross-platform dev setup: Linux laptops, macOS laptops, and a big Windows contingent. They shipped a new containerized dev environment with a bind mount of the repo and a container running as root “to avoid permission issues.” Nobody liked it, but it worked well enough—until it didn’t.

The wrong assumption was subtle: they assumed “root in the container can always write to the bind mount.” On Linux hosts, that’s often true (modulo SELinux/AppArmor). On Windows-backed mounts, root doesn’t magically override Windows ACL decisions. The translation layer can block operations, and sometimes it does so inconsistently across machines depending on how the share is configured.

It surfaced as intermittent failures in a code generator that wrote into the repo. Half the Windows developers saw “permission denied,” half didn’t, and the generator’s error handling wasn’t great. It retried aggressively and left partial output. Suddenly the repo contained a mix of generated files with different ownership and inconsistent timestamps. Builds failed, then succeeded, then failed again.

They treated it like a flaky tool. It wasn’t. It was storage semantics. The fix was boring: move the workspace into WSL2, run containers as the WSL user, and store generated artifacts in a volume with a controlled export step. The generator stopped “randomly failing,” because it was no longer negotiating with NTFS through a narrow pipe.

Mini-story 2: The optimization that backfired

An engineering group wanted faster installs for a JavaScript monorepo. Someone proposed bind-mounting node_modules from the host so dependencies would persist across container rebuilds. It was pitched as “free caching.” Management loved free.

On Linux machines it was decent. On Windows machines it was a slow-motion accident. Dependency install involved creating and deleting huge numbers of tiny files, plus symlinks. The Windows-backed mount layer handled it, but with high overhead. Installs became slower than clean installs inside a container filesystem, and file watchers became unreliable.

Worse: permissions became a mess. Some developers had their containers running as root, some as a user. The same dependency directory ended up with mixed ownership. Tooling started to error out on EPERM, and developers “fixed it” by deleting the directory and reinstalling—over and over.

They eventually reversed the “optimization” and replaced it with a named volume for dependency caches, and a separate volume for node_modules when needed. The repo remained bind-mounted, but churn lived in volumes. The team stopped paying an I/O tax to the Windows mount layer. The lesson stuck: caching is not free if it’s cached in the wrong place.

Mini-story 3: The boring practice that saved the day

A regulated enterprise team had a surprisingly sane practice: every developer environment change required a small “ops-style” checklist update. Not a bureaucracy novella—just a page with standard commands to verify correctness, performance, and ownership.

When they adopted Docker Desktop with WSL2, they added three checks: (1) project path is under /home, (2) docker compose exec app id matches the developer UID, and (3) tiny-file benchmark under a bind mount doesn’t exceed a rough threshold. If it did, the checklist told them to move caches into volumes.

Months later, a Docker Desktop update changed behavior for a subset of machines. People started complaining about slow builds and weird chmod behavior. The checklist made it obvious: the workspace had migrated back to /mnt/c because a new hire followed an old onboarding guide that assumed Windows-native paths.

The practice that saved the day was dull: enforced workspace location and a short diagnostic ritual. They fixed the onboarding doc, re-cloned repos into WSL, and the incident died quietly. Nothing heroic. That’s the best kind of operational outcome.

Interesting facts and historical context (8 points)

  1. Docker’s original model assumed a Linux kernel. Windows support came later and required either Windows containers or a Linux VM for Linux containers.
  2. Docker Desktop on Windows commonly runs Linux containers inside WSL2. That’s why Linux semantics are “real” inside the VM but become “translated” when you touch Windows files.
  3. WSL1 and WSL2 are fundamentally different. WSL1 was a syscall translation layer; WSL2 is a real Linux kernel in a lightweight VM, which changes filesystem behavior dramatically.
  4. NTFS permissions are ACL-based, not mode-bit-based. The mapping to rwx bits is approximate, and tools that assume POSIX semantics can be misled.
  5. Case sensitivity on Windows has evolved. Windows can enable per-directory case sensitivity, but many toolchains still assume the default case-insensitive behavior on NTFS.
  6. File watching is a portability trap. Inotify is Linux-native; Windows has different APIs; bridging events across layers is inherently imperfect and has improved over time but remains a common dev pain.
  7. Bind mounts are not “volumes but simpler.” They inherit host filesystem semantics, which is great on Linux and complicated on Windows/macOS.
  8. Named Docker volumes are often faster because they stay inside the Linux filesystem. Fewer boundaries crossed means fewer surprises and fewer performance cliffs.

Checklists / step-by-step plan

Checklist A: New Windows laptop, least painful Docker dev setup

  1. Enable WSL2 and install a Linux distro.
  2. Install Docker Desktop and ensure it uses the WSL2 engine.
  3. In WSL, create a workspace directory: mkdir -p ~/work.
  4. Clone repos into ~/work, not /mnt/c.
  5. Decide UID/GID policy: container runs as your UID/GID for dev.
  6. Update Compose: bind mount source, volumes for caches and data.
  7. Add a one-liner smoke test: container creates a file in the bind mount and ownership is correct.

Checklist B: Compose file patterns that reduce pain

  • Bind mount: only the repo source and minimal config.
  • Volumes: dependency dirs, build outputs, DB data, package caches.
  • User mapping: set user: "${UID}:${GID}" for dev services that write to mounts.
  • Entrypoints: avoid scripts that assume chmod works on bind mounts.
  • Health checks: if your app writes state, verify it can write at startup and fail loudly.

Checklist C: When you must keep the repo on C:\

  1. Accept that chmod/chown may be unreliable; design around it.
  2. Run containers as non-root where possible to avoid root-owned artifacts.
  3. Move all write-heavy paths into volumes.
  4. If file watching is flaky, use polling watchers and document the tradeoff.
  5. Keep your “bind mount correctness tests” in CI or onboarding scripts so drift is caught early.

FAQ

1) Why does chmod not work on my bind mount from Windows?

Because the underlying filesystem is NTFS exposed through a translation layer. Mode bits are not a native concept there, so they may not persist or may be emulated.

2) Is running the container as root the simplest fix?

It’s the simplest way to create a different set of problems. You’ll get root-owned artifacts, confusing cleanup, and sometimes the same write failures anyway on Windows-backed mounts.

3) What’s the cleanest way to avoid root-owned files?

Run the container as your WSL UID/GID and keep the workspace in the WSL filesystem. That combination is the closest you get to “it behaves like Linux.”

4) Should I use bind mounts or volumes for databases on Windows?

Volumes. Always. Databases assume certain filesystem guarantees and performance characteristics that Windows-backed bind mounts don’t reliably provide.

5) I need to edit files with Windows apps. Can I still keep the repo in WSL?

Yes, as long as your editor supports WSL integration or remote filesystem access. That’s the modern, supported path for serious dev on Windows with Linux containers.

6) My build is slow, but permissions look fine. What now?

Benchmark small-file I/O on bind mount vs volume (Tasks 11 and 12). If the volume is much faster, move caches and dependency directories into volumes.

7) Why does file watching/hot reload fail only on Windows?

Because file event APIs differ and events must cross layers (Windows ↔ WSL2 ↔ container). The further your files are from the container’s native filesystem, the more likely you’ll hit watcher gaps.

8) Can I “fix” this with a single Docker Desktop setting?

Not reliably. You can improve things with configuration, but the most effective fix is architectural: keep source in WSL filesystem and use volumes for write-heavy paths.

9) What if my team uses mixed host OSes?

Standardize the container user behavior and storage layout. Your Compose should not assume Linux-only semantics if Windows hosts are first-class. Document the WSL workspace requirement.

Conclusion: next steps you can actually do today

If you want “least painful,” stop negotiating with NTFS for Linux semantics. Put the repo in WSL2’s Linux filesystem, bind mount from there, and run dev containers as your UID/GID. Then use named volumes for anything that writes a lot.

Concrete next steps:

  1. Check your repo path. If it’s under /mnt/c, re-clone into ~/work.
  2. Set user: "1000:1000" (or your actual UID/GID) in Compose for dev services that write to mounts.
  3. Move dependency directories and caches into volumes and re-run your build. Compare Task 11 vs Task 12 numbers.
  4. Add a small smoke test script to onboarding: create a file in the bind mount and verify ownership and writability.

You’re not trying to win an argument with Windows. You’re trying to run production-grade dev workflows on a laptop. Make the storage layout boring, and the rest follows.

← Previous
Why Two GPUs Are Often Worse Than One (The Real Reasons)
Next →
ZFS zfs list -o space: The View That Explains ‘Where Did It Go?’

Leave a comment