Choosing the Right Storage Type
LEVEL 0
The Problem
You’re designing a Dockerized application. You need to decide:
- Where should database data go?
- Where should uploaded user files be stored?
- Where should logs be written?
- Where should temporary cache files live?
- Where should configuration be loaded from?
You have three options: volumes, bind mounts, or tmpfs. How do you choose?
LEVEL 1
The Decision Tree
Do you need the data to persist?
│
├─ NO → Use tmpfs mount
│ (Fast, memory-based, temporary)
│
└─ YES → Is this for production or development?
│
├─ PRODUCTION → Use Docker volume
│ (Managed, portable, backupable)
│
└─ DEVELOPMENT → Use bind mount
(Direct host access, easy editing)
LEVEL 2
Storage Type Comparison
| Scenario | Volume | Bind Mount | tmpfs |
|---|---|---|---|
| Production database | ✅ Best | ❌ No | ❌ No |
| Development database | ⚠️ OK | ⚠️ OK | ❌ No |
| Source code (dev) | ❌ No | ✅ Best | ❌ No |
| Static config files | ⚠️ OK | ✅ Best | ❌ No |
| User uploads (prod) | ✅ Best | ❌ No | ❌ No |
| Application logs | ✅ Best | ⚠️ OK | ❌ No |
| Build cache | ⚠️ OK | ❌ No | ✅ Best |
| Temp processing | ❌ No | ❌ No | ✅ Best |
| Session data (ephemeral) | ❌ No | ❌ No | ✅ Best |
| Secrets (runtime only) | ❌ No | ❌ No | ✅ Best |
LEVEL 3
Real-World Architecture Example
E-commerce application:
# --- DATABASE (PostgreSQL) ---
# Use volume: Data must persist, production-ready
docker volume create pgdata
docker run -d \
--name postgres \
-v pgdata:/var/lib/postgresql/data \
postgres:15
# --- WEB APPLICATION ---
# Production: Use volume for user uploads
docker volume create user-uploads
# Development: Use bind mount for source code
docker run -d \
--name webapp \
-v $(pwd)/src:/app:ro \
-v user-uploads:/app/uploads \
--tmpfs /app/cache:size=100m \
-p 8080:8080 \
my-web-app
# --- REDIS CACHE ---
# Use tmpfs: Data can be rebuilt, speed is critical
docker run -d \
--name cache \
--tmpfs /data:size=1g \
redis:7 \
redis-server --save ""
# --- NGINX (reverse proxy) ---
# Use bind mount: Config from host, read-only
docker run -d \
--name nginx \
-v /host/nginx.conf:/etc/nginx/nginx.conf:ro \
-p 80:80 -p 443:443 \
nginx
# --- LOG AGGREGATOR ---
# Use volume: Logs should persist for analysis
docker volume create app-logs
docker run -d \
--name logs \
-v app-logs:/logs \
log-collector
LEVEL 4
Migration Considerations
Moving from bind mounts to volumes (dev → prod):
Development:
docker run -v $(pwd)/data:/app/data myapp
Production:
docker volume create app-data
docker run -v app-data:/app/data myapp
Migrating existing bind mount data to volume:
# Copy data from host directory to volume
docker run --rm \
-v /host/data:/source:ro \
-v app-data:/dest \
alpine \
cp -a /source/. /dest/
LEVEL 5
Docker Compose Example
A complete application with proper storage:
version: '3.8'
volumes:
pgdata: # Production database
uploads: # User-uploaded files
app-logs: # Application logs
services:
database:
image: postgres:15
volumes:
- pgdata:/var/lib/postgresql/data # Volume
environment:
POSTGRES_PASSWORD: secret
webapp:
build: .
volumes:
- ./src:/app:ro # Bind mount (dev only)
- uploads:/app/uploads # Volume
- app-logs:/app/logs # Volume
tmpfs:
- /app/cache:size=200m # tmpfs
ports:
- "8080:8080"
depends_on:
- database
nginx:
image: nginx
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro # Bind mount (read-only)
ports:
- "80:80"
depends_on:
- webapp
cache:
image: redis:7
tmpfs:
- /data:size=1g # tmpfs (ephemeral cache)
For production, remove the bind mount for source code:
webapp:
image: myapp:latest # Use built image, not local source
volumes:
- uploads:/app/uploads
- app-logs:/app/logs
tmpfs:
- /app/cache:size=200m