One of the most common frustrations when starting with Docker is losing all your data every time a container is removed or recreated. This happens because Docker containers are ephemeral by design — their writable layer is created with the container and destroyed along with it. Docker Volumes solve this problem by providing a storage mechanism that exists entirely outside the container lifecycle. Whether you're running a MySQL database, storing user uploads, or persisting application configuration, Volumes ensure your data survives container restarts, updates, and replacements. This guide covers everything you need to know about Docker Volumes, from basic concepts to production-ready backup strategies, all tested on Ubuntu VPS environments.
Understanding Docker's Layered Filesystem
Before diving into Volumes, it helps to understand how Docker manages storage internally. Every Docker image is built from a stack of read-only layers using a Union Filesystem (UnionFS). When a container is started from an image, Docker adds a thin writable layer on top of all the image layers. Any file you create, modify, or delete inside a running container goes into this writable layer — the underlying image layers are never changed.
The critical issue is that this writable layer is tightly coupled to the specific container instance. When you run docker rm or docker-compose down, the writable layer is permanently deleted along with the container. This means any data written inside the container — such as database files, uploaded assets, or generated logs — is gone forever. Docker provides three mechanisms for persisting data beyond the container lifecycle: Volumes, Bind Mounts, and tmpfs Mounts.
Docker Volumes Explained
A Docker Volume is a dedicated storage directory managed by the Docker Engine, completely separate from the container lifecycle. Volume data is stored on the host at /var/lib/docker/volumes/<volume-name>/_data, but Docker owns and manages this path — not the user directly. This distinction provides several important advantages over other storage approaches.
Because Volumes are managed by Docker, they are the recommended storage mechanism for production workloads. You can delete and recreate containers freely without touching the Volume data. Multiple containers can mount the same Volume simultaneously. Volumes can be backed up, restored, and migrated with standard Docker tooling, and they support Volume Drivers that extend storage to remote backends like NFS, AWS EBS, or Azure Disk.
- Lifecycle independence — Removing a container does not affect the Volume or its data
- Docker-managed permissions — No need to manually configure ownership on the host
- Shareable across containers — Multiple containers can mount the same Volume concurrently
- Easy backup and migration — Standard Docker commands handle the entire workflow
- Volume Driver support — Extend to cloud storage providers transparently
- Better performance — Direct writes to host filesystem, bypassing the UnionFS overhead
Volume vs Bind Mount vs tmpfs: Comparison Table
Choosing the right storage type depends on your use case. Here is a quick comparison to guide your decision:
| Type | Storage Location | Persistence | Best For |
|---|---|---|---|
| Named Volume | /var/lib/docker/volumes/ | Permanent (manual delete) | Databases, user uploads, production data |
| Bind Mount | Any path on host | Permanent (host-dependent) | Development, live code reloading, config files |
| tmpfs Mount | RAM only (no disk write) | Temporary (lost on stop) | Session caches, temp data, secrets in memory |
| Anonymous Volume | /var/lib/docker/volumes/ (random ID) | Permanent but hard to track | Not recommended for production |
Named Volumes are the default recommendation for production use. Bind Mounts work well in development environments where you want to mount your local source code into a container and have changes reflected immediately without rebuilding the image.
Creating and Managing Volumes — Essential Commands
Here are the core Docker CLI commands every developer working with Volumes should know:
# Create a new Named Volume docker volume create myapp_data # List all Volumes on this host docker volume ls # Inspect a Volume (shows mount path, driver, labels) docker volume inspect myapp_data # Remove a specific Volume (container must not be using it) docker volume rm myapp_data # Remove all unused Volumes (prune) docker volume prune # Remove Volumes together with containers on compose down docker-compose down -v
Sample output from docker volume inspect:
[
{
"CreatedAt": "2026-06-09T10:00:00+07:00",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/myapp_data/_data",
"Name": "myapp_data",
"Options": {},
"Scope": "local"
}
]
Mounting Volumes with docker run and Docker Compose
There are two primary ways to attach a Volume to a container: using the -v or --mount flags with docker run, or declaring them in a docker-compose.yml file.
Option 1 — docker run with the -v flag
# Syntax: -v <volume-name>:<path-inside-container> docker run -d \ --name mysql_db \ -e MYSQL_ROOT_PASSWORD=secret \ -e MYSQL_DATABASE=myapp \ -v mysql_data:/var/lib/mysql \ mysql:8.0 # Bind Mount — maps a specific host directory into the container docker run -d \ --name nginx_web \ -v /home/user/website:/usr/share/nginx/html:ro \ -p 80:80 \ nginx:alpine
Option 2 — docker-compose.yml (recommended for production)
version: '3.8'
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
POSTGRES_DB: myapp
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped
app:
image: myapp:latest
depends_on:
- db
volumes:
- app_uploads:/app/uploads
- app_logs:/app/logs
ports:
- "8000:8000"
# Declare Named Volumes at the top level
volumes:
postgres_data:
app_uploads:
app_logs:
Declaring Volumes at the top level of docker-compose.yml instructs Docker to create them automatically if they do not exist yet. Running docker-compose down without the -v flag will stop and remove containers while leaving all Volumes intact. This is the intended behavior for production deployments — containers are ephemeral, data is not.
Backing Up and Restoring Docker Volumes
No storage solution is complete without a tested backup strategy. The standard Docker approach is to spin up a temporary container that mounts the Volume and writes a compressed archive to the host.
Creating a Volume Backup
# Backup the postgres_data Volume to the current directory docker run --rm \ -v postgres_data:/source:ro \ -v $(pwd):/backup \ alpine \ tar czf /backup/postgres_data_$(date +%Y%m%d).tar.gz -C /source . # Verify the backup file ls -lh postgres_data_*.tar.gz
Restoring a Volume from Backup
# Create a new Volume for the restore target docker volume create postgres_data_restore # Extract the archive into the new Volume docker run --rm \ -v postgres_data_restore:/target \ -v $(pwd):/backup \ alpine \ sh -c "cd /target && tar xzf /backup/postgres_data_20260609.tar.gz"
Database-Specific Backup (Recommended for PostgreSQL and MySQL)
# PostgreSQL — pg_dump produces a consistent logical backup docker exec postgres_container pg_dump -U myuser mydb \ > backup_$(date +%Y%m%d).sql # MySQL — mysqldump docker exec mysql_container mysqldump \ -u root -psecret myapp \ > mysql_backup_$(date +%Y%m%d).sql # Restore PostgreSQL cat backup_20260609.sql | docker exec -i postgres_container \ psql -U myuser mydb
Production Tip: Never back up a running database by taring its raw data files. The result may be an inconsistent snapshot that cannot be restored cleanly. Always use the database's own dump tools — pg_dump for PostgreSQL, mysqldump for MySQL, and mongodump for MongoDB. These tools are designed to produce consistent backups while the database is still accepting connections. Set up a nightly cron job and test your restore procedure at least once a month.
Sharing Volumes Between Multiple Containers
One of the most powerful features of Docker Volumes is the ability to mount the same Volume into multiple containers simultaneously. This is useful in microservice architectures where services need to read from or write to a shared file store.
version: '3.8'
services:
# Application server writes uploaded files
app:
image: myapp:latest
volumes:
- shared_uploads:/app/uploads
# Nginx reads uploads to serve as static files
nginx:
image: nginx:alpine
volumes:
- shared_uploads:/usr/share/nginx/html/uploads:ro
ports:
- "80:80"
# Background worker processes uploaded files
worker:
image: myworker:latest
volumes:
- shared_uploads:/worker/input:ro
volumes:
shared_uploads:
In this example, all three containers share the shared_uploads Volume. Nginx and the worker both mount it read-only (:ro) to prevent accidental writes. Applying :ro to any container that only needs read access is a security best practice that reduces the blast radius of a potential compromise or misconfiguration.
Handling Volume Permissions on Linux
Permission issues are among the most common problems when using Volumes on Linux. The UID/GID of the process running inside a container may not match the ownership of the Volume directory on the host, resulting in "Permission denied" errors.
# Check ownership of a Volume on the host
ls -la /var/lib/docker/volumes/myapp_data/_data
# Fix permissions in the Dockerfile
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
RUN chown -R appuser:appgroup /app
USER appuser
# Or specify the user at runtime
docker run -d \
--user 1000:1000 \
-v myapp_data:/app/data \
myapp:latest
# In docker-compose.yml
services:
app:
image: myapp:latest
user: "1000:1000"
volumes:
- myapp_data:/app/data
Official Docker images for databases like MySQL, PostgreSQL, and MongoDB handle permission setup automatically in their entrypoint scripts. If you are building your own images, always define a non-root user in your Dockerfile and ensure the Volume mount point is owned by that user before the USER directive.
Production Best Practices for Docker Volumes
Running Docker Volumes on a production VPS requires discipline around naming, backup, and monitoring to avoid unpleasant surprises.
- Use meaningful names — Follow a
projectname_service_dataconvention such asmyapp_postgres_data. Random or generic names become hard to manage as your stack grows. - Always declare Volumes in docker-compose.yml — Avoid anonymous Volumes in production. Docker prune commands will remove unnamed Volumes attached to stopped containers.
- Automate backups with cron — Schedule nightly backups and keep at least 30 days of rolling archives. Copy them off the same server to a remote location.
- Monitor disk usage — Volumes holding database files or logs can grow continuously. Set up disk usage alerts to avoid containers crashing from a full filesystem.
- Use read-only mounts where possible — Any container that only reads from a Volume should mount it
:roto prevent accidental writes. - Separate Volumes by data type — Keep database data, uploaded files, and logs in separate Volumes so you can apply different backup retention policies to each.
- Test your restore procedure — A backup you have never tested is not a real backup. Schedule a quarterly restore drill to a staging environment.
# Automated nightly backup cron job (add via crontab -e) # Runs at 02:00 every night, keeps 30 days of backups 0 2 * * * docker run --rm \ -v myapp_postgres_data:/source:ro \ -v /backup/docker:/backup \ alpine \ tar czf /backup/postgres_$(date +\%Y\%m\%d).tar.gz \ -C /source . && \ find /backup/docker -name "postgres_*.tar.gz" -mtime +30 -delete
Frequently Asked Questions
What is the difference between a Docker Volume and a Bind Mount?
Docker Volumes are managed entirely by the Docker Engine and stored under /var/lib/docker/volumes/, making them portable and easy to migrate. Bind Mounts tie a container path directly to a specific path on the host filesystem, which is great for development workflows where you want live code reloading. For production workloads, Named Volumes are recommended because Docker handles their lifecycle, permissions, and they are host-path agnostic.
Will data in a Docker Volume be lost when a container is deleted?
No. Volumes have a lifecycle completely independent from containers. Running docker rm -f or docker-compose down will remove the container but leave the Named Volume intact. Data is only deleted when you explicitly run docker volume rm or docker volume prune. This makes it safe to recreate or update containers without risking data loss, though regular backups are still strongly recommended.
How do I back up data from a Docker Volume?
The standard approach is to spin up a temporary Alpine container that mounts the volume and creates a tar archive: docker run --rm -v myvolume:/data -v $(pwd):/backup alpine tar czf /backup/backup.tar.gz -C /data . This produces a backup.tar.gz in your current directory. For PostgreSQL or MySQL, use pg_dump or mysqldump respectively instead of raw file backups to ensure data consistency.
Should I use a Volume or tmpfs for temporary data in a container?
If the data does not need to survive container restarts and you want maximum speed, use a tmpfs mount since it stores data in RAM without any disk I/O. If losing data on restart is unacceptable, use a Named Volume. tmpfs is ideal for session caches and regenerable temp files, while Volumes are the right choice for databases, user uploads, and configuration files that must persist across restarts.
High-Performance KVM VPS by AsiaGB
Full root access VPS ready for Docker, MySQL, Python, Node.js and more. Starting from 500 THB/month with 99% uptime.
View VPS Plans