Learning Guides
Menu

Managing Data and Volumes

9 min readDocker for Developers

Managing Data and Volumes

Containers are ephemeral—when they're deleted, their data disappears. This chapter covers how to persist data using volumes, bind mounts, and tmpfs mounts, along with strategies for managing application data effectively.

The Storage Problem

By default, all files created inside a container are stored in a writable container layer:

BASH
# Create a file in a container
docker run --name mycontainer alpine sh -c "echo 'Hello' > /data.txt"
 
# File exists
docker exec mycontainer cat /data.txt  # Hello
 
# Remove container
docker rm mycontainer
 
# Data is gone forever

Warning

Container filesystems are temporary. Any data not stored in volumes is lost when the container is removed. This is by design—containers should be stateless and reproducible.

Storage Options

Docker provides three ways to persist data:

PLAINTEXT
┌─────────────────────────────────────────────────────────┐
│                    Container                             │
│  ┌─────────────────────────────────────────────────┐    │
│  │              Writable Layer                      │    │
│  │         (Ephemeral - lost on remove)            │    │
│  └─────────────────────────────────────────────────┘    │
│         ▲              ▲              ▲                 │
│         │              │              │                 │
│  ┌──────┴──────┐ ┌─────┴─────┐ ┌─────┴─────┐           │
│  │   Volume    │ │Bind Mount │ │   tmpfs   │           │
│  │             │ │           │ │           │           │
│  │ Docker-     │ │ Host      │ │ Memory    │           │
│  │ managed     │ │ directory │ │ only      │           │
│  └─────────────┘ └───────────┘ └───────────┘           │
└─────────────────────────────────────────────────────────┘

Storage Types Comparison

TypeLocationManaged ByPersistenceUse Case
VolumeDocker areaDockerPermanentProduction data
Bind mountHost filesystemYouPermanentDevelopment, config
tmpfsMemoryKernelUntil restartSensitive temp data

Docker Volumes

Volumes are the preferred way to persist data. Docker manages them completely.

Creating Volumes

BASH
# Create a named volume
docker volume create mydata
 
# Create with options
docker volume create --driver local \
  --opt type=nfs \
  --opt o=addr=192.168.1.1,rw \
  --opt device=:/path/to/dir \
  nfs-volume
 
# Volumes are created automatically when used
docker run -v newvolume:/data alpine

Listing and Inspecting Volumes

BASH
# List all volumes
docker volume ls
 
# Inspect a volume
docker volume inspect mydata
 
# Find where volume is stored
docker volume inspect mydata --format '{{.Mountpoint}}'
# /var/lib/docker/volumes/mydata/_data

Using Volumes

BASH
# Mount volume at container path
docker run -d \
  --name postgres \
  -v pgdata:/var/lib/postgresql/data \
  postgres:16
 
# Multiple volumes
docker run -d \
  -v app-data:/app/data \
  -v app-logs:/app/logs \
  myapp
 
# Read-only volume
docker run -d \
  -v config:/app/config:ro \
  myapp

Note

The -v or --volume flag uses the format volume-name:container-path[:options]. The :ro option mounts the volume as read-only.

Volume with --mount Syntax

The --mount syntax is more explicit and recommended:

BASH
# Using --mount
docker run -d \
  --mount type=volume,source=pgdata,target=/var/lib/postgresql/data \
  postgres:16
 
# With options
docker run -d \
  --mount type=volume,source=config,target=/app/config,readonly \
  myapp

-v vs --mount

BASH
# -v syntax (shorter but less clear)
docker run -v myvolume:/data:ro myapp
 
# --mount syntax (more explicit)
docker run --mount type=volume,source=myvolume,target=/data,readonly myapp
 
# --mount fails if volume doesn't exist (safer)
# -v creates the volume automatically

Removing Volumes

BASH
# Remove a volume
docker volume rm mydata
 
# Remove all unused volumes
docker volume prune
 
