Docker Compose — Named Volumes

2 minute read

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