Docker containers are ephemeral by default — all data written inside a container is lost when the container is removed. Docker provides two mechanisms for persisting data and sharing files between containers and the host: volumes (managed by Docker, stored in /var/lib/docker/volumes/) and bind mounts (map a host directory directly into a container). Volumes are the recommended approach for production data persistence — they are portable, can be backed up, migrated, and managed with docker volume commands, and Docker manages their permissions and lifecycle. Bind mounts are useful for development (mounting source code into a container for live editing) and for sharing configuration files. This guide covers creating and managing Docker volumes and bind mounts on RHEL 9 with practical database and web application examples.
Prerequisites
- Docker Engine installed on RHEL 9
Step 1 — Docker Volumes
# Create a named volume
docker volume create postgres_data
# List volumes
docker volume ls
# Inspect a volume (shows the host path)
docker volume inspect postgres_data
# Use a volume with a PostgreSQL container
docker run -d
--name postgres
-e POSTGRES_PASSWORD=secret
-v postgres_data:/var/lib/postgresql/data
postgres:16-alpine
# Verify data persists after container removal
docker stop postgres && docker rm postgres
docker run -d --name postgres -e POSTGRES_PASSWORD=secret
-v postgres_data:/var/lib/postgresql/data postgres:16-alpine
# Data is still there!
Step 2 — Bind Mounts
# Bind mount for development: mount local source code into container
mkdir -p /var/www/myapp/src
docker run -d
--name webapp
-p 8080:80
-v /var/www/myapp/html:/usr/share/nginx/html:ro
nginx:alpine
# :ro = read-only mount (container cannot write to the host directory)
# Omit :ro for read-write mounts (development use)
Step 3 — Volume in Docker Compose
# compose.yaml
services:
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: RootPass123!
MYSQL_DATABASE: myapp
volumes:
- mysql_data:/var/lib/mysql # Named volume — persistent
- ./mysql/init.sql:/docker-entrypoint-initdb.d/init.sql:ro # Bind mount for init scripts
web:
image: nginx:alpine
volumes:
- ./html:/usr/share/nginx/html:ro # Bind mount for content
- nginx_logs:/var/log/nginx # Named volume for logs
volumes:
mysql_data:
nginx_logs:
Step 4 — Back Up and Restore Volumes
# Back up a volume to a tar archive
docker run --rm
-v postgres_data:/data
-v /var/backups:/backup
alpine tar czf /backup/postgres_data_backup.tar.gz -C /data .
# Restore a volume from backup
docker volume create postgres_data_restored
docker run --rm
-v postgres_data_restored:/data
-v /var/backups:/backup
alpine tar xzf /backup/postgres_data_backup.tar.gz -C /data
Step 5 — Volume Management
# Remove a specific volume (only when no containers use it)
docker volume rm postgres_data
# Remove all unused volumes (CAUTION — destructive)
docker volume prune
# Check disk usage by volumes
docker system df -v | grep -A 20 "Volumes"
Conclusion
Docker volumes on RHEL 9 ensure data persists independently of container lifecycle. The key distinction: use named volumes for production data (databases, uploads, logs) — they survive docker compose down unless -v is added explicitly. Use bind mounts for development workflows where you need to edit code on the host and see changes reflected immediately in the container. Never store important data in a container’s writable layer — it is permanently lost on docker rm.
Next steps: How to Install Docker Engine on RHEL 9, How to Configure Docker Networking on RHEL 9, and How to Deploy Applications with Docker Compose on RHEL 9.