# Force remove (no confirmation)
docker volume prune -f
 
# Remove container and its volumes
docker rm -v mycontainer

Warning

Be careful with docker volume prune—it removes ALL volumes not attached to containers. This can cause data loss.

Bind Mounts

Bind mounts map a host directory to a container path. Essential for development workflows.

Using Bind Mounts

BASH
# Mount current directory
docker run -v $(pwd):/app myapp
 
# Mount specific directory
docker run -v /home/user/project:/app myapp
 
# Using --mount syntax
docker run --mount type=bind,source=/home/user/project,target=/app myapp
 
# Read-only bind mount
docker run -v /etc/config:/app/config:ro myapp

Development Workflow

Bind mounts enable live code changes:

BASH
# Mount source code for development
docker run -d \
  --name dev-server \
  -v $(pwd)/src:/app/src \
  -v $(pwd)/package.json:/app/package.json \
  -p 3000:3000 \
  node:20-alpine npm run dev
 
# Changes to local ./src are immediately reflected in container

Note

On macOS and Windows, bind mounts can be slower than on Linux due to filesystem translation. Docker Desktop provides optimization options for improved performance.

Bind Mount Permissions

BASH
# Specify user/group mapping
docker run -v $(pwd):/app -u $(id -u):$(id -g) myapp
 
# Match host user ID
docker run -v $(pwd):/app --user 1000:1000 myapp

File vs Directory Mounts

BASH
# Mount a single file
docker run -v $(pwd)/config.json:/app/config.json myapp
 
# Mount a directory
docker run -v $(pwd)/config:/app/config myapp

Warning

When mounting a single file, the file must exist before the container starts. For directories, Docker creates them if they don't exist.

tmpfs Mounts

tmpfs mounts store data in memory—never written to disk.

Using tmpfs

BASH
# Create tmpfs mount
docker run -d \
  --tmpfs /app/temp \
  myapp
 
# With options
docker run -d \
  --mount type=tmpfs,target=/app/temp,tmpfs-size=100m,tmpfs-mode=1777 \
  myapp
 
# Multiple tmpfs mounts
docker run -d \
  --tmpfs /app/temp \
  --tmpfs /app/cache \
  myapp

tmpfs Use Cases

  • Sensitive data that shouldn't persist (tokens, keys during build)
  • High-speed temporary storage
  • Preventing writes to container filesystem

tmpfs for Secrets

BASH
# Sensitive data in tmpfs (never hits disk)
docker run -d \
  --tmpfs /run/secrets:size=1m,mode=0700 \
  -e SECRET_FILE=/run/secrets/api-key \
  myapp

Volume Backup and Restore

Backup a Volume

BASH
# Backup volume to tar file
docker run --rm \
  -v mydata:/source:ro \
  -v $(pwd):/backup \
  alpine tar czf /backup/mydata-backup.tar.gz -C /source .
 
# Alternative: copy to stdout
docker run --rm \
  -v mydata:/source:ro \
  alpine tar czf - -C /source . > mydata-backup.tar.gz

Restore a Volume

BASH
# Restore from backup
docker run --rm \
  -v mydata:/target \
  -v $(pwd):/backup \
  alpine tar xzf /backup/mydata-backup.tar.gz -C /target
 
# Or from stdin
docker run --rm -i \
  -v mydata:/target \
  alpine tar xzf - -C /target < mydata-backup.tar.gz

Copy Between Volumes

BASH
# Copy data between volumes
docker run --rm \
  -v source-vol:/source:ro \
  -v dest-vol:/dest \
  alpine cp -a /source/. /dest/

Database Volume Patterns

PostgreSQL

BASH
# PostgreSQL with named volume
docker run -d \
  --name postgres \
  -e POSTGRES_PASSWORD=secret \
  -v postgres-data:/var/lib/postgresql/data \
  -p 5432:5432 \
  postgres:16
 
# Check data directory
docker exec postgres ls -la /var/lib/postgresql/data

