Docker Compose — Bind Mounts in Compose
Bind mounts map a host directory directly into a container. They’re essential for development — edit code on your machine, see changes instantly in the container.
Named Volumes vs Bind Mounts
Named Volume: Bind Mount:
┌─────────────┐ ┌─────────────┐
│ Container │ │ Container │
│ /data ─────┼──→ Docker Vol │ /app ──────┼──→ Host Dir
│ │ (managed) │ │ (your code)
└─────────────┘ └─────────────┘
• Docker manages storage • YOU manage the path
• Good for databases • Good for source code
• Not visible in host filesystem • Visible and editable
| Feature | Named Volume | Bind Mount |
|---|---|---|
| Managed by | Docker | You |
| Path | Docker decides | You specify |
| Performance | Native speed | May have overhead (macOS/Windows) |
| Use case | Database data | Source code, config files |
| In top-level volumes: | Yes | No |
Basic Bind Mount
services:
web:
image: nginx:alpine
volumes:
- ./html:/usr/share/nginx/html # Host path : Container path
The ./html is a relative path on your host machine. It’s relative to the compose file location.
Read-Only Bind Mounts
Append :ro to prevent the container from writing to the mount:
services:
web:
image: nginx:alpine
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro # Read-only
- ./html:/usr/share/nginx/html:ro # Read-only
Use :ro for:
- Configuration files (the container shouldn’t modify them)
- Source code served by web servers
- Any mount where writes aren’t needed
Development Workflow with Bind Mounts
The classic development pattern — mount your source code into the container:
services:
api:
build: .
ports:
- "3000:3000"
volumes:
- .:/app # Mount entire project
- /app/node_modules # Exclude node_modules (anonymous volume)
command: npm run dev
The anonymous volume trick (/app/node_modules) prevents the host’s node_modules from overriding the container’s:
Host: Container:
├── src/ ──→ ├── src/ (from host)
├── package.json ──→ ├── package.json (from host)
├── node_modules/ ✗ ├── node_modules/ (from container)
│ └── (installed in container)
Multiple Bind Mounts
services:
api:
build: .
volumes:
- ./src:/app/src # Source code
- ./config:/app/config:ro # Config (read-only)
- ./logs:/app/logs # Logs (writable)
Path Gotchas
| Path | Type | Notes |
|---|---|---|
./data:/container/path |
Bind mount | Relative to compose file |
/absolute/path:/container/path |
Bind mount | Absolute host path |
~/data:/container/path |
Bind mount | Home directory expansion |
data:/container/path |
Named volume | NOT a bind mount (no ./) |
The leading ./ or / is what distinguishes a bind mount from a named volume.
Summary
| Feature | Detail |
|---|---|
| Syntax | ./host/path:/container/path |
| Read-only | Append :ro |
| Relative paths | Relative to compose.yml location |
| Dev workflow | Mount source code, exclude deps |
| Named volume vs bind | ./path = bind, name = volume |
Quick Reference
| Item | Detail |
|---|---|
| Bind mount | ./host/path:/container/path |
| Read-only | ./config:/etc/config:ro |
| Relative paths | Relative to compose.yml |
| Exclude deps | /app/node_modules (anonymous volume) |
| Named volume | data:/path (no ./ prefix) |
| Dev pattern | Mount source, rebuild on change |