Docker volumes vs bind mounts: what is the difference and when do you use each?
Quick Answer
Volumes are managed by Docker and stored in a Docker-controlled location, making them portable and easy to back up. Bind mounts map a specific host path into the container. Use volumes for production data and bind mounts for development live-reloading.
Detailed Answer
Think of Docker volumes like a safe deposit box at a bank. You tell the bank (Docker) to create a box, and the bank decides where it is stored, who can access it, and how it is secured. You interact through the bank's interface, not by walking into the vault. Bind mounts are like giving someone a key to a specific room in your house. They get direct access to that exact location on your host filesystem, with all the benefits and risks that brings. The bank's box is more controlled and portable; the house key gives more flexibility but less isolation.
Docker volumes are the preferred way to persist data from containers. When you create a volume with docker volume create, Docker manages it in a directory under /var/lib/docker/volumes/ on Linux. Volumes can be named or anonymous. Named volumes survive container restarts and removals, making them ideal for database storage, application state, and file uploads. Bind mounts map a directory or file on the host machine directly into the container's filesystem. Unlike volumes, bind mounts depend on the host's directory structure, so they are not portable across machines. Docker also supports tmpfs mounts, which exist only in host memory and are never written to the filesystem, perfect for sensitive data that should not persist.
At the storage driver level, volumes use Docker's volume driver interface, which stores data locally by default but can be extended with plugins for NFS, AWS EBS, Azure Disk, GlusterFS, and other backends. The Docker daemon manages permissions, labels, and cleanup. When a container mounts a volume, the daemon uses a Linux bind mount internally to project the volume directory into the container's mount namespace. For bind mounts, the daemon directly projects the host path into the container's namespace without any management layer in between. Changes to a file on the host are immediately visible inside the container and the other way around. Both volumes and bind mounts can be made read-only by appending :ro to the mount spec, which is a security best practice when the container only needs to read configuration files.
In production, volumes are the standard for stateful services like databases, message queues, and object stores. A PostgreSQL container mounts a named volume to /var/lib/postgresql/data so data survives container updates and restarts. Volume drivers enable cloud storage integration. The REX-Ray driver, for example, automatically provisions and attaches AWS EBS volumes to the Docker host. For CI/CD caching, volumes persist build caches like Maven's .m2 directory or Node's node_modules across pipeline runs. Bind mounts dominate in development workflows: you mount your local source code into the container so file changes trigger hot-reloading without rebuilding the image. This is the pattern behind docker compose watch and tools like nodemon or Air for Go development.
A common gotcha with bind mounts is file ownership and permission mismatches. If your app runs as UID 1000 inside the container but the host files are owned by UID 501 (common on macOS), you get permission denied errors or the app creates files the host user cannot read. This is especially painful on Linux where UIDs are shared between host and container. With named volumes, Docker handles initial permissions based on the container's filesystem state. Another trap is that anonymous volumes created by VOLUME instructions in the Dockerfile pile up over time and eat disk space. Running docker volume prune is essential for cleanup. On macOS and Windows, bind mount performance is significantly slower than on Linux because files must sync between the host OS and the Linux VM running Docker. For large projects, this latency can make bind-mounted builds noticeably slower than building inside a volume.