Docker 容器的设计是临时性的——容器被删除时,其中所有数据都会丢失。Docker 提供三种存储机制来将数据持久化到容器生命周期之外:卷(volumes)、绑定挂载(bind mounts)和 tmpfs 挂载。本指南涵盖 Docker 存储的方方面面,从基本卷命令到高级驱动配置、备份策略和性能调优。
存储类型概览
Docker 为容器提供三种主要存储选项。了解它们的区别对于为每个场景选择正确的方案至关重要。
| 存储类型 | 管理者 | 主机位置 | 最佳用途 |
|---|---|---|---|
| Volume | Docker | /var/lib/docker/volumes/ | 生产数据、数据库 |
| Bind Mount | 用户 | 任意宿主机路径 | 开发环境、配置文件 |
| tmpfs | Docker | 内存(RAM) | 密钥、临时数据(仅 Linux) |
# Comparison of storage types in docker run
# Named volume
docker run -v mydata:/app/data nginx
# Bind mount
docker run -v /home/user/data:/app/data nginx
# tmpfs mount
docker run --tmpfs /app/temp nginx
# Using --mount flag (more explicit)
docker run --mount type=volume,source=mydata,target=/app/data nginx
docker run --mount type=bind,source=/home/user/data,target=/app/data nginx
docker run --mount type=tmpfs,target=/app/temp nginx命名卷
命名卷是 Docker 中最常用且推荐的数据持久化方式。Docker 管理卷的完整生命周期,包括创建、存储位置和清理。
创建和使用命名卷
# Create a named volume
docker volume create mydata
# Create with specific driver and options
docker volume create --driver local \
--opt type=none \
--opt device=/path/to/data \
--opt o=bind \
my-bind-vol
# Use a named volume in docker run
docker run -d \
--name postgres-db \
-v pgdata:/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=secret \
postgres:16
# Volume is auto-created if it doesn't exist
docker run -d -v mydata:/data alpine
# Use --mount for explicit volume creation
docker run -d \
--mount type=volume,source=mydata,target=/data \
alpine
# Inspect volume details
docker volume inspect mydata
# Output:
# [
# {
# "CreatedAt": "2025-01-15T10:30:00Z",
# "Driver": "local",
# "Labels": {},
# "Mountpoint": "/var/lib/docker/volumes/mydata/_data",
# "Name": "mydata",
# "Options": {},
# "Scope": "local"
# }
# ]卷生命周期
命名卷独立于容器持久化。它们在容器停止、重启和删除后仍然存在。删除命名卷的唯一方式是通过 docker volume rm 或 docker volume prune 显式删除。
# Create container with volume
docker run -d --name app1 -v shared-data:/data alpine sleep 3600
# Write data inside the volume
docker exec app1 sh -c 'echo "Hello from app1" > /data/message.txt'
# Stop and remove the container
docker stop app1 && docker rm app1
# Data persists! Create new container with same volume
docker run --rm -v shared-data:/data alpine cat /data/message.txt
# Output: Hello from app1
# Multiple containers can share the same volume
docker run -d --name writer -v shared-data:/data alpine sh -c \
'while true; do date >> /data/log.txt; sleep 5; done'
docker run -d --name reader -v shared-data:/data:ro alpine tail -f /data/log.txt绑定挂载
绑定挂载将宿主机的特定目录或文件直接映射到容器中。宿主机路径必须是绝对路径。绑定挂载非常适合需要实时代码重载的开发工作流。
绑定挂载语法
# Short syntax: -v host_path:container_path
docker run -v /home/user/project:/app node:20
# Short syntax with read-only
docker run -v /home/user/config:/etc/app/config:ro nginx
# Long syntax: --mount (recommended for clarity)
docker run --mount type=bind,source=/home/user/project,target=/app node:20
# --mount with read-only
docker run \
--mount type=bind,source=/home/user/config,target=/etc/app/config,readonly \
nginx
# Key difference: -v creates the host dir if missing
# --mount fails with an error if host dir doesn't exist
docker run -v /nonexistent/path:/data alpine # Creates /nonexistent/path
docker run --mount type=bind,source=/nonexistent/path,target=/data alpine # ERROR使用绑定挂载的开发工作流
绑定挂载在开发中表现出色——在宿主机上编辑代码,更改立即出现在容器内,无需重建镜像。
# React development with hot reload
docker run -d \
--name react-dev \
-v $(pwd)/src:/app/src \
-v $(pwd)/public:/app/public \
-p 3000:3000 \
-e CHOKIDAR_USEPOLLING=true \
my-react-app
# Python Flask development
docker run -d \
--name flask-dev \
-v $(pwd):/app \
-p 5000:5000 \
-e FLASK_DEBUG=1 \
my-flask-app
# Go development with air (live reload)
docker run -d \
--name go-dev \
-v $(pwd):/app \
-p 8080:8080 \
cosmtrek/airDocker Compose 卷
Docker Compose 提供声明式方式来定义服务的卷。卷可以使用短语法或长语法来定义,长语法提供更多控制选项。
短语法
services:
db:
image: postgres:16
volumes:
# Named volume
- pgdata:/var/lib/postgresql/data
# Bind mount (relative path)
- ./init-scripts:/docker-entrypoint-initdb.d
# Bind mount (absolute path)
- /var/log/postgres:/var/log/postgresql
# Read-only bind mount
- ./config/postgresql.conf:/etc/postgresql/postgresql.conf:ro
# Anonymous volume
- /var/lib/postgresql/temp
volumes:
pgdata: # Named volume declaration
driver: local长语法
长语法提供更多配置选项,包括只读模式、卷子路径和绑定传播。
services:
web:
image: nginx:alpine
volumes:
# Long syntax - named volume
- type: volume
source: web-data
target: /usr/share/nginx/html
volume:
nocopy: true # Don't copy container data into volume
# Long syntax - bind mount
- type: bind
source: ./nginx.conf
target: /etc/nginx/nginx.conf
read_only: true
# Long syntax - tmpfs
- type: tmpfs
target: /tmp
tmpfs:
size: 100000000 # 100MB limit
volumes:
web-data:外部卷和共享卷
使用外部卷在多个 Compose 项目之间共享数据,或引用在 Compose 之外创建的卷。
# Project A: creates the volume
# docker-compose.yml (Project A)
services:
api:
image: my-api
volumes:
- shared-data:/data
volumes:
shared-data:
name: my-shared-data # Explicit name (not prefixed with project name)
---
# Project B: uses the same volume
# docker-compose.yml (Project B)
services:
worker:
image: my-worker
volumes:
- shared-data:/data
volumes:
shared-data:
external: true
name: my-shared-data # Must match the volume name from Project A
---
# Pre-create a volume externally
docker volume create --driver local my-shared-data卷驱动
Docker 支持可插拔的卷驱动,允许将数据存储在远程主机或云服务商。默认驱动是 "local",将数据存储在 Docker 宿主机上。
带选项的本地驱动
# Local driver with tmpfs backend (RAM-based volume)
docker volume create --driver local \
--opt type=tmpfs \
--opt device=tmpfs \
--opt o=size=256m \
ramdisk
# Local driver with ext4 filesystem on a specific device
docker volume create --driver local \
--opt type=ext4 \
--opt device=/dev/sdb1 \
fast-storage
# Local driver binding to a specific directory
docker volume create --driver local \
--opt type=none \
--opt device=/mnt/data/app \
--opt o=bind \
app-dataNFS 卷驱动
# NFS volume using the local driver
docker volume create --driver local \
--opt type=nfs \
--opt o=addr=192.168.1.100,rw,nfsvers=4 \
--opt device=:/exports/data \
nfs-data
# NFS volume in Docker Compose
services:
app:
image: my-app
volumes:
- nfs-data:/app/data
volumes:
nfs-data:
driver: local
driver_opts:
type: nfs
o: addr=192.168.1.100,rw,nfsvers=4
device: ":/exports/data"
# CIFS/SMB volume (Windows shares)
docker volume create --driver local \
--opt type=cifs \
--opt device=//server/share \
--opt o=addr=server,username=user,password=pass \
smb-data云卷驱动
在云端生产工作负载中,使用特定云服务商的卷驱动将云存储直接挂载到容器中。
# AWS EFS volume (using amazon-ecs-volume-plugin or docker plugin)
# First install the plugin
docker plugin install rexray/efs
# Create volume backed by EFS
docker volume create --driver rexray/efs \
--opt filesystem=fs-12345678 \
efs-data
# Azure Files in Docker Compose
volumes:
azure-data:
driver: azure_file
driver_opts:
share_name: myshare
storage_account_name: mystorageaccount
# GCP Filestore / persistent disk (via CSI or plugin)
# Typically used with Kubernetes rather than standalone Docker只读卷
:ro 标志(或长语法中的 read_only 选项)将卷以只读方式挂载到容器内。这是关键的安全实践——如果容器被攻破,它无法修改挂载的数据。
# Read-only with -v flag
docker run -v myconfig:/etc/app/config:ro nginx
# Read-only with --mount
docker run \
--mount type=volume,source=myconfig,target=/etc/app/config,readonly \
nginx
# Read-only bind mount
docker run -v $(pwd)/nginx.conf:/etc/nginx/nginx.conf:ro nginx
# Docker Compose - short syntax
services:
web:
volumes:
- ./config:/etc/app/config:ro
- certs:/etc/ssl/certs:ro
# Docker Compose - long syntax
services:
web:
volumes:
- type: bind
source: ./config
target: /etc/app/config
read_only: true
# Entire container filesystem as read-only (--read-only flag)
docker run --read-only \
--tmpfs /tmp \
--tmpfs /run \
-v data:/app/data \
my-app只读挂载的安全优势
- 防止容器修改配置文件
- 保护宿主机文件系统免受被攻破容器的影响
- 强制执行不可变基础设施模式
- 减少容器逃逸漏洞的影响范围
数据持久化
了解数据何时持久化、何时丢失,对于可靠的容器部署至关重要。
| 场景 | 卷数据 | 绑定挂载数据 | 容器数据 |
|---|---|---|---|
| 容器停止 | 保留 | 保留 | 保留 |
| 容器重启 | 保留 | 保留 | 保留 |
| 容器删除 | 保留 | 保留 | 丢失 |
| docker compose down | 保留 | 保留 | 丢失 |
| docker compose down -v | 丢失 | 保留 | 丢失 |
| docker volume prune | 删除(未使用的) | 保留 | N/A |
备份与恢复
Docker 没有提供内置的卷备份命令。标准方法是使用临时容器挂载卷并创建 tar 归档。
备份卷
# Backup a named volume to a tar.gz file
docker run --rm \
-v mydata:/source:ro \
-v $(pwd)/backups:/backup \
alpine tar czf /backup/mydata-$(date +%Y%m%d).tar.gz -C /source .
# Backup PostgreSQL volume (prefer pg_dump for consistency)
docker exec postgres-db pg_dump -U postgres mydb > backup.sql
# Backup MySQL volume
docker exec mysql-db mysqldump -u root -p mydb > backup.sql
# Backup with compression and timestamp
docker run --rm \
-v pgdata:/source:ro \
-v $(pwd)/backups:/backup \
alpine sh -c 'tar czf /backup/pgdata-$(date +%Y%m%d-%H%M%S).tar.gz -C /source .'恢复卷
# Restore a volume from backup
docker volume create mydata-restored
docker run --rm \
-v mydata-restored:/target \
-v $(pwd)/backups:/backup:ro \
alpine sh -c 'cd /target && tar xzf /backup/mydata-20250115.tar.gz'
# Restore PostgreSQL from SQL dump
docker exec -i postgres-db psql -U postgres mydb < backup.sql
# Clone a volume (backup + restore in one step)
docker run --rm \
-v source-vol:/source:ro \
-v target-vol:/target \
alpine sh -c 'cd /source && tar cf - . | (cd /target && tar xf -)'使用 --volumes-from 备份
--volumes-from 标志挂载另一个容器的所有卷,当你不知道确切的卷挂载路径时非常有用。
# Backup all volumes from a running container
docker run --rm \
--volumes-from my-running-container \
-v $(pwd)/backups:/backup \
alpine tar czf /backup/all-volumes.tar.gz \
/var/lib/postgresql/data \
/etc/app/config
# This is useful when containers have multiple volumes
# and you want a single backup of all data自动备份脚本
#!/bin/bash
# backup-volumes.sh - Automated Docker volume backup
BACKUP_DIR="/backups/docker"
RETENTION_DAYS=30
DATE=$(date +%Y%m%d-%H%M%S)
# Backup each named volume
for vol in $(docker volume ls -q); do
echo "Backing up volume: $vol"
docker run --rm \
-v "$vol":/source:ro \
-v "$BACKUP_DIR":/backup \
alpine tar czf "/backup/$vol-$DATE.tar.gz" -C /source .
done
# Clean up old backups
find "$BACKUP_DIR" -name "*.tar.gz" -mtime +$RETENTION_DAYS -delete
echo "Backup complete: $(ls -1 $BACKUP_DIR/*$DATE* | wc -l) volumes backed up"
# Add to crontab for daily backups at 2 AM:
# 0 2 * * * /path/to/backup-volumes.sh >> /var/log/docker-backup.log 2>&1卷管理命令
Docker 提供了一套完整的 CLI 命令来管理卷的整个生命周期。
| 命令 | 说明 |
|---|---|
| docker volume create mydata | 创建命名卷 |
| docker volume ls | 列出所有卷 |
| docker volume ls -f dangling=true | 列出未被任何容器使用的卷 |
| docker volume inspect mydata | 显示卷的详细信息 |
| docker volume rm mydata | 删除指定卷(必须未被使用) |
| docker volume rm $(docker volume ls -q) | 删除所有卷(危险!) |
| docker volume prune | 删除所有未使用的卷 |
| docker volume prune -f | 强制删除未使用的卷(无确认) |
| docker system df -v | 显示卷磁盘使用情况 |
| docker inspect -f '{{.Mounts}}' container | 查看容器的挂载信息 |
性能考量
卷性能在不同操作系统上差异显著。在 Linux 上,卷和绑定挂载都能达到接近原生的性能。在 macOS 和 Windows 上,有一些重要区别。
Linux
卷和绑定挂载都使用原生文件系统。性能与直接访问宿主机相同。这是生产环境 Docker 工作负载的推荐平台。
macOS 和 Windows
Docker Desktop 在轻量级虚拟机内运行 Linux 容器。宿主机和虚拟机之间的文件共享引入了 I/O 开销,特别是对于包含大量小文件的绑定挂载(如 node_modules)。
# Problem: node_modules in bind mount is extremely slow on macOS
# BAD - entire project as bind mount (slow npm install, slow builds)
docker run -v $(pwd):/app node:20 npm install
# This syncs thousands of node_modules files between host and VM
# GOOD - use named volume for node_modules
docker run \
-v $(pwd):/app \
-v node_modules:/app/node_modules \
node:20 npm install
# node_modules stays inside the VM, only source code is synced一致性标志(macOS)
macOS 上的 Docker 支持一致性标志来调整性能与数据一致性之间的权衡。注意:这些标志在使用 VirtioFS 的新版 Docker Desktop 中已弃用。
| 标志 | 行为 | 使用场景 |
|---|---|---|
| consistent | 完全一致(默认),宿主机和容器始终看到相同内容 | 需要强一致性时 |
| cached | 宿主机写入最终会在容器中可见(有延迟) | 源代码(宿主机编辑,容器读取) |
| delegated | 容器写入最终会在宿主机上可见(有延迟) | 构建输出(容器写入,宿主机读取) |
# Legacy consistency flags (deprecated with VirtioFS)
docker run -v $(pwd)/src:/app/src:cached node:20
docker run -v $(pwd)/dist:/app/dist:delegated node:20
# Modern Docker Desktop (4.15+) uses VirtioFS by default
# which provides near-native performance without flags
# Check: Docker Desktop → Settings → General → Virtual file sharing性能建议
- 使用命名卷来存储 node_modules,避免跨文件系统同步
- 在 macOS/Windows 上,使用 VirtioFS(Docker Desktop 4.15+ 默认)获取最佳绑定挂载性能
- 使用命名卷覆盖层将大型依赖目录从绑定挂载中排除
- 对于数据库卷,始终使用命名卷而非绑定挂载
常见模式
以下是常见 Docker 存储场景中经过实战检验的模式。
数据库数据持久化
# PostgreSQL with persistent data
services:
postgres:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
POSTGRES_DB: myapp
volumes:
- pgdata:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
restart: unless-stopped
mysql:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: myapp
volumes:
- mysqldata:/var/lib/mysql
restart: unless-stopped
mongo:
image: mongo:7
volumes:
- mongodata:/data/db
- mongoconfig:/data/configdb
restart: unless-stopped
redis:
image: redis:7-alpine
command: redis-server --appendonly yes
volumes:
- redisdata:/data
restart: unless-stopped
volumes:
pgdata:
mysqldata:
mongodata:
mongoconfig:
redisdata:共享配置文件
# Share config files across multiple services
services:
web:
image: nginx:alpine
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/certs:/etc/nginx/certs:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- web-logs:/var/log/nginx
app:
build: .
volumes:
- ./config/app.yml:/app/config/app.yml:ro
- app-logs:/app/logs
# Log aggregator reads logs from both services
logrotate:
image: alpine
volumes:
- web-logs:/logs/nginx:ro
- app-logs:/logs/app:ro
volumes:
web-logs:
app-logs:开发热重载与依赖隔离
此模式绑定挂载源代码以实现实时重载,同时使用命名卷存储 node_modules 以避免性能问题和平台特定的二进制不兼容。
# Development setup with hot-reload + fast dependencies
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
volumes:
# Bind mount source code for hot-reload
- ./src:/app/src
- ./public:/app/public
- ./package.json:/app/package.json
# Named volume for node_modules (performance + isolation)
- node_modules:/app/node_modules
# Exclude build artifacts
- dist:/app/dist
ports:
- "3000:3000"
environment:
- NODE_ENV=development
volumes:
node_modules: # Stays in Docker VM, no host sync overhead
dist: # Build output stays fast
# After changing package.json, rebuild:
# docker compose run --rm app npm install
# docker compose up构建产物输出
使用命名卷在构建容器和 Web 服务容器之间共享构建产物。
# Multi-stage build with artifact sharing
services:
builder:
build:
context: .
target: builder
volumes:
- build-output:/app/dist
web:
image: nginx:alpine
volumes:
- build-output:/usr/share/nginx/html:ro
ports:
- "80:80"
depends_on:
builder:
condition: service_completed_successfully
volumes:
build-output:相关工具
使用我们的 Docker Compose 生成器可视化生成 Docker Compose 配置:
常见问题
Docker 卷和绑定挂载有什么区别?
卷由 Docker 管理,存储在 Docker 控制的宿主机文件系统区域(/var/lib/docker/volumes/)。绑定挂载将特定的宿主机路径直接映射到容器中。卷更具可移植性、更易于备份,且跨平台表现一致。绑定挂载依赖宿主机文件系统结构,主要用于开发工作流。
Docker 卷在容器删除后还存在吗?
是的。命名卷独立于容器持久化。使用 docker rm 删除容器不会删除其卷。卷只有在通过 docker volume rm、docker volume prune 或 docker compose down -v 显式删除时才会被移除。匿名卷(未命名创建的)可能会被 docker rm -v 或 docker volume prune 删除。
如何备份 Docker 卷?
使用临时容器创建卷内容的 tar 归档:docker run --rm -v myvolume:/data -v $(pwd):/backup alpine tar czf /backup/myvolume-backup.tar.gz -C /data . 这将挂载卷和当前目录,然后创建压缩归档。对于数据库,建议使用数据库原生导出工具(pg_dump、mysqldump)进行一致性备份。
为什么 Docker 卷在 macOS 上很慢?
macOS 上的 Docker Desktop 在轻量级虚拟机内运行 Linux 容器。绑定挂载需要在 macOS 宿主机和 Linux 虚拟机之间同步文件,引入了 I/O 开销。这在大量小文件(如 node_modules)时特别明显。解决方案包括:对依赖目录使用命名卷、在 Docker Desktop 设置中启用 VirtioFS、减少绑定挂载中的文件数量。
开发时应该用卷还是绑定挂载?
对源代码使用绑定挂载,这样宿主机上的更改会立即反映到容器中。对依赖目录(node_modules、vendor、venv)使用命名卷,以避免性能问题和平台特定的二进制兼容问题。这种混合方式既能让代码实时重载,又能保持依赖快速且隔离。