Skip to main content

Volume Mount

A configuration that connects a directory or file from outside a container into the container's filesystem, making data accessible to the container process. Docker supports three mount types: named volumes, bind mounts, and tmpfs.

Volume Mount — Connecting Storage to Docker Containers

What Is a Volume Mount in Simple Terms?

By default, everything a Docker container writes to disk disappears when the container is removed. A volume mount solves this — it connects a storage location from outside the container into the container's filesystem, so data persists beyond the container's lifetime.

Think of it like plugging an external hard drive into a laptop. The files on the drive are accessible while the drive is plugged in. When you unplug it (stop the container), the files are still on the drive. Plug it into a different laptop (new container), the same files are there.

Bash
+------------------------------------------+
| Docker Container |
| |
| /app/ <- container filesystem |
| /tmp/ <- container filesystem |
| /var/lib/postgresql/data <- MOUNTED |
| | |
+----------------|-------------------------+
|
| volume mount
|
+----------------|-------------------------+
| Host | |
| /var/lib/docker/volumes/pgdata/_data |
| (named volume managed by Docker) |
+------------------------------------------+

Three Mount Types

Bash
+------------------------------------------+
| Named Volume |
| -v postgres-data:/var/lib/postgresql/data|
| |
| Managed by Docker |
| Stored at /var/lib/docker/volumes/ |
| Survives container removal |
| Best for: production data (databases) |
+------------------------------------------+
+------------------------------------------+
| Bind Mount |
| -v /host/path:/container/path |
| |
| Host path mapped directly |
| Changes on host appear in container |
| Changes in container appear on host |
| Best for: dev hot-reload, config files |
+------------------------------------------+
+------------------------------------------+
| tmpfs Mount |
| --tmpfs /tmp |
| |
| In-memory only (RAM) |
| Never written to disk |
| Lost when container stops |
| Best for: sensitive temp files, caches |
+------------------------------------------+

Named Volumes — Production Persistent Storage

Bash
# Create a named volume
docker volume create postgres-data
# Mount it when running a container
docker run -d \
--name postgres \
-v postgres-data:/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=secret \
postgres:15
# Data persists — remove and recreate container, data is still there
docker rm -f postgres
docker run -d \
--name postgres-new \
-v postgres-data:/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=secret \
postgres:15
# All your tables and data are still there
# List volumes
docker volume ls
# DRIVER VOLUME NAME
# local postgres-data
# Inspect a volume
docker volume inspect postgres-data
# Shows mountpoint: /var/lib/docker/volumes/postgres-data/_data
# Remove a volume (permanent data deletion)
docker volume rm postgres-data

Bind Mounts — Development Hot Reloading

Bash
# Mount current directory into container
docker run -d \
--name dev-api \
-p 3000:3000 \
-v $(pwd)/src:/app/src \
node:20-alpine \
node /app/src/server.js
# Edit src/server.js on your host
# Container sees changes immediately — no rebuild needed
# Mount a config file (read-only)
docker run -d \
--name nginx \
-v $(pwd)/nginx.conf:/etc/nginx/nginx.conf:ro \
nginx:1.25
# :ro = read-only, container cannot modify the host file
# Inspect mounts on a running container
docker inspect postgres \
--format '{{range .Mounts}}{{.Type}} {{.Source}} -> {{.Destination}}{{println}}{{end}}'
# volume /var/lib/docker/volumes/pgdata/_data -> /var/lib/postgresql/data

Volume Mounts in Docker Compose

YAML
version: "3.8"
services:
api:
image: payment-api:latest
volumes:
# Named volume
- payment-data:/app/data
# Bind mount for dev hot-reload
- ./src:/app/src
# Config file injection (read-only)
- ./config/prod.json:/app/config/prod.json:ro
# tmpfs for temp files
- type: tmpfs
target: /tmp
postgres:
image: postgres:15
volumes:
- postgres-data:/var/lib/postgresql/data
volumes:
postgres-data: # declared at top level — Docker manages it
payment-data:

Backup and Restore

Bash
# Backup a named volume to a tar file
docker run --rm \
-v postgres-data:/source:ro \
-v $(pwd):/backup \
alpine \
tar czf /backup/postgres-backup.tar.gz -C /source .
# Restore from backup
docker volume create postgres-data-restored
docker run --rm \
-v postgres-data-restored:/target \
-v $(pwd):/backup:ro \
alpine \
tar xzf /backup/postgres-backup.tar.gz -C /target

When to Use Each Mount Type

Use Case Mount Type Why
PostgreSQL data Named volume Persists, Docker-managed, easy backup
Redis persistence Named volume Survives restarts
Dev code hot-reload Bind mount Host edits appear instantly in container
Config file injection Bind mount (:ro) Use host file without rebuilding image
Sensitive temp files tmpfs Never written to disk
High-speed scratch space tmpfs Fastest I/O, no persistence needed
Tip

Use the --mount flag syntax instead of -v for clarity in scripts — --mount type=volume,source=pgdata,target=/var/lib/postgresql/data. It is more verbose but each option is named, making it impossible to mix up source and target.

Remember

docker compose down keeps volumes safe. docker compose down -v deletes all volumes and all data permanently. Never run down -v on a production system unless you intend to destroy all data.

Common Mistake

Using bind mounts in production with a host path that might not exist on every server. Bind mounts require the exact host path to exist and have correct permissions. Named volumes are created automatically by Docker if they do not exist. Use named volumes in production, bind mounts in development only.

Security

Be careful when bind-mounting host directories into containers. A container with a bind mount to a sensitive directory (like /etc or /root) can read host system files. Always use :ro (read-only) for config files and never bind-mount directories that contain credentials or SSH keys into production containers.

Frequently Asked Questions

What's the practical difference between a named volume and a bind mount that determines which one to use?

A bind mount points at a specific path on the host filesystem (e.g. `-v /home/user/code:/app`), so its content and permissions are entirely host-dependent — useful for local development where you want live code editing reflected inside the container. A named volume (`-v mydata:/var/lib/data`) is managed entirely by Docker in its own storage area, portable across hosts, and the right choice for persistent application data like database files that shouldn't depend on host directory structure.

What's a common production mistake involving volume mounts?

Bind-mounting source code into a container in a way that was only meant for local dev, then carrying that same docker-compose file into production — this can silently mask the image's actual baked-in code with whatever happens to be on the production host's filesystem, defeating the point of immutable images. Also watch file permission mismatches: a container process running as a non-root UID often can't write to a bind-mounted host directory owned by a different UID, causing confusing permission-denied errors only in certain environments.