Bind Mounts
Bind mounts link a specific host directory to a container directory. Unlike volumes, you control exactly where on the host filesystem the data lives.
Volumes vs Bind Mounts
┌─────────────────────────────────────────────────────────────┐
│ Host System │
│ │
│ ┌─────────────────────┐ ┌─────────────────────────────┐ │
│ │ Docker-managed area │ │ Host filesystem │ │
│ │ /var/lib/docker/ │ │ /home/user/project │ │
│ │ volumes/my-vol/ │ │ /etc/nginx/ │ │
│ └─────────────────────┘ └─────────────────────────────┘ │
│ │ │ │
│ Volume Bind Mount │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Container │ │
│ │ /app/data /app/code │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
| Feature | Volume | Bind Mount |
|---|---|---|
| Location | Docker-managed | You choose |
| Portability | High | Depends on host |
| Backup | Docker tools | Standard file tools |
| Performance | Optimized | Host filesystem |
| Use case | Production | Development |
Creating Bind Mounts
Using -v (short syntax)
# Absolute path required
docker run -v /host/path:/container/path nginx
# Current directory
docker run -v $(pwd):/app nginx
# Windows paths
docker run -v C:\Users\me\project:/app nginx
docker run -v //c/Users/me/project:/app nginx # Git Bash/MinGW
Using --mount (explicit syntax)
docker run --mount type=bind,source=/host/path,target=/container/path nginx
# With options
docker run --mount type=bind,source=$(pwd),target=/app,readonly nginx
Development Workflow
Bind mounts are essential for development - edit code on your host and see changes in the container:
# Mount project directory
docker run -d \
--name dev-server \
-v $(pwd):/app \
-w /app \
-p 3000:3000 \
node:18 \
npm run dev
# Changes to files on host are immediately reflected in container
Hot Reload Example
# React development with hot reload
docker run -d \
--name react-dev \
-v $(pwd):/app \
-v /app/node_modules \ # Anonymous volume for node_modules
-w /app \
-p 3000:3000 \
-e CHOKIDAR_USEPOLLING=true \ # File watching on some systems
node:18 \
npm start
The node_modules Problem
When developing Node.js apps, you need a strategy for node_modules:
Problem
# Host has empty or different node_modules
# Container installs its own
# Bind mount overwrites container's node_modules with host's
docker run -v $(pwd):/app node:18 npm install
# node_modules installed in container...
# but bind mount means host's (empty) node_modules is used!
Solution: Anonymous Volume
# Empty anonymous volume hides host's node_modules
docker run \
-v $(pwd):/app \
-v /app/node_modules \ # Anonymous volume takes precedence
node:18 npm start
Alternative: Install on Host
# Install dependencies on host
npm install
# Then run container
docker run -v $(pwd):/app node:18 npm start
Read-Only Bind Mounts
Protect host files from container modifications:
# Mount as read-only
docker run -v $(pwd)/config:/app/config:ro nginx
# Using --mount
docker run --mount type=bind,source=$(pwd)/config,target=/app/config,readonly nginx
Use cases:
- Configuration files
- Source code in production
- Shared reference data
Bind Mount Options
# Read-only
-v /path:/path:ro
# Consistent (macOS - full consistency)
-v /path:/path:consistent
# Cached (macOS - host authoritative)
-v /path:/path:cached
# Delegated (macOS - container authoritative)
-v /path:/path:delegated
macOS Performance
Docker Desktop on macOS can be slow with bind mounts. Use cached or delegated:
# For development (container can lag behind host changes)
docker run -v $(pwd):/app:cached node:18 npm start
# For builds (host can lag behind container changes)
docker run -v $(pwd):/app:delegated node:18 npm run build
Common Bind Mount Patterns
Configuration Files
# Mount nginx config
docker run -d \
-v $(pwd)/nginx.conf:/etc/nginx/nginx.conf:ro \
-p 80:80 \
nginx
# Mount multiple configs
docker run -d \
-v $(pwd)/config:/etc/myapp/config:ro \
-v $(pwd)/certs:/etc/myapp/certs:ro \
myapp
Log Files
# Mount log directory
docker run -d \
-v /var/log/myapp:/app/logs \
myapp
# Now logs are on host filesystem for easy access
tail -f /var/log/myapp/app.log
Database Files
# Development database with bind mount
docker run -d \
-v $(pwd)/data:/var/lib/postgresql/data \
postgres:15
# Note: For production, prefer named volumes
Shared Data Between Containers
# Multiple containers accessing same host directory
docker run -d -v /shared/data:/data container1
docker run -d -v /shared/data:/data container2
File Permissions
Bind mounts can cause permission issues:
# Container runs as root, creates files owned by root
docker run -v $(pwd):/app alpine touch /app/file.txt
ls -la file.txt # Owned by root!
# Solution 1: Run as current user
docker run -v $(pwd):/app -u $(id -u):$(id -g) alpine touch /app/file.txt
# Solution 2: Match user in container
docker run -v $(pwd):/app myapp # Container has user with same UID
Dockerfile with Matching User
FROM node:18
# Create user with specific UID (match host user)
ARG UID=1000
ARG GID=1000
RUN groupmod -g $GID node && usermod -u $UID node
USER node
WORKDIR /app
Bind Mounts in Development Containers
Complete development environment:
docker run -it --rm \
--name dev \
-v $(pwd):/app \
-v /app/node_modules \
-v ~/.gitconfig:/home/node/.gitconfig:ro \
-v ~/.ssh:/home/node/.ssh:ro \
-w /app \
-p 3000:3000 \
-p 5000:5000 \
-u node \
node:18 \
bash
Security Considerations
# Avoid mounting sensitive host paths
# BAD - exposes entire system
docker run -v /:/host alpine
# BAD - exposes Docker socket
docker run -v /var/run/docker.sock:/var/run/docker.sock alpine
# GOOD - mount only what's needed
docker run -v $(pwd)/app:/app:ro myapp
Troubleshooting
Path Doesn't Exist
# -v creates missing host directories
docker run -v /nonexistent:/app alpine ls /app
# Creates /nonexistent on host
# --mount fails if host path doesn't exist
docker run --mount type=bind,source=/nonexistent,target=/app alpine
# Error: bind source path does not exist
Empty Directory
# If container path has files, bind mount hides them
docker run -v $(pwd)/empty:/usr/share/nginx/html nginx
# nginx serves empty directory, not default page
Permission Denied
# Check host permissions
ls -la /path/to/mount
# Run container as appropriate user
docker run -u $(id -u):$(id -g) ...
Key Takeaways
- Bind mounts map specific host paths to container paths
- Essential for development workflows with hot reload
- Use anonymous volumes to prevent node_modules conflicts
- Mount as read-only when container shouldn't modify files
- Consider macOS performance options (cached, delegated)
- Watch out for file permission issues
- Prefer volumes for production, bind mounts for development

