Docker Volumes vs Bind Mounts vs tmpfs
Introduction¶
Containers are ephemeral; their writable layer vanishes on docker rm. To keep
data — or share files with the host — you mount storage. There are three kinds
and they behave differently.
The three types¶
| Type | Where it lives | Use for |
|---|---|---|
| Named volume | /var/lib/docker/volumes/<name> (Docker-managed) |
databases, uploads, anything that must survive |
| Bind mount | any host path you choose | source code in dev, config files, host logs |
| tmpfs | host RAM, never on disk | secrets, scratch space, caches you don't want persisted |
# Named volume
docker run -v pgdata:/var/lib/postgresql/data postgres:16
# Bind mount (absolute host path, or ./ in compose)
docker run -v /home/me/project:/app node:20
# tmpfs
docker run --tmpfs /run:rw,size=64m myapp
# Explicit long form (clearer, recommended)
docker run --mount type=volume,source=pgdata,target=/var/lib/postgresql/data postgres:16
docker run --mount type=bind,source="$(pwd)",target=/app node:20
Named volumes¶
Docker creates and owns them. Benefits: portable (no host path assumptions),
work identically on Linux/macOS/Windows, can use volume drivers (NFS, cloud
block storage), and docker volume prune cleans up orphans.
docker volume create pgdata
docker volume ls
docker volume inspect pgdata
docker volume rm pgdata
Backup a named volume:
docker run --rm -v pgdata:/data -v "$(pwd)":/backup alpine \
tar czf /backup/pgdata-$(date +%F).tar.gz -C /data .
# Restore
docker run --rm -v pgdata:/data -v "$(pwd)":/backup alpine \
sh -c "cd /data && tar xzf /backup/pgdata-2026-08-27.tar.gz"
Bind mounts¶
You control the exact host path. Essential for development (edit on host, run in container). Downsides: the host path must exist and match across machines; performance is poor on macOS/Windows (the FS crosses a VM boundary); and permissions get messy.
The permission problem¶
The container process runs as some UID (often root, or a fixed app UID like 1000). Files it creates on a bind mount are owned by that UID on the host. If the container runs as root, you get root-owned files in your project directory.
Fixes:
- Run the container as your host user:
docker run -u "$(id -u):$(id -g)" ... - Use
USERin the Dockerfile with a UID that matches yours, or make it configurable via build arg. - For named volumes this rarely matters (Docker manages the path); it's mostly a bind-mount issue.
tmpfs¶
services:
app:
tmpfs:
- /tmp
- /run:size=32m
Data never touches disk and is gone when the container stops. Good for decrypted secrets, session scratch, and reducing writes on flash storage.
Verification and troubleshooting¶
docker inspect -f '{{json .Mounts}}' <container> | jq
docker exec <container> ls -lan /var/lib/postgresql/data
docker system df -v # space used by volumes
- Data disappears on
docker compose down— plaindownkeeps named volumes;down -vdeletes them. If it still vanishes, you used an anonymous volume (-v /var/lib/postgresql/datawith no name) — a new one is created each run. - "permission denied" writing to a bind mount — container UID ≠ host file
owner, or SELinux (
:z/:Zsuffix needed on RHEL:-v $(pwd):/app:Z). - Bind-mounted
node_modules/vendorempty or wrong — the host directory shadowed the image's. Add an anonymous volume over the subpath (see the Compose post). - macOS: builds/tests crawl — bind mount FS overhead. Mount less, use
:delegated, or keep dependencies in a named volume. - Named volume has old data after you "changed the init script" — Postgres
et al. only run init scripts on an empty data dir.
docker volume rmit. - Can't delete a volume: "volume is in use" — a stopped container still
references it.
docker rmthat container first, ordocker volume rm -f.
Related tools and reading¶
- On-site: Crontab Generator (for backup jobs).
- Related posts: Docker Compose for local development, Persistent Volumes and Claims in Kubernetes.
Stuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.More in Docker & Kubernetes
StatefulSets Explained
StatefulSets give pods sticky names, sticky storage, and ordered operations — what databases and clustered apps need. Here's how each guarantee works.
October 24, 2026Persistent Volumes and Claims, Explained
PVC is a request, PV is the fulfilled storage, StorageClass is the factory. Most 'Pending PVC' problems are an access-mode or provisioner mismatch.
October 24, 2026Kubernetes Jobs and CronJobs
A Job runs pods until they succeed; a CronJob creates Jobs on a schedule. Here's parallelism, retry limits, the missed-schedule trap, and history cleanup.
October 24, 2026