docker.recipes
Fundamentals7 min read

Persistent Volumes in Docker: Bind Mounts vs Named Volumes

Understand the difference between bind mounts and named volumes, and when to use each for persistent data.

01Introduction

Containers are ephemeral—when they're removed, their data is gone. For databases, config files, and user uploads, you need persistent storage. Docker offers two main options: bind mounts and named volumes. Each serves different use cases.

02Bind Mounts: Direct Host Access

Bind mounts map a specific path on your host to a path in the container. You control exactly where data lives on the filesystem. Great for config files and development, but comes with permission complexities.
1services:
2 nginx:
3 image: nginx:alpine
4 volumes:
5 # Bind mount: host path -> container path
6 - ./nginx.conf:/etc/nginx/nginx.conf:ro
7 - ./html:/usr/share/nginx/html
8 - /data/logs/nginx:/var/log/nginx
9
10 app:
11 image: node:20
12 volumes:
13 # Development: mount source code
14 - ./src:/app/src
15 - ./package.json:/app/package.json

Use :ro (read-only) for config files that containers should never modify.

03Named Volumes: Docker-Managed Storage

Named volumes are managed by Docker. They're stored in Docker's data directory (usually /var/lib/docker/volumes/). Docker handles permissions automatically, making them ideal for databases and application data.
1services:
2 postgres:
3 image: postgres:15
4 volumes:
5 # Named volume: Docker manages the location
6 - postgres_data:/var/lib/postgresql/data
7
8 redis:
9 image: redis:alpine
10 volumes:
11 - redis_data:/data
12
13volumes:
14 postgres_data: # Docker creates and manages this
15 redis_data:
16
17# Data lives in /var/lib/docker/volumes/projectname_postgres_data/_data

Named volumes survive docker compose down. Only docker volume rm or docker compose down -v removes them.

04When to Use Each

**Use Bind Mounts for:** - Configuration files you edit on the host - Development source code - Log files you want to access easily - Sharing files between host and container **Use Named Volumes for:** - Database data (PostgreSQL, MySQL, MongoDB) - Application data that containers manage - Data that should survive container recreation - When you don't care about the exact host path

05The Permissions Problem

Bind mounts inherit host filesystem permissions. If the container runs as a different user than your host user, you'll hit permission errors. Named volumes avoid this because Docker sets permissions correctly.
1# Common permission fix for bind mounts
2# Find the UID/GID the container uses
3docker exec container_name id
4
5# Option 1: Change host directory ownership
6sudo chown -R 1000:1000 ./data
7
8# Option 2: Set PUID/PGID (linuxserver.io images)
9services:
10 app:
11 image: linuxserver/sonarr
12 environment:
13 - PUID=1000
14 - PGID=1000
15 volumes:
16 - ./config:/config

Don't use chmod 777 as a fix. It's a security risk. Properly set ownership instead.

06Backup Considerations

Your volume choice affects backup strategies. Bind mounts are easy—just backup the host directory. Named volumes require either Docker commands or knowing the Docker storage path.
1# Backup a bind mount
2tar -czf backup.tar.gz /path/to/bind/mount
3
4# Backup a named volume
5docker run --rm \
6 -v postgres_data:/source:ro \
7 -v $(pwd):/backup \
8 alpine tar -czf /backup/postgres_data.tar.gz -C /source .
9
10# Or find the volume location
11docker volume inspect postgres_data --format '{{ .Mountpoint }}'
12# Then backup that path (requires root)

07Best Practices

1. Use named volumes for database data—they're designed for it 2. Use bind mounts for configs you need to edit 3. Document which volumes contain critical data 4. Use volume labels to add metadata 5. Regularly backup volumes (covered in our backup guide)
1volumes:
2 # Add labels for documentation
3 postgres_data:
4 labels:
5 com.example.description: "PostgreSQL database files"
6 com.example.backup: "daily"
7
8 # Use external volumes for truly persistent data
9 critical_data:
10 external: true
11 name: my_critical_data