
Docker Impl Storage
- 13 installs
- 9 repo stars
- Updated July 8, 2026
- openaec-foundation/docker-claude-skill-package
Helps with devops & ci/cd tasks.
About
docker-impl-storage is a Claude Code skill for devops & ci/cd. It helps solo builders move faster with AI-assisted development.
- docker-impl-storage
- DevOps & CI/CD
- AI-coding skill
Docker Impl Storage by the numbers
- 13 all-time installs (skills.sh)
- Ranked #965 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/docker-claude-skill-package --skill docker-impl-storageAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 13 |
|---|---|
| repo stars | ★ 9 |
| Last updated | July 8, 2026 |
| Repository | openaec-foundation/docker-claude-skill-package ↗ |
What it does
Helps with devops & ci/cd tasks.
Files
docker-impl-storage
Quick Reference
Storage Types
| Type | Persistence | Managed By | Location | Use Case |
|---|---|---|---|---|
| Named volume | Yes | Docker (/var/lib/docker/volumes/) | Docker-managed | Databases, shared data, backups |
| Anonymous volume | Until container removed | Docker | Docker-managed | Temporary per-container data |
| Bind mount | Yes | Host filesystem | Any host path | Development, config injection |
| tmpfs | No (RAM only) | Kernel | Memory | Secrets, temp files, performance |
Critical Warnings
NEVER use anonymous volumes for database data -- data is lost when the container is removed with --rm. ALWAYS use named volumes for any data that must survive container recreation.
NEVER use -v syntax with volume drivers or driver options -- -v does not support them. ALWAYS use --mount when configuring volume drivers, NFS, or CIFS mounts.
NEVER run docker system prune -a --volumes on production systems without first checking docker volume ls -- this removes ALL unused volumes including database data.
NEVER mount /var/lib/docker/ as a bind mount inside a container -- this causes filesystem handle conflicts and Unable to remove filesystem errors.
ALWAYS use --mount syntax for production workloads and documentation -- it is explicit, self-documenting, and supports all mount options.
ALWAYS verify mount destination paths match the application's data directory -- mounting to the wrong path silently obscures existing container data.
---
Mount Type Decision Tree
Need to persist data?
├── NO → tmpfs mount (RAM-only, fastest, lost on stop)
│ docker run --mount type=tmpfs,dst=/tmp,tmpfs-size=64m IMAGE
│
└── YES → Need Docker to manage the storage?
├── YES → Named volume (portable, backupable, driver support)
│ docker run --mount type=volume,src=mydata,dst=/data IMAGE
│
└── NO → Need host filesystem access?
├── YES → Bind mount (direct host path access)
│ docker run --mount type=bind,src=/host/path,dst=/app IMAGE
│
└── NO → Named volume (default choice for persistence)When to Use Each Type
| Scenario | Mount Type | Reason |
|---|---|---|
| Database storage | Named volume | Survives container lifecycle, portable |
| Development source code | Bind mount | Live editing from host |
| Config file injection | Bind mount (read-only) | Host-managed configuration |
| Temporary build artifacts | tmpfs | Fast, no disk I/O, auto-cleaned |
| Secrets at runtime | tmpfs | Never written to disk |
| Shared data between containers | Named volume | Multiple containers mount same volume |
| NFS/CIFS network storage | Named volume + driver | Volume drivers handle network mounts |
| CI/CD build cache | Named volume | Persists between pipeline runs |
---
--mount vs -v Syntax Comparison
--mount Syntax (Preferred)
Key-value pairs, explicit and self-documenting:
# Named volume
docker run --mount type=volume,src=mydata,dst=/data nginx
# Bind mount
docker run --mount type=bind,src=/host/path,dst=/container/path nginx
# tmpfs
docker run --mount type=tmpfs,dst=/tmp,tmpfs-size=64m nginx
# Read-only volume
docker run --mount source=data,destination=/data,readonly nginx
# Volume with subdirectory
docker run --mount src=logs,dst=/var/log/app1,volume-subpath=app1 app1-v Syntax (Quick Development Only)
Three colon-separated fields: [name:]container-path[:options]
docker run -v mydata:/data nginx # Named volume
docker run -v /host/path:/app nginx # Bind mount
docker run -v mydata:/data:ro nginx # Read-onlyKey Differences
| Feature | --mount | -v |
|---|---|---|
| Syntax | Key-value pairs | Colon-separated positional |
| Volume drivers | Supported | NOT supported |
| Volume options | Supported | NOT supported |
| Missing host dir (bind) | Error (safe) | Auto-creates (silent) |
| Clarity | Self-documenting | Position-dependent |
| Subpath mounting | Supported | NOT supported |
--mount Option Reference
| Option | Applies To | Description |
|---|---|---|
type | All | volume, bind, or tmpfs |
source / src | volume, bind | Volume name or host path |
destination / dst / target | All | Container mount path |
readonly / ro | volume, bind | Read-only access |
volume-subpath | volume | Mount subdirectory within volume |
volume-nocopy | volume | Skip copying container data into empty volume |
volume-opt | volume | Driver-specific options (repeatable) |
volume-driver | volume | Volume driver name |
tmpfs-size | tmpfs | Size limit in bytes |
tmpfs-mode | tmpfs | File mode (e.g., 1770) |
---
Volume Lifecycle
Auto-Population Behavior
When mounting an empty named volume to a container directory that has existing files, Docker copies those files into the volume:
# Nginx HTML files get copied into nginx-vol on first mount
docker run -d --mount source=nginx-vol,destination=/usr/share/nginx/html nginxNEVER rely on auto-population for production data -- it only happens once when the volume is empty. Use explicit initialization instead.
Named vs Anonymous Volumes
| Aspect | Named Volume | Anonymous Volume |
|---|---|---|
| Creation | Explicit name provided | Auto-generated hash ID |
| Reuse | Easy to reference by name | Must use random ID |
Cleanup with --rm | Persists | Auto-removed |
| Sharing | Easy between containers | Difficult |
| Backup | Straightforward | Error-prone |
Simultaneous Mounting
Multiple containers can mount the same volume simultaneously. ALWAYS use read-only mounts for consumers that do not need write access:
# Writer container
docker run --mount source=shared,dst=/data mywriter
# Reader container (read-only)
docker run --mount source=shared,dst=/data,readonly myreader---
Volume Drivers
Local Driver (Default)
Stores data on the host filesystem. Supports NFS, CIFS, and block devices via options.
NFS Volumes
# NFSv3
docker volume create --driver local \
--opt type=nfs \
--opt device=:/var/docker-nfs \
--opt o=addr=10.0.0.10 \
nfs-vol
# NFSv4
docker volume create --driver local \
--opt type=nfs \
--opt device=:/var/docker-nfs \
--opt "o=addr=10.0.0.10,rw,nfsvers=4,async" \
nfs-volCIFS/SMB Volumes
docker volume create --driver local \
--opt type=cifs \
--opt device=//server.example.com/backup \
--opt o=addr=server.example.com,username=user,password=pass,file_mode=0777,dir_mode=0777 \
--name cifs-volThe addr option is REQUIRED when using a hostname instead of an IP address.
Third-Party Volume Drivers
# Install plugin
docker plugin install --grant-all-permissions rclone/docker-volume-rclone
# Create volume with plugin -- MUST use --mount syntax
docker run --mount type=volume,volume-driver=rclone,src=remote-vol,target=/app nginx---
Database Persistence Patterns
PostgreSQL
docker run -d --name postgres \
--mount source=pgdata,target=/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=secret \
postgres:16MySQL
docker run -d --name mysql \
--mount source=mysqldata,target=/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=secret \
mysql:8MongoDB
docker run -d --name mongo \
--mount source=mongodata,target=/data/db \
mongo:7ALWAYS use named volumes for database containers. See references/examples.md for Compose patterns and backup procedures.
---
Storage Cleanup Strategy
# 1. Assess current usage FIRST
docker system df -v
# 2. List dangling volumes (not attached to any container)
docker volume ls -f dangling=true
# 3. Remove specific unused volumes
docker volume rm VOLUME_NAME
# 4. Remove ALL unused anonymous volumes
docker volume prune -f
# 5. Targeted prune (exclude labeled volumes)
docker volume prune --filter "label!=keep"
# 6. Full system cleanup (DANGEROUS on production)
docker system prune -a --volumes -fALWAYS run docker system df before pruning to understand what consumes space. NEVER run docker volume prune on production without verifying which volumes are unused.
---
Compose Volume Integration
services:
db:
image: postgres:16
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata: # Named volume (managed by Compose)See references/examples.md for NFS volumes, external volumes, and multi-service patterns in Compose.
---
Reference Links
- references/mount-types.md -- Complete comparison of volumes, bind mounts, and tmpfs with all options
- references/examples.md -- Database persistence, backup/restore, NFS volumes, Compose volume patterns
- references/anti-patterns.md -- Common storage mistakes and how to avoid them
Official Sources
- https://docs.docker.com/engine/storage/
- https://docs.docker.com/engine/storage/volumes/
- https://docs.docker.com/reference/cli/docker/volume/
- https://docs.docker.com/compose/how-tos/volumes/
Storage Anti-Patterns
Common Docker storage mistakes, why they fail, and the correct approach.
---
AP-01: Anonymous Volumes for Database Data
The Mistake
# Anonymous volume -- no name specified
docker run -d --rm postgres:16Why It Fails
Anonymous volumes receive a random hash as their name. When the container is removed (especially with --rm), the anonymous volume is also removed. All database data is permanently lost.
Correct Approach
# ALWAYS use named volumes for database data
docker run -d --name postgres \
--mount source=pgdata,target=/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=secret \
postgres:16---
AP-02: Using -v Syntax for Volume Drivers
The Mistake
# -v does NOT support volume driver options
docker run -v nfs-data:/data nginx
# No way to specify NFS options with -vWhy It Fails
The -v flag only supports three colon-separated fields: name:path:options. It cannot pass volume-driver, volume-opt, or other advanced mount options.
Correct Approach
# ALWAYS use --mount for volume drivers
docker run --mount type=volume,volume-driver=local,src=nfs-data,dst=/data,\
volume-opt=type=nfs,volume-opt=device=:/nfs/share,volume-opt=o=addr=10.0.0.1 nginx---
AP-03: Wrong Mount Path for Database
The Mistake
# Wrong path -- PostgreSQL data is NOT at /data
docker run -d \
--mount source=pgdata,target=/data \
postgres:16Why It Fails
Each database stores data at a specific path. Mounting a volume to the wrong path means the database writes to the container's ephemeral filesystem instead, and data is lost on container removal.
Correct Approach
ALWAYS verify the data directory for each database:
| Database | Correct Path |
|---|---|
| PostgreSQL | /var/lib/postgresql/data |
| MySQL/MariaDB | /var/lib/mysql |
| MongoDB | /data/db |
| Redis | /data |
| Elasticsearch | /usr/share/elasticsearch/data |
---
AP-04: Bind Mount Auto-Creates Missing Directories
The Mistake
# -v silently creates /nonexistent/path as an empty directory
docker run -v /nonexistent/path:/data myappWhy It Fails
With -v syntax, Docker auto-creates missing host directories. The application receives an empty directory instead of the expected data, leading to silent data loss or startup errors that are difficult to diagnose.
Correct Approach
# --mount raises an error if the host path does not exist
docker run --mount type=bind,src=/host/data,dst=/data myapp
# Error: bind mount source path does not exist: /host/dataALWAYS use --mount for bind mounts. The explicit error prevents silent failures.
---
AP-05: Mounting /var/lib/docker Inside a Container
The Mistake
docker run -v /var/lib/docker:/var/lib/docker myappWhy It Fails
Mounting Docker's internal storage directory causes filesystem handle conflicts. Containers that bind-mount this path hold open handles that prevent Docker from cleaning up resources, producing Unable to remove filesystem errors.
Correct Approach
NEVER mount /var/lib/docker into containers. If you need Docker access inside a container (Docker-in-Docker), use the Docker socket:
# Docker socket (for Docker CLI access, not storage)
docker run -v /var/run/docker.sock:/var/run/docker.sock myapp---
AP-06: No Backup Strategy for Volumes
The Mistake
Relying on Docker volumes as the sole copy of important data without any backup procedure.
Why It Fails
docker volume prune removes all unused volumes. docker system prune --volumes does the same. A single accidental command can destroy all database data.
Correct Approach
# Regular volume backup
docker run --rm \
--mount source=pgdata,target=/data,readonly \
-v /backups:/backup \
alpine tar czf /backup/pgdata-$(date +%Y%m%d).tar.gz -C /data .
# Label volumes that need backup
docker volume create --label backup=true pgdataALWAYS implement automated backup for production volumes. ALWAYS label volumes to distinguish critical from disposable data.
---
AP-07: Blind docker system prune --volumes on Production
The Mistake
# Removes ALL unused volumes including database data
docker system prune -a --volumes -fWhy It Fails
If a database container is stopped (not running), its volume is considered "unused" and is removed. This permanently destroys production data.
Correct Approach
# 1. Check what will be removed FIRST
docker system df -v
docker volume ls -f dangling=true
# 2. Remove only specific resources
docker container prune -f
docker image prune -f
# 3. Remove volumes ONLY after verifying
docker volume rm specific-volume-name
# 4. Use label-based filtering
docker volume prune --filter "label!=keep" -f---
AP-08: UID/GID Mismatch on Bind Mounts
The Mistake
# Container runs as UID 1000, host files owned by UID 0
docker run -v /host/data:/data myapp
# Result: Permission deniedWhy It Fails
Bind mounts map host files directly into the container. If the container process runs as a different UID/GID than the file owner on the host, permission errors occur.
Correct Approach
# Option 1: Match container user to host UID
docker run -u $(id -u):$(id -g) -v /host/data:/data myapp
# Option 2: Set ownership in Dockerfile
RUN chown -R 1001:1001 /data
USER 1001:1001
# Option 3: Use named volumes (Docker manages permissions)
docker run --mount source=appdata,dst=/data myappNamed volumes avoid UID/GID issues because Docker manages the filesystem permissions.
---
AP-09: Storing Secrets in Volumes
The Mistake
# Secret file persisted in a volume
docker run -v secrets:/run/secrets myapp
echo "password123" | docker exec -i myapp tee /run/secrets/db_passwordWhy It Fails
Volume data persists on disk and survives container removal. Secrets in volumes can be accessed by any container that mounts the volume and remain on disk even after the container is deleted.
Correct Approach
# Use tmpfs for runtime secrets -- never written to disk
docker run --mount type=tmpfs,dst=/run/secrets myapp
# Or use Docker secrets (Swarm mode)
echo "password123" | docker secret create db_password -
docker service create --secret db_password myapp---
AP-10: Not Using read_only for Consumer Containers
The Mistake
services:
writer:
volumes:
- shared:/data
reader:
volumes:
- shared:/data # Read-write, but only readsWhy It Fails
Without explicit read-only access, any bug in the reader service can corrupt shared data. Multiple writers to the same volume without coordination causes data corruption.
Correct Approach
services:
writer:
volumes:
- shared:/data
reader:
volumes:
- shared:/data:ro # Explicitly read-onlyALWAYS mount volumes as read-only for services that do not need write access.
---
AP-11: Ignoring Volume Cleanup in CI/CD
The Mistake
Running docker compose up and docker compose down in CI/CD pipelines without --volumes, leaving orphaned volumes after every build.
Why It Fails
Each CI/CD run creates new anonymous volumes. Over time, these consume all available disk space on the CI runner, causing no space left on device errors.
Correct Approach
# ALWAYS clean up volumes in CI/CD
docker compose down --volumes --remove-orphans
# Or use docker system prune in CI cleanup
docker system prune -a --volumes -fIn CI/CD environments (where data persistence is not needed), ALWAYS include --volumes in cleanup commands.
---
AP-12: Using VOLUME in Dockerfile Without Intent
The Mistake
# Creates an anonymous volume at /data for EVERY container
VOLUME /dataWhy It Fails
The VOLUME instruction in a Dockerfile forces Docker to create an anonymous volume at that path for every container created from the image. This:
- Prevents overriding with a bind mount in some edge cases
- Creates orphaned anonymous volumes that consume disk space
- Cannot be un-declared by downstream images
Correct Approach
Do NOT use VOLUME in Dockerfiles unless you have a specific technical reason. Let users choose their mount strategy at runtime:
# User decides the mount type at runtime
docker run --mount source=mydata,dst=/data myapp---
Summary: Storage Do's and Don'ts
| Do | Don't |
|---|---|
| Use named volumes for databases | Use anonymous volumes for important data |
Use --mount syntax in production | Use -v with volume drivers |
| Verify mount paths match app data dirs | Assume generic paths like /data |
Use --mount for bind mounts (safe errors) | Use -v for bind mounts (auto-creates) |
| Implement automated volume backups | Rely on volumes as sole data copy |
Check docker system df before pruning | Run docker system prune --volumes blindly |
| Match container UID to host file ownership | Ignore UID/GID on bind mounts |
| Use tmpfs for secrets | Store secrets in persistent volumes |
| Mount read-only for consumers | Give write access to all consumers |
| Clean volumes in CI/CD pipelines | Leave orphaned volumes after builds |
| Let users choose mounts at runtime | Use VOLUME in Dockerfile without intent |
Storage Examples
Working examples for Docker storage patterns. All examples verified against Docker Engine 24+ and Docker Compose v2.
---
Database Persistence Patterns
PostgreSQL with Named Volume
# Create and run with named volume
docker run -d --name postgres \
--mount source=pgdata,target=/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=secret \
-e POSTGRES_DB=myapp \
-p 5432:5432 \
postgres:16
# Verify volume created
docker volume inspect pgdata
# Stop and remove container -- data persists
docker stop postgres && docker rm postgres
# Start new container with same volume -- data intact
docker run -d --name postgres \
--mount source=pgdata,target=/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=secret \
-p 5432:5432 \
postgres:16MySQL with Named Volume
docker run -d --name mysql \
--mount source=mysqldata,target=/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=secret \
-e MYSQL_DATABASE=myapp \
-p 3306:3306 \
mysql:8MongoDB with Named Volume
docker run -d --name mongo \
--mount source=mongodata,target=/data/db \
-e MONGO_INITDB_ROOT_USERNAME=admin \
-e MONGO_INITDB_ROOT_PASSWORD=secret \
-p 27017:27017 \
mongo:7Redis with Named Volume
docker run -d --name redis \
--mount source=redisdata,target=/data \
redis:7 redis-server --appendonly yesDatabase Data Directories Reference
| Database | Data Directory | Volume Target |
|---|---|---|
| PostgreSQL | /var/lib/postgresql/data | /var/lib/postgresql/data |
| MySQL | /var/lib/mysql | /var/lib/mysql |
| MongoDB | /data/db | /data/db |
| Redis | /data | /data |
| Elasticsearch | /usr/share/elasticsearch/data | /usr/share/elasticsearch/data |
| MariaDB | /var/lib/mysql | /var/lib/mysql |
---
Backup and Restore Procedures
Backup a Named Volume
# Method 1: tar backup via helper container
docker run --rm \
--mount source=pgdata,target=/data,readonly \
-v $(pwd):/backup \
alpine tar czf /backup/pgdata-backup.tar.gz -C /data .
# Method 2: Backup from a running database container
docker run --rm \
--volumes-from postgres:ro \
-v $(pwd):/backup \
alpine tar czf /backup/pgdata-backup.tar.gz -C /var/lib/postgresql/data .Restore a Named Volume
# Create fresh volume
docker volume create pgdata-restored
# Restore from backup
docker run --rm \
--mount source=pgdata-restored,target=/data \
-v $(pwd):/backup \
alpine sh -c "cd /data && tar xzf /backup/pgdata-backup.tar.gz"
# Use restored volume
docker run -d --name postgres \
--mount source=pgdata-restored,target=/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=secret \
postgres:16PostgreSQL-Native Backup (pg_dump)
# Logical backup (SQL dump)
docker exec postgres pg_dump -U postgres myapp > myapp-backup.sql
# Restore from SQL dump
docker exec -i postgres psql -U postgres myapp < myapp-backup.sql
# Compressed backup
docker exec postgres pg_dump -U postgres -Fc myapp > myapp-backup.dump
# Restore compressed
docker exec -i postgres pg_restore -U postgres -d myapp < myapp-backup.dumpMySQL-Native Backup (mysqldump)
# Backup
docker exec mysql mysqldump -u root -psecret myapp > myapp-backup.sql
# Restore
docker exec -i mysql mysql -u root -psecret myapp < myapp-backup.sqlAutomated Backup Script
#!/bin/bash
# backup-volumes.sh — Backup all named volumes
BACKUP_DIR="/backups/$(date +%Y%m%d)"
mkdir -p "$BACKUP_DIR"
for volume in $(docker volume ls -q --filter "label=backup=true"); do
echo "Backing up volume: $volume"
docker run --rm \
--mount source="$volume",target=/data,readonly \
-v "$BACKUP_DIR":/backup \
alpine tar czf "/backup/${volume}.tar.gz" -C /data .
done
# Remove backups older than 30 days
find /backups -type f -name "*.tar.gz" -mtime +30 -delete---
NFS Volume Examples
NFSv3 Volume
docker volume create --driver local \
--opt type=nfs \
--opt device=:/var/docker-nfs \
--opt o=addr=10.0.0.10 \
nfs-dataNFSv4 Volume
docker volume create --driver local \
--opt type=nfs \
--opt device=:/var/docker-nfs \
--opt "o=addr=10.0.0.10,rw,nfsvers=4,async" \
nfs-dataNFS Volume in Compose
services:
app:
image: myapp:latest
volumes:
- nfs-data:/app/data
volumes:
nfs-data:
driver: local
driver_opts:
type: nfs
device: ":/var/docker-nfs"
o: "addr=10.0.0.10,rw,nfsvers=4,async"CIFS/SMB Volume
docker volume create --driver local \
--opt type=cifs \
--opt device=//fileserver.example.com/shared \
--opt o=addr=fileserver.example.com,username=dockeruser,password=secret,file_mode=0777,dir_mode=0777 \
--name cifs-dataCIFS Volume in Compose
volumes:
cifs-data:
driver: local
driver_opts:
type: cifs
device: "//fileserver.example.com/shared"
o: "addr=fileserver.example.com,username=dockeruser,password=secret"---
Docker Compose Volume Patterns
Basic Named Volume
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:Shared Volume Between Services
services:
writer:
image: mywriter:latest
volumes:
- shared-data:/data
reader:
image: myreader:latest
volumes:
- shared-data:/data:ro # Read-only access
volumes:
shared-data:External Volume (Pre-Created)
# Volume MUST exist before running docker compose up
# Create it first: docker volume create pgdata-prod
services:
db:
image: postgres:16
volumes:
- pgdata-prod:/var/lib/postgresql/data
volumes:
pgdata-prod:
external: trueBind Mount for Development
services:
app:
image: node:20-alpine
working_dir: /app
volumes:
- ./src:/app/src # Bind mount for live reload
- node_modules:/app/node_modules # Named volume for deps
command: npm run dev
volumes:
node_modules:Full-Stack Application with Volumes
services:
web:
image: nginx:alpine
ports:
- "80:80"
volumes:
- static-files:/usr/share/nginx/html:ro
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- app
app:
build: .
volumes:
- static-files:/app/static
- uploads:/app/uploads
depends_on:
- db
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
static-files:
uploads:
pgdata:Compose Volume with Labels
volumes:
pgdata:
labels:
backup: "true"
project: "myapp"
environment: "production"tmpfs in Compose
services:
app:
image: myapp:latest
read_only: true
tmpfs:
- /tmp
- /run
volumes:
- appdata:/data---
Development Workflow Examples
Node.js Development with Volume Optimization
services:
app:
image: node:20-alpine
working_dir: /app
volumes:
- .:/app # Source code bind mount
- node_modules:/app/node_modules # Preserve node_modules in volume
command: npm run dev
ports:
- "3000:3000"
volumes:
node_modules:Using a named volume for node_modules prevents host OS native module conflicts and improves performance.
Python Development with pip Cache
services:
app:
build: .
volumes:
- .:/app
- pip-cache:/root/.cache/pip
command: python manage.py runserver 0.0.0.0:8000
volumes:
pip-cache:---
Read-Only Container with Selective Write Access
# Production hardened container
docker run -d --name secure-app \
--read-only \
--tmpfs /tmp:size=64m \
--tmpfs /run \
--mount source=appdata,dst=/app/data \
--mount type=bind,src=/etc/myapp/config.yml,dst=/app/config.yml,readonly \
myapp:latestCompose equivalent:
services:
app:
image: myapp:latest
read_only: true
tmpfs:
- /tmp:size=64m
- /run
volumes:
- appdata:/app/data
- ./config.yml:/app/config.yml:ro
volumes:
appdata:---
Volume Inspection and Debugging
# List all volumes with details
docker volume ls --format "table {{.Name}}\t{{.Driver}}\t{{.Labels}}"
# Check volume mount point on host
docker volume inspect --format '{{.Mountpoint}}' mydata
# See what volumes a container uses
docker inspect --format='{{range .Mounts}}{{.Name}} -> {{.Destination}} ({{.Type}}){{println}}{{end}}' myapp
# Check volume size (via container)
docker run --rm --mount source=mydata,dst=/data,readonly alpine du -sh /data
# Find containers using a volume
docker ps -a --filter volume=mydata --format "{{.Names}}"Mount Types Reference
Complete comparison of Docker storage mount types for Docker Engine 24+.
---
Named Volumes
What They Are
Docker-managed directories stored at /var/lib/docker/volumes/<name>/_data on the host. The preferred mechanism for persisting data generated by and used by Docker containers.
Creation
# Explicit creation
docker volume create mydata
docker volume create --label project=web mydata
# Implicit creation (created on first use)
docker run --mount type=volume,src=mydata,dst=/data nginxProperties
| Property | Value |
|---|---|
| Managed by | Docker Engine |
| Location | /var/lib/docker/volumes/ |
| Survives container removal | Yes |
Survives --rm flag | Yes (named volumes only) |
| Shareable between containers | Yes |
| Supports volume drivers | Yes |
| Supports backup via tar | Yes |
| Auto-populates from container | Yes (when volume is empty) |
| Works on all platforms | Yes |
--mount Options for Volumes
# Full option set
docker run --mount \
type=volume,\
src=mydata,\
dst=/data,\
readonly,\
volume-nocopy,\
volume-driver=local,\
volume-opt=type=nfs,\
volume-opt=device=:/nfs/share,\
volume-opt=o=addr=10.0.0.1 \
nginx| Option | Default | Description |
|---|---|---|
type=volume | (required) | Specifies volume mount type |
src / source | (anonymous if omitted) | Volume name |
dst / destination / target | (required) | Container mount path |
readonly / ro | false | Read-only access |
volume-nocopy | false | Prevent auto-copying container data to empty volume |
volume-subpath | — | Mount subdirectory within the volume |
volume-driver | local | Volume driver |
volume-opt | — | Driver-specific option (repeatable) |
Volume Subpath Mounting
Mount a specific subdirectory within a volume. The subdirectory MUST exist in the volume before mounting.
# Share one volume with multiple apps, each in its own subdirectory
docker run --mount src=logs,dst=/var/log/app1,volume-subpath=app1 app1
docker run --mount src=logs,dst=/var/log/app2,volume-subpath=app2 app2Volume Management Commands
# List all volumes
docker volume ls
docker volume ls --filter dangling=true
docker volume ls --filter label=project=web
docker volume ls --format "{{.Name}}: {{.Driver}}"
docker volume ls -q # Names only
# Inspect volume details
docker volume inspect mydata
docker volume inspect --format '{{.Mountpoint}}' mydata
# Remove specific volume
docker volume rm mydata
docker volume rm -f mydata # Force
# Remove ALL unused volumes
docker volume prune
docker volume prune -f # No confirmation
docker volume prune --filter "label!=keep" # Except labeled volumesAuto-Population Behavior
When a named volume is empty and mounted to a container directory that already has files, Docker copies the container's files into the volume. This happens ONLY once:
# First run: nginx HTML files copied into nginx-vol
docker run -d --mount source=nginx-vol,destination=/usr/share/nginx/html nginx
# Subsequent runs: volume already has data, no copy occursNEVER rely on auto-population for critical data. ALWAYS initialize volumes explicitly.
---
Anonymous Volumes
What They Are
Volumes without a user-specified name. Docker assigns a random hash as the name. Created when a Dockerfile has a VOLUME instruction or when -v is used without a name.
Properties
| Property | Value |
|---|---|
| Managed by | Docker Engine |
| Survives container removal | Only if not using --rm |
| Easy to identify | No (random hash name) |
| Easy to reuse | No |
Cleaned by docker volume prune | Yes |
When They Appear
# In Dockerfile — creates anonymous volume at /data
VOLUME /data# On the CLI — anonymous volume (no name before colon)
docker run -v /data nginxALWAYS use named volumes instead of anonymous volumes for any data you need to keep.
---
Bind Mounts
What They Are
Direct mapping from a host filesystem path to a container path. The host path can be any directory or file on the host machine.
Syntax
# --mount syntax (recommended)
docker run --mount type=bind,src=/host/path,dst=/container/path nginx
# -v syntax
docker run -v /host/path:/container/path nginx
# Read-only
docker run --mount type=bind,src=/host/config,dst=/etc/app/config,readonly nginx
docker run -v /host/config:/etc/app/config:ro nginxProperties
| Property | Value |
|---|---|
| Managed by | Host filesystem |
| Location | Any host path |
| Survives container removal | Yes (data on host) |
| Container modifies host files | Yes (unless read-only) |
| Supports volume drivers | No |
| Performance | Native (no Docker overhead) |
| Portable | No (depends on host path) |
Key Behavior Differences
| Behavior | --mount | -v |
|---|---|---|
| Host path does not exist | ERROR (safe) | Auto-creates directory (silent) |
This is a critical safety difference. ALWAYS use --mount for bind mounts to catch missing paths.
Common Bind Mount Uses
# Development: mount source code for live reload
docker run --mount type=bind,src=$(pwd)/src,dst=/app/src node:20
# Configuration: inject config file (read-only)
docker run --mount type=bind,src=/etc/myapp/config.yml,dst=/app/config.yml,readonly myapp
# Logs: write container logs to host directory
docker run --mount type=bind,src=/var/log/myapp,dst=/app/logs myappBind Mount Caveats
1. Non-portable: Host path must exist on every machine where the container runs 2. Security risk: Container can modify host files (use readonly when possible) 3. Obscures container data: Bind mount hides any existing files at the mount destination 4. UID/GID mismatches: Container process UID may not match host file ownership
---
tmpfs Mounts
What They Are
Temporary filesystem stored in host memory (RAM). Data is NEVER written to the host filesystem and is lost when the container stops.
Syntax
# --mount syntax
docker run --mount type=tmpfs,dst=/tmp,tmpfs-size=64m nginx
# --tmpfs flag
docker run --tmpfs /tmp:size=64k nginx
# Multiple tmpfs mounts
docker run --mount type=tmpfs,dst=/tmp --mount type=tmpfs,dst=/run nginxProperties
| Property | Value |
|---|---|
| Stored in | Host RAM |
| Survives container stop | No |
| Survives container restart | No |
| Shareable between containers | No |
| Platform support | Linux only |
| Performance | Fastest (memory speed) |
tmpfs Options
| Option | Description | Example |
|---|---|---|
tmpfs-size | Size limit in bytes | tmpfs-size=67108864 (64 MB) |
tmpfs-mode | File permissions | tmpfs-mode=1770 |
When to Use tmpfs
| Scenario | Why tmpfs |
|---|---|
| Application secrets at runtime | Never written to disk, gone when container stops |
| Temporary build artifacts | Fast I/O, no disk wear |
| Session data | Ephemeral by nature |
| Scratch space for computation | Memory-speed I/O |
| Read-only root FS exceptions | --read-only --tmpfs /tmp --tmpfs /run |
---
Mount Type Comparison Matrix
| Feature | Named Volume | Anonymous Volume | Bind Mount | tmpfs |
|---|---|---|---|---|
| Persists data | Yes | Conditional | Yes (host) | No |
| Docker-managed | Yes | Yes | No | No |
| Portable | Yes | No | No | N/A |
| Shareable | Yes | Difficult | Yes | No |
| Volume drivers | Yes | Yes | No | No |
| Backup support | Yes | Difficult | Host tools | N/A |
| Performance | Good | Good | Native | Best |
| Platform support | All | All | All | Linux only |
| Auto-populate | Yes | Yes | No | No |
| Read-only option | Yes | Yes | Yes | No |
---
volumes-from Flag
Mount all volumes from another container:
# Create data container
docker create --name dbstore -v /dbdata postgres:16
# Mount volumes from dbstore
docker run --volumes-from dbstore myapp
# Mount as read-only
docker run --volumes-from dbstore:ro myappALWAYS prefer named volumes over --volumes-from for new projects. --volumes-from is a legacy pattern that creates implicit dependencies.
---
Read-Only Root Filesystem
Combine --read-only with tmpfs and volumes for a hardened container:
docker run --read-only \
--tmpfs /tmp \
--tmpfs /run \
--mount source=appdata,dst=/data \
myappThis prevents any writes to the container's root filesystem. ALWAYS use this pattern for production containers where possible.