Skip to content
C Codeloom
Docker

Docker Volumes vs Bind Mounts Explained

The practical differences between Docker volumes and bind mounts, when to pick each, and how to avoid the permission and performance traps.

·4 min read · By Codeloom
Intermediate 10 min read

What you'll learn

  • How volumes and bind mounts differ
  • Lifecycle and ownership
  • Performance characteristics
  • Permissions and UID mismatches
  • Backup and migration patterns

Prerequisites

  • Familiar with terminals and YAML

What and Why

Containers are designed to be ephemeral; their writable layer disappears with the container. For anything that must survive a restart - a database file, an upload directory, a cache - you need to mount storage from outside the container. Docker gives you two main options: a named volume managed by Docker, or a bind mount straight from the host filesystem.

They look similar in docker run flags but behave differently around lifecycle, portability, performance, and permissions. Picking the wrong one leads to data lost on docker compose down -v or hosts littered with mystery directories.

Mental Model

Bind mount:        host:/srv/app/data  -->  container:/var/lib/app
                 (you own the path, exact mapping)

Named volume:      docker volume "appdata"  -->  container:/var/lib/app
                 (Docker owns it under /var/lib/docker/volumes)

Tmpfs:             RAM only, vanishes on stop
Two storage paths into a container

Pick a bind mount when the host path is the source of truth (your source code during development, a config file, a Unix socket). Pick a volume when the container is the source of truth and Docker should manage it (database data, caches, anything you do not want to know the host path of).

Hands-on Example

Local dev with hot reload uses a bind mount of source code:

docker run --rm -it \
  -v "$PWD":/app -w /app \
  -p 3000:3000 \
  node:20 npm run dev

Production-style Postgres uses a named volume:

# compose.yaml
services:
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: secret
    volumes:
      - pgdata:/var/lib/postgresql/data
    ports: ["5432:5432"]

volumes:
  pgdata:

Inspect and back up the volume without caring where it lives:

docker volume inspect pgdata
docker run --rm \
  -v pgdata:/data \
  -v "$PWD":/backup \
  alpine tar czf /backup/pg-$(date +%F).tgz -C /data .

A read-only config bind mount is a great pattern:

docker run -d \
  -v "$PWD/nginx.conf":/etc/nginx/nginx.conf:ro \
  nginx:1.27

The :ro makes the mount read-only so the container cannot corrupt your config.

Common Pitfalls

  • Compose down -v deletes volumes. That is the entire point of the -v, but it bites people who run it during cleanup. Tag environments and gate the flag.
  • Bind mount UID mismatch. The container process runs as UID 1000 but the host directory is owned by UID 1001. Files get written but you cannot read them. Either match UIDs (--user $(id -u):$(id -g)) or chown the directory.
  • Bind mount hides image content. Mounting onto /app replaces whatever the image baked in. Useful for dev, surprising in prod.
  • macOS and Windows bind performance. The virtualization layer makes bind mounts slow for large node_modules style trees. Use named volumes for dependencies or enable VirtioFS.
  • SELinux denials on RHEL. Append :z (shared) or :Z (private) to the bind mount to relabel.

Production Tips

  • In production, prefer named volumes for stateful data. They are easier to back up, easier to migrate, and survive image upgrades cleanly.
  • For shared config and secrets, mount specific files read-only. Avoid bind-mounting whole host directories into prod containers.
  • Use a volume driver for remote storage: local-persist, rexray, or the cloud-native CSI drivers in Kubernetes. Same mental model, different backend.
  • Back up volumes by running a short-lived helper container that mounts the volume and writes a tarball to object storage. Schedule it from cron or a CI runner.
  • Document the data directory contract for each container: which path is durable, what owns the path, how to restore it. Future-you will be grateful.

Wrap-up

Volumes and bind mounts cover the same gap - persistence beyond the container - but optimize for different ends. Bind mounts give you exact host control, perfect for dev and config files. Named volumes give you portable, Docker-managed storage, perfect for state in production. Pick deliberately, mount read-only where you can, and put a backup story in place before you need it.