Skip to content

Docker Volumes

Containers are ephemeral — when a container is removed, all data inside it is lost. Volumes and bind mounts persist data beyond the container lifecycle.

TypeDescriptionUse Case
VolumeManaged by Docker, stored in Docker’s directoryDatabase data, persistent state
Bind mountMaps a host directory into the containerSource code, config files
tmpfsStored in memory onlySensitive data, temp files
Terminal window
# Create a volume
docker volume create my-data
# Use in a container
docker run -v my-data:/app/data my-image
# List volumes
docker volume ls
# Inspect a volume
docker volume inspect my-data
# Remove a volume
docker volume rm my-data
# Remove all unused volumes
docker volume prune

In Docker Compose:

services:
db:
image: postgres:16
volumes:
- postgres-data:/var/lib/postgresql/data
volumes:
postgres-data: # Docker manages this

Volumes persist when you run docker compose down. They’re removed with docker compose down -v.

Maps a host directory or file into the container:

Terminal window
# Mount source code (absolute path required)
docker run -v /Users/me/myapp:/app my-image
# Read-only bind mount
docker run -v /Users/me/config:/app/config:ro my-image

In Docker Compose:

services:
api:
volumes:
- .:/app # current directory → /app in container
- ./config:/app/config:ro

The most common use: mount source code so changes reflect immediately without rebuilding:

services:
api:
build: .
volumes:
- .:/app # mount source
- /app/node_modules # anonymous volume prevents host from overwriting node_modules
command: npm run dev

The anonymous volume /app/node_modules ensures the container’s node_modules (installed during build) isn’t overwritten by the empty node_modules on the host.

Terminal window
# Backup a volume to a tar file
docker run --rm \
-v my-data:/source \
-v $(pwd):/backup \
alpine tar czf /backup/my-data-backup.tar.gz -C /source .
# Restore from backup
docker run --rm \
-v my-data:/target \
-v $(pwd):/backup \
alpine tar xzf /backup/my-data-backup.tar.gz -C /target

Named volumes can be mounted in multiple containers simultaneously:

services:
api:
volumes:
- shared-uploads:/app/uploads
worker:
volumes:
- shared-uploads:/app/uploads
volumes:
shared-uploads:

For production, volumes can use external storage:

volumes:
my-data:
driver: local # default
driver_opts:
type: nfs
o: addr=192.168.1.100,rw
device: ":/nfs/data"

Cloud volume drivers: rexray/ebs (AWS), azurefile (Azure), gcepersistentdisk (GCP).

Make the root filesystem read-only and use volumes for writable paths:

Terminal window
docker run \
--read-only \
-v /tmp \ # writable tmpfs
-v my-logs:/app/logs \ # writable volume
my-image
  • Named volumes — database data, anything that needs to persist across container recreations
  • Bind mounts — development (live source code changes), configuration files, secrets files
  • tmpfs — temporary files, secrets that shouldn’t touch disk

Never store database data in the container’s writable layer — always use a named volume.