使用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磁盘等远程后端。

卷与绑定挂载与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卷需要围绕命名、备份和监控的纪律,以避免不愉快的意外。

# 自动化每晚备份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非常适合会话缓存和可再生临时文件,而卷是数据库、用户上传和必须在重启后保留的配置文件的正确选择。

亚洲GB高性能KVM VPS

具有完整root访问权限的VPS,准备好用于Docker、MySQL、Python、Node.js等。从500泰铢/月起,99%正常运行时间。

查看VPS计划