Docker Compose — Named Volumes
Named volumes are Docker-managed storage that persists data across container restarts and recreations. They’re the recommended way to store database data, file uploads, and any data that must survive docker compose down.
Creating a Named Volume
Declare the volume in the top-level volumes: key and reference it in a service:
services:
db:
image: postgres:16
volumes:
- pgdata:/var/lib/postgresql/data # Use named volume
volumes:
pgdata: # Declare named volume
How Named Volumes Work
┌───────────── Docker Host ─────────────┐
│ │
│ ┌──────────────┐ │
│ │ Container │ │
│ │ (postgres) │ │
│ │ /var/lib/ │ │
│ │ postgresql/ │ │
│ │ data/ ──────┼───→ Volume: pgdata │
│ └──────────────┘ │ │
│ │ │
│ Storage location: │ │
│ /var/lib/docker/ │ │
│ volumes/pgdata/_data/ │ │
└────────────────────────┘────────────────┘
Docker manages the storage location. You don’t need to know or care where the data lives on the host — Docker handles it.
Named Volume Lifecycle
# Start the stack — volume is created
docker compose up -d
# Stop the stack — volume is KEPT
docker compose down
# ✅ pgdata still exists
# Start again — volume is reused
docker compose up -d
# ✅ All data is still there
# Stop and REMOVE volumes
docker compose down -v
# ❌ pgdata is removed — data gone!
Volume Options
volumes:
# Simple declaration
pgdata:
# With options
pgdata:
driver: local
labels:
com.example.description: "PostgreSQL data"
# External volume (created outside Compose)
shared-data:
external: true
External Volumes
An external volume is one that already exists on the Docker host:
# Create a volume manually
docker volume create shared-data
volumes:
shared-data:
external: true # Compose won't create or delete this volume
Compose will not create or remove external volumes — it expects them to already exist.
Practical Example: PostgreSQL with Persistent Data
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: admin
POSTGRES_PASSWORD: secret
POSTGRES_DB: myapp
volumes:
- pgdata:/var/lib/postgresql/data
ports:
- "5432:5432"
volumes:
pgdata:
Test data persistence:
# Start and create some data
docker compose up -d
docker compose exec db psql -U admin -d myapp -c "CREATE TABLE test (id int);"
docker compose exec db psql -U admin -d myapp -c "INSERT INTO test VALUES (42);"
# Stop (not -v!)
docker compose down
# Start again
docker compose up -d
# Data is still there!
docker compose exec db psql -U admin -d myapp -c "SELECT * FROM test;"
# id
# ----
# 42
Inspecting Volumes
# List all volumes
docker volume ls
# Inspect a specific volume
docker volume inspect myapp_pgdata
[
{
"CreatedAt": "2025-01-01T00:00:00Z",
"Driver": "local",
"Labels": {
"com.docker.compose.project": "myapp",
"com.docker.compose.volume": "pgdata"
},
"Mountpoint": "/var/lib/docker/volumes/myapp_pgdata/_data",
"Name": "myapp_pgdata",
"Options": null,
"Scope": "local"
}
]
Summary
| Feature | Detail |
|---|---|
| Declaration | Top-level volumes: key |
| Persistence | Survives docker compose down |
| Removal | docker compose down -v removes them |
| Docker-managed | Stored in /var/lib/docker/volumes/ |
| External | external: true for pre-existing volumes |
Quick Reference
| Item | Detail |
|---|---|
| Named volume | volumes: pgdata: (top-level declaration) |
| Mount syntax | pgdata:/var/lib/postgresql/data |
| Persists through | docker compose down (without -v) |
| Remove volumes | docker compose down -v |
| Inspect | docker volume inspect myapp_pgdata |
| External | external: true — pre-existing volume |