MySQL

BASH
# MySQL with named volume
docker run -d \
  --name mysql \
  -e MYSQL_ROOT_PASSWORD=secret \
  -v mysql-data:/var/lib/mysql \
  -p 3306:3306 \
  mysql:8

MongoDB

BASH
# MongoDB with named volume
docker run -d \
  --name mongo \
  -v mongo-data:/data/db \
  -p 27017:27017 \
  mongo:7

Note

Always check the official image documentation for the correct data directory path. Using the wrong path means your data won't persist.

Volume Drivers

Docker supports volume plugins for different storage backends.

Local Driver Options

BASH
# Create volume with specific options
docker volume create \
  --driver local \
  --opt type=none \
  --opt o=bind \
  --opt device=/path/on/host \
  my-local-vol
 
# NFS volume
docker volume create \
  --driver local \
  --opt type=nfs \
  --opt o=addr=nfs-server.example.com,rw \
  --opt device=:/exported/path \
  nfs-vol

Third-Party Volume Drivers

Popular volume drivers for production:

  • REX-Ray: Multi-platform storage orchestration
  • NetApp: NetApp storage integration
  • Portworx: Cloud-native storage
  • AWS EBS/EFS: Amazon storage services
BASH
# Install and use a volume driver
docker plugin install rexray/ebs
docker volume create --driver rexray/ebs --opt size=10 ebs-vol

Volume Best Practices

Use Named Volumes in Production

BASH
# GOOD: Named volume (easy to manage)
docker run -v postgres-data:/var/lib/postgresql/data postgres:16
 
# AVOID: Anonymous volume (hard to track)
docker run -v /var/lib/postgresql/data postgres:16

Separate Data and Application

BASH
# Separate volumes for different data types
docker run -d \
  -v app-data:/app/data \      # Application data
  -v app-logs:/app/logs \      # Logs (may rotate differently)
  -v app-uploads:/app/uploads \ # User uploads
  myapp

Use Read-Only Where Possible

BASH
# Application code should be read-only in production
docker run -d \
  -v app-code:/app:ro \
  -v app-data:/app/data \
  myapp

Don't Store Secrets in Volumes

BASH
# AVOID: Secrets in volumes
docker run -v secrets:/secrets myapp
 
# BETTER: Use Docker secrets or environment variables
docker run -e DATABASE_URL=postgres://... myapp

Troubleshooting Volumes

Check Volume Contents

BASH
# Start a debug container to inspect volume
docker run -it --rm \
  -v mydata:/data \
  alpine sh
 
# Inside the container:
ls -la /data
du -sh /data

Find Volume Usage

BASH
# Find which containers use a volume
docker ps -a --filter volume=mydata
 
# Check volume size
docker system df -v | grep mydata

Permission Issues

BASH
# Check permissions inside container
docker exec mycontainer ls -la /data
 
# Fix permissions (careful!)
docker run --rm \
  -v mydata:/data \
  alpine chown -R 1000:1000 /data

Warning

Permission issues are common when the container user doesn't match the volume's file ownership. Use matching UID/GID or adjust ownership during container startup.

Volume Not Mounting

BASH
# Verify volume exists
docker volume ls | grep myvolume
 
# Check mount in running container
docker inspect mycontainer --format '{{json .Mounts}}'
 
# View mount from inside container
docker exec mycontainer df -h
docker exec mycontainer mount | grep /data

Quick Reference

CommandPurpose
docker volume createCreate a volume
docker volume lsList volumes
docker volume inspectView volume details
docker volume rmRemove a volume
docker volume pruneRemove unused volumes

Mount Options Summary

OptionPurpose
typevolume, bind, or tmpfs
sourceVolume name or host path
targetPath inside container
readonlyMount as read-only
volume-driverVolume driver name
volume-optDriver-specific options

In the next chapter, we'll explore Docker Compose for orchestrating multi-container applications.