
September 09, 2026
14 min read
By Kokil Thapa | Last reviewed: September 2026
Restart a Docker container and every file inside its writable layer disappears. That is fine for stateless PHP-FPM workers or Nginx fronts. It fails fast when MySQL, Redis, or Laravel storage holds uploads you cannot lose. Docker volumes and persistent data solve exactly that gap. They keep database files, logs, and user assets on the host or a remote driver while containers stay replaceable. Our Docker networking and volumes overview covers how containers share networks but not disks by default. This guide goes deeper on storage types, Compose patterns, backups, and the production mistakes I see on real deployments.
docker volume commands.What are Docker volumes and why do containers need persistent data?
Containers are designed to be ephemeral. Their union filesystem stacks a read-only image layer with a thin writable layer. Anything written there vanishes when you run docker rm or replace the container during a deploy. For a local Laravel dev stack with Sail and Docker, that means your MySQL tables, Redis keys, and storage/app uploads would reset on every rebuild unless you mount persistent storage.
Docker gives you three mount types. Each solves a different problem. Picking the wrong one is a common source of data loss and permission headaches on production servers.
On a production Laravel application I maintain, the pattern is predictable. The app container is disposable. MySQL, Redis, and the storage directory live on named volumes or bind mounts. Deployments swap the app image without touching customer data. That separation is the core reason teams adopt containers without giving up durability.
When ephemeral storage is enough
Stateless services do not need volumes. Nginx serving static assets from the image, a queue worker with no local cache, or a one-off Artisan command container can run without mounts. If losing the filesystem on restart causes zero business impact, skip the volume and keep ops simpler.
What is the difference between bind mounts, tmpfs mounts, and Docker volumes?
All three attach host storage to a container path. They differ in who manages the path, performance traits, and portability across machines. The official Docker storage documentation defines each type clearly. In practice, most teams use named volumes for databases and bind mounts for developer source code.
| Mount type | Managed by | Best for | Survives container removal |
|---|---|---|---|
| Named volume | Docker engine | MySQL, PostgreSQL, Redis, app uploads | Yes, until docker volume rm |
| Bind mount | You (host path) | Live code sync in dev, config files | Yes (host files remain) |
| tmpfs mount | Kernel memory | Secrets, temp cache, sensitive scratch data | No (RAM only) |
Named volumes
Docker creates named volumes under its own directory, usually /var/lib/docker/volumes/ on Linux. You reference them by name, not by absolute host path. That makes them portable across hosts when you use volume drivers or backup/restore workflows. For running PostgreSQL in Docker for development, a named volume is the right default.
Bind mounts
Bind mounts map a specific host directory into the container. Your Laravel project folder mounted at /var/www/html is the classic dev setup. Edits on the host appear instantly inside the container. On production, bind mounts tie you to a fixed server path. That can complicate migration unless you document paths carefully.
tmpfs mounts
tmpfs stores data in memory. Nothing hits disk. Use it for session scratch space or credentials you do not want written to a block device. Data disappears when the container stops. It is not a substitute for Redis caching and data structures that need durability across restarts.
How do you create and manage Docker volumes from the CLI?
The CLI is straightforward once you know the lifecycle commands. These work on any Linux host where you followed our Install Docker on Ubuntu guide.
Create and inspect a volume
docker volume create db_data
docker volume ls
docker volume inspect db_data The inspect output shows Mountpoint on the host. That path is managed by Docker. Do not edit files there while the database container is running unless you know the engine tolerates it.
Run a container with a named volume
docker run -d \
--name mysql_app \
-e MYSQL_ROOT_PASSWORD=secret \
-e MYSQL_DATABASE=laravel \
-v db_data:/var/lib/mysql \
mysql:9.7 The syntax is -v VOLUME_NAME:CONTAINER_PATH. The left side is the volume. The right side is where the application expects files inside the container. MySQL 9.7 is current; MySQL 8.4 LTS remains common on managed hosting if you need wider compatibility.
Mount a single host file
Sometimes you need one config file, not a whole directory. Docker supports bind-mounting a file directly:
docker run -d \
--name nginx_app \
-v /etc/letsencrypt:/etc/letsencrypt:ro \
-v ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro \
nginx:alpine The :ro flag makes the mount read-only. Use it for TLS certificates and static configs the container must not modify.
Remove volumes safely
docker stop mysql_app
docker rm mysql_app
docker volume rm db_data Never run docker volume prune on a production host without checking the list first. That command deletes every unused volume. I have seen staging databases vanish because someone ran prune after a failed deploy left volumes detached but still needed.
- Create the volume before first container start, or let Compose create it automatically.
- Mount at the path the application expects, not a convenience path you invent.
- Label volumes in Compose with clear names like
app_mysql_data. - Back up before upgrades that change major database versions.
- Document which volumes belong to which service in your runbook.
How do you configure Docker volumes in Docker Compose?
Most Laravel and WordPress stacks I ship use Compose for local dev and small production setups. Compose declares volumes at the top level and references them in each service. See also our Docker Compose multi-container apps walkthrough for full stack wiring.
services:
app:
build: .
volumes:
- app_storage:/var/www/html/storage/app
- ./:/var/www/html:cached
depends_on:
- mysql
- redis
mysql:
image: mysql:9.7
environment:
MYSQL_DATABASE: laravel
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
redis:
image: redis:8.10
command: redis-server --appendonly yes
volumes:
- redis_data:/data
volumes:
mysql_data:
redis_data:
app_storage: The top-level volumes: block tells Compose to create named volumes. Compose prefixes them with the project name by default. A project called booking yields a volume like booking_mysql_data. Override the name when you need stable identifiers across environments.
External volumes for shared data
When two Compose projects must share a database volume, mark the volume as external:
volumes:
shared_uploads:
external: true
name: production_app_storage Create the external volume once with docker volume create production_app_storage. Both stacks can then mount it. This pattern appears on trek booking platforms with Laravel and Livewire where a separate worker container processes uploads.
Override files for dev versus prod
Keep production bind mounts out of the base Compose file. Use an override for local development only. Our Docker Compose for local Laravel dev article shows the split. Production should mount named volumes, not your laptop source tree.
How do you backup and restore Docker volume data?
Containers are easy to recreate. Volume data is not. Backup strategy belongs in the same checklist as SSL renewal and database dumps. The Docker volume backup guide uses a temporary container to stream a tarball. That approach works on every Linux host without extra tools.
Backup a named volume
docker run --rm \
-v mysql_data:/source:ro \
-v $(pwd):/backup \
alpine tar czf /backup/mysql_data_$(date +%F).tar.gz -C /source . Store the tarball off the server. S3, Backblaze, or a second VPS is fine. A backup on the same disk as the volume protects against container mistakes, not disk failure.
Restore into a new volume
docker volume create mysql_data_restored
docker run --rm \
-v mysql_data_restored:/target \
-v $(pwd):/backup \
alpine sh -c "cd /target && tar xzf /backup/mysql_data_2026-09-01.tar.gz" Point the database container at mysql_data_restored and verify tables before cutting traffic over. For logical backups, mysqldump or pg_dump inside the running container is often safer across version upgrades.
Schedule nightly backups with cron on the host or a CI job in GitLab. Pair volume tarballs with logical dumps for MySQL 9.7 or PostgreSQL 18. Test restores quarterly. An untested backup is wishful thinking.
What are common Docker volume mistakes in production?
Most volume disasters are operational, not Docker bugs. Permissions, prune commands, and silent empty mounts cause more downtime than engine failures.
UID and GID mismatches
Laravel expects www-data to write to storage/. A bind mount owned by root on the host blocks uploads. Fix ownership on the host or set a user in the Dockerfile. On Ubuntu 22/24 servers I administer, aligning UID 33 inside and outside the container resolves most upload failures.
Anonymous volumes shadowing your data
Declaring -v /var/lib/mysql without a name creates an anonymous volume. It persists, but the random ID makes ops painful. Always use named volumes in production Compose files. Anonymous volumes also appear when an image declares a VOLUME instruction and you omit an explicit mount. Docker creates hidden storage you may not know exists.
Empty bind mount hiding image content
Mounting an empty host directory over /var/www/html/public hides built assets from the image. The site loads with no CSS. Mount only directories that need persistence or live sync. Keep compiled Vite 8.x assets inside the image for production.
Forgetting volume scope during migration
Moving containers to a new VPS without copying volumes gives you a fresh empty database. Plan volume export before DNS changes. Our website migration service always includes a storage audit for containerised apps. Treat volumes as first-class migration assets alongside DNS and SSL.
- Run
docker system df -vmonthly to see volume disk usage. - Set log rotation on apps writing logs to bind mounts.
- Use read-only root filesystem plus explicit writable volumes for tighter security.
- Pin image tags; do not pull
lateston production database containers. - Keep Compose project names stable so volume names do not drift.
For reverse-proxy setups, Traefik or Nginx configs often use bind mounts for certificates. Our Traefik reverse proxy for Docker guide covers read-only cert mounts. Pair that with named volumes for application state.
Resource limits matter too. A runaway log volume can fill the disk and crash MySQL. See limit Docker container resources for cgroup settings. Monitoring disk at the host level catches volume growth before services fail.
Teams building on Laravel 13.x with PHP 8.3 or PHP 8.5 should treat storage and bootstrap/cache as persistent or image-baked paths. Never assume a rebuilt container preserves locally generated keys. Mount storage or inject secrets via environment variables and CI.
If you outgrow single-host volumes, Kubernetes persistent volumes are the next step. Our Kubernetes persistent volumes and storage article covers CSI drivers and reclaim policies. Docker volumes teach the same concepts at smaller scale.
For JSON config stored in volumes, validate files after deploy with a JSON formatter tool before restarting dependent services. A trailing comma in a mounted config has taken down more staging stacks than any Docker upgrade.
Production support often means fixing volume permissions at 2 a.m. after a client ran the wrong command. Document volume names, backup paths, and restore steps in your internal wiki. Good support and maintenance processes prevent repeat incidents.
On booking systems like those built for grocery eCommerce with delivery zones, order history lives in MySQL volumes. Cart sessions may use Redis. Losing either volume during a deploy without backup is a business outage, not a dev inconvenience.
Compare this approach with traditional Deployer 7 symlink releases on bare metal. Both separate code from data. Containers just make the boundary explicit through mount declarations. Many sister sites I maintain still use symlinked releases while newer stacks use Compose on a single EC2 instance.
Whether you choose Docker or systemd plus PHP-FPM, the rule holds. Code is replaceable. Data is not. Volumes are how you enforce that boundary in a container world.
Key Takeaways
- Use named Docker volumes for databases, Redis AOF files, and Laravel storage that must survive container replacement.
- Reserve bind mounts for development source sync and read-only config; avoid empty host dirs shadowing image assets in production.
- Declare volumes explicitly in Compose with stable names and document them before running prune or migration scripts.
- Back up volumes with a temporary Alpine container and tar archive, then store copies off the host and test restores regularly.
- Fix UID/GID ownership on mounted paths and monitor disk usage with
docker system df -vto prevent silent fill-ups. - Treat Docker volumes and persistent data as migration assets equal to DNS and SSL when moving between servers.
People Also Ask
Do Docker volumes persist after the container is deleted?
Yes. Named volumes and bind mounts survive docker rm. Only the container writable layer is destroyed. You must run docker volume rm or docker volume prune to delete a named volume. That is why prune commands are dangerous on production hosts with detached but important volumes.
What is the difference between a Docker volume and a bind mount?
A Docker volume is managed by the engine and lives under Docker storage paths. A bind mount points to any host directory you specify. Volumes port cleanly across hosts with backup tools. Bind mounts give direct filesystem access, which helps local development but couples production to specific server paths.
Can multiple containers share the same Docker volume?
Yes, if the workload supports shared file access. Multiple read-only mounts are safe for static assets. Multiple writers to the same database data directory will corrupt MySQL or PostgreSQL. Share volumes for uploads only when the application coordinates file locks or uses object storage instead.
How do I see how much disk space Docker volumes use?
Run docker system df -v for per-volume size breakdown. On the host, du -sh /var/lib/docker/volumes/ shows total footprint. Set alerts when volume growth exceeds expected rates, especially for log and upload directories on small VPS plans common in Nepal hosting at Rs 1,500–3,000/month (~USD 11–22).
Ship durable container stacks with confidence
Docker volumes and persistent data are not optional extras for stateful apps. They are the contract that lets you rebuild containers daily without losing customer records, uploaded documents, or payment logs. Start with named volumes in Compose, add tested backups, and document every mount before your first production deploy. Need help containerising a Laravel app, migrating existing data, or hardening a Compose stack on Ubuntu? Contact us or explore our Linux system administration service and custom software development options. You can also browse the portfolio for production apps that rely on persistent storage every day.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

