🔥 0
0
Lesson 7 of 10 18 min +115 XP

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.

Volume vs Bind Mount:
AspectNamed VolumeBind Mount
Managed byDockerYou
LocationDocker's areaYour specified path
Use caseProduction dataDevelopment, config
Syntax-v name:/path-v /host/path:/path
PortabilityHighDepends 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!

🧠 Quick Quiz

Test your understanding of this lesson.

1

What happens to data inside a container when it's removed?

2

What's the difference between volumes and bind mounts?

3

Which flag creates a named volume called 'product-data'?

Connecting Store Services