What this error means
Error: bind mount failed: source path does not exist
Docker could not find the host-side path you asked it to mount into the container. The path must exist and be reachable by the Docker daemon (which might be running on a VM, WSL, or a remote host).
Common causes:
- Typo or wrong relative path
- Using a path that exists on your machine but not on the Docker host/context
- Windows path conversion issues (PowerShell vs Git Bash)
- Missing directory or insufficient permissions
- SELinux/AppArmor labeling blocks access
- macOS/Windows: path not shared with Docker Desktop
Quickstart: make it work now
- Always create the directory before running the container.
- Use an absolute path when in doubt.
- Ensure you’re mounting a path on the Docker host you’re actually using (check docker context).
Minimal working example (Linux/macOS)
mkdir -p "$PWD/data"
echo "hello" > "$PWD/data/hello.txt"
docker run --rm \
-v "$PWD/data:/data:ro" \
alpine:3.20 sh -c 'ls -la /data && cat /data/hello.txt'
Minimal working example (Windows PowerShell)
New-Item -ItemType Directory -Force "$PWD/data" | Out-Null
Set-Content -Path "$PWD/data/hello.txt" -Value "hello"
docker run --rm `
-v "${PWD}/data:/data:ro" `
alpine:3.20 sh -c "ls -la /data && cat /data/hello.txt"
If you use Git Bash on Windows and see path conversion issues, prefix the command with:
MSYS_NO_PATHCONV=1 docker run -v "/c/Users/you/project/data:/data" alpine:3.20 ls /data
Minimal Compose example
version: "3.9"
services:
app:
image: alpine:3.20
command: ["sh", "-c", "ls -la /data && cat /data/hello.txt"]
volumes:
- type: bind
source: ./data
target: /data
read_only: true
Run:
mkdir -p data && echo "hello" > data/hello.txt
docker compose up --abort-on-container-exit --remove-orphans
Step-by-step diagnosis and fixes
- Confirm the Docker host/context
- Check where Docker commands execute:
- docker context ls
- If you’re using a remote context or Docker Desktop with WSL, the source path must exist on that host, not just on your local filesystem.
- Verify the path exists and is correct
- Use absolute paths for quick checks: on Linux/macOS run pwd; on Windows PowerShell use ${PWD}.
- Test the path:
- Linux/macOS: ls -ld "$PWD/data"
- Windows PowerShell: Get-Item "$PWD/data"
- Create the source directory/file
- mkdir -p "$PWD/data"
- Ensure the parent directories exist.
- Check permissions
- Linux/macOS: ensure your user (and Docker daemon) can read the directory: ls -ld data
- On SELinux distros (RHEL/Fedora/CentOS), add :z or :Z to relabel for the container:
docker run --rm -v "$PWD/data:/data:Z" alpine:3.20 ls /data
- Fix platform-specific path issues
- Windows PowerShell: prefer "${PWD}/subdir:/container". Avoid bare C:\ paths unless quoted.
- Git Bash: disable path mangling with MSYS_NO_PATHCONV=1 or use /c/Users/... form.
- macOS/Windows (Docker Desktop): ensure the drive/path is shared. Keep the path inside your user directory if possible.
- WSL: ensure the path is accessible to the Docker daemon (e.g., use /mnt/c/... or a path inside the WSL distro if the daemon runs there).
- Resolve relative vs Compose working dir
- Compose resolves relative paths from the directory of the compose.yaml file.
- If using variables, confirm expansion: volumes: - "${PWD}/data:/data" or "${HOME}/data:/data".
- Avoid symlink surprises
- Mount the real path rather than a symlink that points outside shared areas. On macOS/Windows, symlinks to unshared locations will fail.
- Re-run with a simple container
- Alpine or BusyBox with ls/cat is perfect for validating the mount before launching your real service.
Platform path cheatsheet
| Platform | Example host path | Example flag |
|---|---|---|
| Linux/macOS | /home/user/project/data | -v /home/user/project/data:/data |
| Windows PowerShell | ${PWD}/data | -v "${PWD}/data:/data" |
| Windows Git Bash | /c/Users/user/project/data | MSYS_NO_PATHCONV=1 -v "/c/Users/user/project/data:/data" |
| WSL | /mnt/c/Users/user/project/data | -v "/mnt/c/Users/user/project/data:/data" |
Compose patterns that work
- Explicit bind with variable expansion:
services:
web:
image: nginx:1.27
volumes:
- type: bind
source: ${PWD}/site
target: /usr/share/nginx/html
- Home directory mount (portable):
services:
app:
image: node:22-alpine
working_dir: /app
volumes:
- ${HOME}/projects/demo:/app
Note: Ensure ${HOME}/projects/demo exists before starting Compose.
Common pitfalls
- Using ~ in paths without confirming expansion; prefer ${HOME} or absolute paths.
- Trailing slashes rarely matter but can hide typos; check carefully.
- Mounting files vs directories: if you intend a single file, the exact file must exist.
- Docker Desktop not authorized to access the drive or network share.
- Using a path that only exists on the client while pointing DOCKER_HOST to a remote daemon.
Performance notes
- Bind mounts on macOS/Windows are slower due to VM/FS translation. Options:
- Mount only what you need (avoid mounting the whole repo when only src/ is required).
- Use :cached or :delegated on Docker Desktop for read-heavy/write-heavy cases:
docker run --rm -v "$PWD/site:/usr/share/nginx/html:ro,cached" nginx:1.27
- For heavy I/O, consider copying sources into the image at build time or use named volumes for runtime data.
- On Linux, bind mounts are fast; prefer them for development but still avoid mounting large, constantly changing trees if not needed.
Tiny FAQ
Q: Why does it work with docker build but fail with docker run? A: Builds use the build context sent to the daemon; run-time bind mounts require the path to exist on the host visible to the daemon.
Q: Can Docker create the host path for me? A: Treat the source path as must-exist. Create it explicitly with mkdir -p to avoid surprises and permission issues.
Q: How do I check which daemon I’m using? A: Run docker context ls. If it’s not default, ensure the source path exists on that host.
Q: Do I need :z or :Z on all systems? A: Only on SELinux-enforcing systems when labeling prevents container access. Use :Z for private labels, :z for shared.