使用Docker时最常见的问题之一是,每次删除或重新创建容器时都会丢失所有数据。这是因为Docker容器在设计上是短暂的——它们的可写层在容器创建时生成,随着容器销毁而销毁。Docker卷通过提供存在于容器生命周期之外的存储机制来解决这个问题。无论您是运行MySQL数据库、存储用户上传还是持久化应用配置,卷都确保您的数据在容器重启、更新和替换时得以保留。本指南涵盖了关于Docker卷的一切,从基本概念到生产级备份策略,全部在Ubuntu VPS环境中进行测试。
理解Docker的分层文件系统
在深入研究卷之前,理解Docker如何在内部管理存储会有所帮助。每个Docker镜像都由使用联合文件系统(UnionFS)的一堆只读层组成。从镜像启动容器时,Docker在所有镜像层之上添加一个薄的可写层。您在运行容器内创建、修改或删除的任何文件都会进入这个可写层——底层镜像层永远不会改变。
关键问题是这个可写层与特定的容器实例紧密耦合。当您运行docker rm或docker-compose down时,可写层与容器一起被永久删除。这意味着容器内写入的任何数据——例如数据库文件、上传的资产或生成的日志——都将永远丢失。Docker提供了三种在容器生命周期之外保留数据的机制:卷、绑定挂载和tmpfs挂载。
Docker卷解释
Docker卷是由Docker Engine管理的专用存储目录,完全独立于容器生命周期。卷数据存储在主机的/var/lib/docker/volumes/<volume-name>/_data,但Docker拥有并管理此路径——不是用户直接管理。这种区分在其他存储方法上提供了几个重要优势。
因为卷由Docker管理,它们是生产工作负载的推荐存储机制。您可以自由删除和重新创建容器,无需触及卷数据。多个容器可以同时挂载同一个卷。卷可以使用标准Docker工具进行备份、恢复和迁移,它们支持卷驱动程序,可以将存储扩展到NFS、AWS EBS或Azure磁盘等远程后端。
- 生命周期独立性 — 删除容器不影响卷或其数据
- Docker管理的权限 — 无需在主机上手动配置所有权
- 可跨容器共享 — 多个容器可以同时挂载同一个卷
- 简便的备份和迁移 — 标准Docker命令处理整个工作流
- 卷驱动程序支持 — 透明地扩展到云存储提供商
- 更好的性能 — 直接写入主机文件系统,绕过UnionFS开销
卷与绑定挂载与tmpfs的比较表
选择正确的存储类型取决于您的用例。以下是快速比较以指导您的决定:
| 类型 | 存储位置 | 持久性 | 最佳用途 |
|---|---|---|---|
| 命名卷 | /var/lib/docker/volumes/ | 永久(手动删除) | 数据库、用户上传、生产数据 |
| 绑定挂载 | 主机上的任何路径 | 永久(依赖主机) | 开发、实时代码重新加载、配置文件 |
| tmpfs挂载 | 仅限RAM(无磁盘写入) | 临时(停止时丢失) | 会话缓存、临时数据、内存中的机密 |
| 匿名卷 | /var/lib/docker/volumes/(随机ID) | 永久但难以跟踪 | 不推荐用于生产 |
命名卷是生产使用的默认推荐。绑定挂载在开发环境中效果很好,您可以将本地源代码挂载到容器中,并且无需重新构建镜像即可立即反映更改。
创建和管理卷——基本命令
以下是每个使用卷的开发人员应该知道的核心Docker CLI命令:
# Create a new Named Volume docker volume create myapp_data # List all Volumes on this host docker volume ls # Inspect a Volume (shows mount path, driver, labels) docker volume inspect myapp_data # Remove a specific Volume (container must not be using it) docker volume rm myapp_data # Remove all unused Volumes (prune) docker volume prune # Remove Volumes together with containers on compose down docker-compose down -v
docker volume inspect的示例输出:
[
{
"CreatedAt": "2026-06-09T10:00:00+07:00",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/myapp_data/_data",
"Name": "myapp_data",
"Options": {},
"Scope": "local"
}
]
使用docker run和Docker Compose挂载卷
将卷附加到容器的两种主要方式是:使用docker run中的-v或--mount标志,或在docker-compose.yml文件中声明它们。
选项1 — docker run 使用 -v 标志
# 语法: -v <volume-name>:<path-inside-container> docker run -d \ --name mysql_db \ -e MYSQL_ROOT_PASSWORD=secret \ -e MYSQL_DATABASE=myapp \ -v mysql_data:/var/lib/mysql \ mysql:8.0 # 绑定挂载 — 将特定的主机目录映射到容器中 docker run -d \ --name nginx_web \ -v /home/user/website:/usr/share/nginx/html:ro \ -p 80:80 \ nginx:alpine
选项2 — docker-compose.yml(推荐用于生产)
version: '3.8'
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
POSTGRES_DB: myapp
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped
app:
image: myapp:latest
depends_on:
- db
volumes:
- app_uploads:/app/uploads
- app_logs:/app/logs
ports:
- "8000:8000"
# 在顶级声明命名卷
volumes:
postgres_data:
app_uploads:
app_logs:
在docker-compose.yml顶级声明卷会指示Docker在卷不存在时自动创建它们。运行docker-compose down时不加-v标志将停止并删除容器,同时保持所有卷完整。这是生产部署的预期行为——容器是短暂的,数据不是。
备份和恢复Docker卷
没有经过测试的备份策略,任何存储解决方案都是不完整的。标准的Docker方法是启动一个临时容器来挂载卷并将压缩归档写入主机。
创建卷备份
# 备份postgres_data卷到当前目录 docker run --rm \ -v postgres_data:/source:ro \ -v $(pwd):/backup \ alpine \ tar czf /backup/postgres_data_$(date +%Y%m%d).tar.gz -C /source . # 验证备份文件 ls -lh postgres_data_*.tar.gz
从备份恢复卷
# 为恢复目标创建新卷 docker volume create postgres_data_restore # 将归档提取到新卷中 docker run --rm \ -v postgres_data_restore:/target \ -v $(pwd):/backup \ alpine \ sh -c "cd /target && tar xzf /backup/postgres_data_20260609.tar.gz"
特定于数据库的备份(推荐用于PostgreSQL和MySQL)
# PostgreSQL — pg_dump生成一致的逻辑备份 docker exec postgres_container pg_dump -U myuser mydb \ > backup_$(date +%Y%m%d).sql # MySQL — mysqldump docker exec mysql_container mysqldump \ -u root -psecret myapp \ > mysql_backup_$(date +%Y%m%d).sql # 恢复PostgreSQL cat backup_20260609.sql | docker exec -i postgres_container \ psql -U myuser mydb
生产提示:永远不要通过对运行中的数据库的原始数据文件进行tar来备份。结果可能是无法干净恢复的不一致快照。始终使用数据库自己的转储工具——PostgreSQL使用pg_dump、MySQL使用mysqldump、MongoDB使用mongodump。这些工具旨在在数据库仍在接受连接时生成一致的备份。设置每晚的cron作业,并至少每月测试一次恢复过程。
在多个容器之间共享卷
Docker卷最强大的功能之一是能够同时将同一个卷挂载到多个容器中。这在微服务体系结构中很有用,其中服务需要读取或写入共享文件存储。
version: '3.8'
services:
# 应用程序服务器写入上传的文件
app:
image: myapp:latest
volumes:
- shared_uploads:/app/uploads
# Nginx读取上传文件作为静态文件服务
nginx:
image: nginx:alpine
volumes:
- shared_uploads:/usr/share/nginx/html/uploads:ro
ports:
- "80:80"
# 后台工作进程处理上传的文件
worker:
image: myworker:latest
volumes:
- shared_uploads:/worker/input:ro
volumes:
shared_uploads:
在这个例子中,所有三个容器共享shared_uploads卷。Nginx和worker都以只读(:ro)方式挂载它,以防止意外写入。对任何只需要读取权限的容器应用:ro是一个安全最佳实践,可以减少潜在泄露或配置错误的影响范围。
处理Linux上的卷权限
在Linux上使用卷时,权限问题是最常见的问题之一。容器内运行的进程的UID/GID可能不匹配主机上卷目录的所有权,导致"权限被拒绝"错误。
# 检查主机上卷的所有权
ls -la /var/lib/docker/volumes/myapp_data/_data
# 在Dockerfile中修复权限
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
RUN chown -R appuser:appgroup /app
USER appuser
# 或在运行时指定用户
docker run -d \
--user 1000:1000 \
-v myapp_data:/app/data \
myapp:latest
# 在docker-compose.yml中
services:
app:
image: myapp:latest
user: "1000:1000"
volumes:
- myapp_data:/app/data
MySQL、PostgreSQL和MongoDB等数据库的官方Docker镜像在其入口点脚本中自动处理权限设置。如果您要构建自己的镜像,始终在Dockerfile中定义非root用户,并确保卷挂载点在USER指令之前由该用户拥有。
Docker卷的生产最佳实践
在生产VPS上运行Docker卷需要围绕命名、备份和监控的纪律,以避免不愉快的意外。
- 使用有意义的名称 — 遵循
projectname_service_data约定,例如myapp_postgres_data。随着堆栈增长,随机或通用名称变得难以管理。 - 始终在docker-compose.yml中声明卷 — 避免在生产中使用匿名卷。Docker prune命令将删除附加到停止容器的未命名卷。
- 使用cron自动化备份 — 安排每晚备份,并保持至少30天的滚动归档。将它们复制到远程位置。
- 监控磁盘使用 — 容纳数据库文件或日志的卷可能会不断增长。设置磁盘使用警报以避免容器因文件系统满而崩溃。
- 尽可能使用只读挂载 — 任何只从卷读取的容器都应该以
:ro方式挂载它,以防止意外写入。 - 按数据类型分离卷 — 将数据库数据、上传文件和日志保存在单独的卷中,以便可以为每个应用不同的备份保留策略。
- 测试恢复过程 — 您从未测试过的备份不是真正的备份。安排季度恢复演练到暂存环境。
# 自动化每晚备份cron作业(通过crontab -e添加) # 每晚02:00运行,保持30天的备份 0 2 * * * docker run --rm \ -v myapp_postgres_data:/source:ro \ -v /backup/docker:/backup \ alpine \ tar czf /backup/postgres_$(date +\%Y\%m\%d).tar.gz \ -C /source . && \ find /backup/docker -name "postgres_*.tar.gz" -mtime +30 -delete
常见问题
Docker卷与绑定挂载有什么区别?
Docker卷完全由Docker Engine管理,存储在/var/lib/docker/volumes/下,使其便于移植和迁移。绑定挂载将容器路径直接绑定到主机文件系统上的特定路径,非常适合开发工作流,可以实现实时代码重新加载。对于生产工作负载,建议使用命名卷,因为Docker处理其生命周期、权限,并且与主机路径无关。
删除容器时Docker卷中的数据会丢失吗?
不会。卷具有完全独立于容器的生命周期。运行docker rm -f或docker-compose down将删除容器,但保留命名卷。数据仅在您显式运行docker volume rm或docker volume prune时才被删除。这使得重新创建或更新容器是安全的,不会有数据丢失的风险,但仍然强烈建议定期备份。
如何从Docker卷备份数据?
标准方法是启动一个临时Alpine容器来挂载卷并创建tar归档:docker run --rm -v myvolume:/data -v $(pwd):/backup alpine tar czf /backup/backup.tar.gz -C /data . 这会在当前目录中生成backup.tar.gz。对于PostgreSQL或MySQL,使用pg_dump或mysqldump而不是原始文件备份以确保数据一致性。
我应该使用Volume还是tmpfs来存储容器中的临时数据?
如果数据不需要在容器重启后保留,并且您想要最大速度,可以使用tmpfs挂载,因为它在RAM中存储数据而无需任何磁盘I/O。如果重启时丢失数据是不可接受的,使用命名卷。tmpfs非常适合会话缓存和可再生临时文件,而卷是数据库、用户上传和必须在重启后保留的配置文件的正确选择。