How to Use Docker Volumes and Bind Mounts on RHEL 7

Containers are ephemeral by design — when a container is removed, any data written inside its writable layer is lost. Docker solves this with three types of persistent storage: named volumes, bind mounts, and tmpfs mounts. Named volumes are managed entirely by Docker and are the preferred solution for storing application data. Bind mounts map a specific directory on the host filesystem into a container, giving you direct access to host files. Tmpfs mounts store data in memory only, never touching disk. This tutorial covers all three on RHEL 7, including critical SELinux label considerations that are unique to Red Hat-based systems.

Prerequisites

  • Docker CE installed and the daemon running on RHEL 7
  • Root or sudo access
  • Basic familiarity with docker run syntax
  • SELinux in enforcing mode (the default on RHEL 7) — do not disable it

Step 1: Create and Use a Named Volume

Named volumes are stored under /var/lib/docker/volumes/ and are managed by the Docker daemon. They are the safest and most portable way to persist container data.

Create a named volume explicitly:

docker volume create myapp_data

Run a container that writes data to the volume:

docker run -d 
  --name myapp 
  -v myapp_data:/var/lib/myapp 
  nginx:1.25-alpine

The -v volumename:/container/path syntax mounts the named volume myapp_data at /var/lib/myapp inside the container. If the volume does not exist at docker run time, Docker creates it automatically.

Step 2: Inspect, List, and Remove Volumes

List all volumes managed by Docker:

docker volume ls

Inspect a specific volume to see its mount point on the host, driver, and labels:

docker volume inspect myapp_data

Sample output:

[
    {
        "CreatedAt": "2026-05-17T10:00:00Z",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/myapp_data/_data",
        "Name": "myapp_data",
        "Options": {},
        "Scope": "local"
    }
]

You can directly access the volume data on the host at the Mountpoint path (requires root). Remove a volume (only when no container is using it):

docker volume rm myapp_data

Remove all unused (dangling) volumes at once:

docker volume prune

Step 3: Use Bind Mounts

Bind mounts map a host directory or file directly into the container. Unlike named volumes, you control the exact host path. This is ideal for development workflows where you want code changes on the host to be reflected immediately inside the container.

mkdir -p /opt/myapp/config
echo "debug=true" > /opt/myapp/config/app.conf

docker run -d 
  --name myapp_bound 
  -v /opt/myapp/config:/etc/myapp:ro 
  nginx:1.25-alpine

The :ro suffix makes the mount read-only inside the container — the container cannot modify host files. Omit it for read-write access.

For a development workflow, mount your source code directory:

docker run -it --rm 
  -v /home/user/myproject:/app 
  -w /app 
  python:3.11-slim 
  bash

Step 4: SELinux Labels for Bind Mounts on RHEL 7

This is the most critical RHEL-specific consideration for bind mounts. When SELinux is in enforcing mode, a container process running with the container_t SELinux type will be denied access to host directories that carry the wrong SELinux context (typically user_home_t or httpd_sys_content_t). You will see Permission denied errors inside the container even though the Unix permissions look correct.

Docker provides two label options appended to the mount spec with a colon:

  • :z — Applies a shared relabel. Sets the SELinux context to container_file_t so multiple containers can share the same directory.
  • :Z — Applies a private relabel. Sets a unique MCS category pair so only this specific container can access the directory.

Without any label (risky — SELinux may deny access):

docker run -v /opt/myapp/data:/data nginx:alpine

With shared label (:z — safe for multiple containers sharing the same host path):

docker run -v /opt/myapp/data:/data:z nginx:alpine

With private label (:Z — safe when only one container should access the path):

docker run -v /opt/myapp/data:/data:Z nginx:alpine

Warning: Do not use :Z on system directories like /etc, /home, or /usr — it will relabel those directories permanently, potentially breaking the host system. Always use purpose-specific directories for bind mounts.

You can verify the SELinux context of a directory:

ls -Z /opt/myapp/data

After using :z or :Z, the context should show system_u:object_r:container_file_t:s0 or similar.

Step 5: Use tmpfs Mounts

A tmpfs mount writes data to the host’s memory (RAM) rather than to disk. The data is never persisted and is automatically discarded when the container stops. This is useful for sensitive data like secrets or temporary scratch space that must not be written to disk.

docker run -d 
  --name tmpfs_demo 
  --mount type=tmpfs,destination=/run/secrets,tmpfs-size=64m 
  nginx:1.25-alpine

You can also use the older --tmpfs flag:

docker run -d 
  --name tmpfs_demo2 
  --tmpfs /run/secrets:size=64m,mode=700 
  nginx:1.25-alpine

Note: SELinux labels (:z, :Z) do not apply to tmpfs mounts because no host filesystem path is involved.

Step 6: Named Volumes in docker-compose.yml

When using Docker Compose, declare named volumes at the top level of the file and reference them within service definitions. This ensures volumes are created and managed correctly as part of the application stack.

version: "3.9"

services:
  db:
    image: mariadb:10.11
    environment:
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_DATABASE: appdb
    volumes:
      - db_data:/var/lib/mysql

  app:
    image: myapp:latest
    volumes:
      - app_uploads:/var/www/html/uploads
      - ./config:/etc/myapp:z

volumes:
  db_data:
  app_uploads:

When you run docker compose up, Docker creates db_data and app_uploads as named volumes automatically. They persist across docker compose down restarts (unless you add -v to the down command).

To use an existing, pre-created volume in Compose rather than having Compose create a new one, mark it as external:

volumes:
  db_data:
    external: true

Conclusion

You now understand all three Docker storage mechanisms on RHEL 7: named volumes for durable, Docker-managed data; bind mounts for sharing host files with containers; and tmpfs mounts for in-memory ephemeral storage. The most important RHEL 7-specific detail is applying :z or :Z SELinux relabeling suffixes to bind mounts — without them, SELinux in enforcing mode will silently deny container access to host directories. Use named volumes as your default choice for production data, reserve bind mounts for development and configuration files, and use tmpfs for sensitive transient data that should never touch persistent storage.