Persisting Product Data
Containers are ephemeral - delete one, and all its data vanishes. For an e-commerce app, that means losing your product catalog, customer orders, and uploaded images.
Volumes solve this.
The Problem
Start a database, add data, remove the container:
# Start PostgreSQL
docker run -d --name test-db \
-e POSTGRES_PASSWORD=test \
postgres:15
# Add some data
docker exec -it test-db psql -U postgres -c "CREATE TABLE products (id INT, name TEXT);"
docker exec -it test-db psql -U postgres -c "INSERT INTO products VALUES (1, 'Headphones');"
docker exec -it test-db psql -U postgres -c "SELECT * FROM products;"
# Output: 1 | Headphones
# Remove the container
docker rm -f test-db
# Start a new one with same name
docker run -d --name test-db \
-e POSTGRES_PASSWORD=test \
postgres:15
# Check for data
docker exec -it test-db psql -U postgres -c "SELECT * FROM products;"
# ERROR: relation "products" does not exist
Your data is gone. In production, this would be catastrophic.
Docker Volumes
Volumes store data outside the container:
┌──────────────────────────────────────────────┐
│ Docker Host │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Container 1 │ │ Container 2 │ │
│ │ (removed) │ │ (new) │ │
│ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │
│ └──────────┬───────────┘ │
│ │ │
│ ┌───────▼────────┐ │
│ │ Volume: data │ │
│ │ (persists!) │ │
│ └────────────────┘ │
└──────────────────────────────────────────────┘
Creating Named Volumes
# Create a volume
docker volume create shopflow-db-data
# List volumes
docker volume ls
# Inspect volume details
docker volume inspect shopflow-db-data
Output:
{
"Name": "shopflow-db-data",
"Driver": "local",
"Mountpoint": "/var/lib/docker/volumes/shopflow-db-data/_data"
}
Using Volumes with Containers
# Run PostgreSQL with a volume
docker run -d \
--name shopflow-db \
-e POSTGRES_USER=shopflow \
-e POSTGRES_PASSWORD=secretpass \
-e POSTGRES_DB=shopflow \
-v shopflow-db-data:/var/lib/postgresql/data \
postgres:15
The -v shopflow-db-data:/var/lib/postgresql/data maps:
- shopflow-db-data (volume name) → /var/lib/postgresql/data (container path)
Now test persistence:
# Add data
docker exec -it shopflow-db psql -U shopflow -d shopflow -c "
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
price DECIMAL(10,2)
);
"
docker exec -it shopflow-db psql -U shopflow -d shopflow -c "
INSERT INTO products (name, price) VALUES
('Wireless Headphones', 79.99),
('USB-C Cable', 12.99),
('Laptop Stand', 49.99);
"
# Remove the container
docker rm -f shopflow-db
# Start a new container with SAME volume
docker run -d \
--name shopflow-db \
-e POSTGRES_USER=shopflow \
-e POSTGRES_PASSWORD=secretpass \
-e POSTGRES_DB=shopflow \
-v shopflow-db-data:/var/lib/postgresql/data \
postgres:15
# Check - data is still there!
docker exec -it shopflow-db psql -U shopflow -d shopflow -c "SELECT * FROM products;"
Output:
id | name | price
----+---------------------+-------
1 | Wireless Headphones | 79.99
2 | USB-C Cable | 12.99
3 | Laptop Stand | 49.99
Data persisted across container removal.
E-commerce Volume Setup
For a complete e-commerce app, you need several volumes:
# Database data
docker volume create shopflow-postgres-data
# Redis data (for session persistence)
docker volume create shopflow-redis-data
# Product images uploaded by sellers
docker volume create shopflow-uploads
# Start the stack with volumes
docker run -d \
--name shopflow-db \
--network shopflow-network \
-v shopflow-postgres-data:/var/lib/postgresql/data \
-e POSTGRES_USER=shopflow \
-e POSTGRES_PASSWORD=secretpass \
-e POSTGRES_DB=shopflow \
postgres:15
docker run -d \
--name shopflow-cache \
--network shopflow-network \
-v shopflow-redis-data:/data \
redis:7-alpine redis-server --appendonly yes
docker run -d \
--name product-api \
--network shopflow-network \
-v shopflow-uploads:/app/uploads \
-p 3000:3000 \
shopflow/product-api:1.0
Bind Mounts for Development
During development, you want code changes to reflect immediately without rebuilding.
Bind mounts map a host directory into the container:# Mount your local code into the container
docker run -d \
--name dev-api \
-v $(pwd)/src:/app/src \
-p 3000:3000 \
node:20-alpine \
sh -c "cd /app && npm install && npm run dev"
Now edit src/server.js on your machine - changes appear in the container instantly.
| Aspect | Named Volume | Bind Mount |
|---|---|---|
| Managed by | Docker | You |
| Location | Docker's area | Your specified path |
| Use case | Production data | Development, config |
| Syntax | -v name:/path | -v /host/path:/path |
| Portability | High | Depends on host |
Product Images Example
Handle uploaded product images:
// server.js
const express = require('express');
const multer = require('multer');
const path = require('path');
const app = express();
// Store uploads in /app/uploads (mounted volume)
const storage = multer.diskStorage({
destination: '/app/uploads/products',
filename: (req, file, cb) => {
const uniqueName = `${Date.now()}-${file.originalname}`;
cb(null, uniqueName);
}
});
const upload = multer({ storage });
app.post('/api/products/:id/image', upload.single('image'), (req, res) => {
res.json({
message: 'Image uploaded',
filename: req.file.filename,
path: `/uploads/products/${req.file.filename}`
});
});
// Serve uploaded images
app.use('/uploads', express.static('/app/uploads'));
app.listen(3000);
Dockerfile for uploads:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY src/ ./src/
# Create uploads directory
RUN mkdir -p /app/uploads/products
EXPOSE 3000
CMD ["node", "src/server.js"]
Run with uploads volume:
docker run -d \
--name product-api \
-v shopflow-uploads:/app/uploads \
-p 3000:3000 \
shopflow/product-api:1.0
Now product images persist even if you redeploy the API.
Volume Backup
Back up a volume by copying its data:
# Backup PostgreSQL data
docker run --rm \
-v shopflow-postgres-data:/source:ro \
-v $(pwd):/backup \
alpine \
tar czf /backup/db-backup-$(date +%Y%m%d).tar.gz -C /source .
# Restore from backup
docker run --rm \
-v shopflow-postgres-data:/target \
-v $(pwd):/backup \
alpine \
tar xzf /backup/db-backup-20240115.tar.gz -C /target
Volume Commands
# List all volumes
docker volume ls
# Create a volume
docker volume create my-volume
# Inspect volume details
docker volume inspect my-volume
# Remove a volume (must not be in use)
docker volume rm my-volume
# Remove all unused volumes
docker volume prune
# Remove ALL volumes (careful!)
docker volume prune -a
Read-Only Volumes
For configuration files, use read-only mounts:
docker run -d \
--name product-api \
-v ./config/production.json:/app/config.json:ro \
shopflow/product-api:1.0
The :ro suffix makes it read-only - the container can't modify it.
Key Takeaways
- Containers are ephemeral - data is lost on removal
- Named volumes persist data managed by Docker
- Bind mounts link host directories (great for development)
- E-commerce needs volumes for databases, uploads, and caches
- Use :ro for read-only configuration mounts
- Back up volumes regularly for production data
Next, let's manage secrets and environment variables for payment configuration!