强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Docker Compose 完全指南 / 第 5 章 · 数据卷:named volumes、bind mounts 与 tmpfs

第 5 章 · 数据卷管理

5.1 为什么需要数据卷?

Docker 容器是无状态的——容器删除后,其内部的所有数据都会丢失。数据卷解决了这个核心问题。

容器文件系统分层

┌────────────────────────────┐
│  可写层 (Container Layer)   │  ← 容器删除即丢失
├────────────────────────────┤
│  只读层 (Image Layers)      │  ← 镜像内容,共享
├────────────────────────────┤
│  宿主机文件系统               │
└────────────────────────────┘

       使用数据卷后:

┌────────────────────────────┐
│  容器                       │
│  /data → 宿主机卷           │  ← 数据持久化
├────────────────────────────┤
│  数据卷 (Volume)             │  ← 独立于容器生命周期
└────────────────────────────┘

三种挂载方式对比

特性Named VolumeBind Mounttmpfs
存储位置Docker 管理的目录宿主机指定路径内存
持久化✅ 是✅ 是❌ 否(容器停止即丢)
跨主机共享❌(需驱动支持)
可移植性✅ 高❌ 低(依赖宿主机路径)✅ 高
性能优秀取决于存储类型最快
典型用途数据库数据、应用状态开发代码同步、配置文件临时缓存、密钥
容器删除后数据保留数据保留(宿主机文件)数据丢失

5.2 Named Volumes(命名卷)

命名卷是 Docker 管理的持久存储,推荐用于生产数据

基本用法

services:
  db:
    image: postgres:16-alpine
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:    # 必须在顶级 volumes 中声明

命名卷的高级配置

volumes:
  pgdata:
    name: myapp_production_pgdata   # 自定义卷名(避免默认的 <project>_<name> 格式)
    driver: local                    # 默认驱动
    driver_opts:
      type: nfs                      # NFS 类型
      o: addr=192.168.1.100,rw,nfsvers=4
      device: ":/exports/pgdata"
    external: false                  # 是否为外部卷
    labels:
      com.example.backup: "daily"
      com.example.retention: "30d"

卷的生命周期管理

# 查看所有卷
docker volume ls

# 查看卷详情
docker volume inspect myapp_production_pgdata

# 创建卷(手动,非 Compose 管理)
docker volume create my-shared-data

# 删除卷(⚠️ 数据会丢失)
docker volume rm my-shared-data

# 清理无主卷(⚠️ 谨慎使用)
docker volume prune

# 清理无主卷 + 确认
docker volume prune -f

⚠️ 注意docker compose down 只会删除容器和网络,不会删除命名卷。要同时删除卷需要 docker compose down -v

卷命名最佳实践

# ❌ 不好的做法:卷名依赖项目目录名
volumes:
  data:   # 实际卷名:myproject_data(目录名变化则卷名变化)

# ✅ 好的做法:显式指定卷名
volumes:
  data:
    name: myapp_pgdata   # 不受项目目录名影响

5.3 Bind Mounts(绑定挂载)

绑定挂载将宿主机的文件或目录映射到容器中,推荐用于开发环境

基本用法

services:
  web:
    image: nginx:alpine
    volumes:
      # 短语法:宿主机路径:容器路径
      - ./html:/usr/share/nginx/html

      # 只读挂载
      - ./nginx.conf:/etc/nginx/nginx.conf:ro

      # 读写挂载(默认)
      - ./logs:/var/log/nginx

长语法(更清晰)

services:
  web:
    volumes:
      - type: bind
        source: ./html              # 宿主机路径
        target: /usr/share/nginx/html
        read_only: false
        bind:
          create_host_path: true    # 宿主机路径不存在时自动创建
          propagation: rprivate     # 挂载传播模式

传播模式(Propagation)

模式说明
private默认,挂载事件不传播
rprivate递归 private
shared挂载事件双向传播
rshared递归 shared
slave从宿主机单向接收传播
rslave递归 slave

💡 大多数场景不需要设置 propagation,保持默认即可。仅在嵌套挂载或特殊存储驱动场景下才需配置。

开发热重载示例

# Node.js 开发环境
services:
  app:
    image: node:20-alpine
    working_dir: /app
    command: npx nodemon src/index.js
    volumes:
      # 绑定挂载源码
      - ./src:/app/src
      # 使用命名卷缓存 node_modules(性能更好)
      - node_modules:/app/node_modules
    ports:
      - "3000:3000"
    environment:
      NODE_ENV: development

volumes:
  node_modules:    # 命名卷,不受 bind mount 影响

💡 技巧:将 node_modules 放在命名卷而非绑定挂载中,可以避免 macOS/Windows 上的 I/O 性能问题。


5.4 tmpfs Mounts(内存挂载)

tmpfs 将数据存储在内存中,适合临时数据和敏感信息。

基本用法

services:
  app:
    image: myapp:latest
    tmpfs:
      - /tmp                    # 简单语法
      - /run:size=10M           # 带大小限制
    volumes:
      - type: tmpfs
        target: /app/cache
        tmpfs:
          size: 104857600       # 100MB (bytes)
          mode: 1777            # 权限

适用场景

场景说明
临时文件/tmp/var/tmp
会话缓存应用运行时缓存
敏感数据密钥文件,不落盘更安全
高性能缓存内存 I/O 远快于磁盘

实际示例:NGINX 临时目录

services:
  nginx:
    image: nginx:alpine
    tmpfs:
      - /var/cache/nginx:size=100M
      - /var/run:size=10M
      - /tmp:size=50M
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    ports:
      - "80:80"

5.5 从容器中复制数据到卷

场景:初始化卷数据

有时需要将文件从容器复制到卷中(如首次初始化配置)。

services:
  db:
    image: postgres:16-alpine
    volumes:
      - pgdata:/var/lib/postgresql/data
      # 挂载初始化脚本(仅在数据目录为空时执行)
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro

手动复制数据到卷

# 创建一个临时容器来复制文件
docker compose run --rm -v myvolume:/target alpine sh -c \
  "cp -r /source/* /target/"

# 或使用 docker cp
docker compose up -d db
docker cp ./backup.sql myproject-db-1:/tmp/
docker compose exec db psql -U postgres -f /tmp/backup.sql

5.6 卷备份与恢复

备份命名卷

# 方法一:使用临时容器导出
docker compose run --rm -v pgdata:/source -v $(pwd):/backup alpine \
  tar czf /backup/pgdata-backup-$(date +%Y%m%d).tar.gz -C /source .

# 方法二:使用 docker exec(容器运行时)
docker compose exec db pg_dump -U postgres mydb > backup.sql

# 方法三:直接备份卷目录(需 root)
sudo tar czf pgdata-backup.tar.gz /var/lib/docker/volumes/myapp_pgdata/_data/

恢复命名卷

# 创建新卷
docker volume create pgdata-restored

# 解压到新卷
docker run --rm \
  -v pgdata-restored:/target \
  -v $(pwd):/backup \
  alpine sh -c "cd /target && tar xzf /backup/pgdata-backup-20260510.tar.gz"

# 使用恢复的卷启动
# 在 compose.yaml 中修改卷名或使用 external volume

自动化备份脚本

#!/bin/bash
# backup-compose-volumes.sh
# 用法: ./backup-compose-volumes.sh <project_name>

PROJECT=${1:-$(basename $(pwd))}
BACKUP_DIR="./backups/$(date +%Y%m%d_%H%M%S)"
mkdir -p "$BACKUP_DIR"

echo "Backing up volumes for project: $PROJECT"

# 获取项目的所有卷
VOLUMES=$(docker volume ls --filter "label=com.docker.compose.project=$PROJECT" -q)

for VOL in $VOLUMES; do
    echo "  → Backing up $VOL..."
    docker run --rm \
        -v "$VOL":/source:ro \
        -v "$(pwd)/$BACKUP_DIR":/backup \
        alpine tar czf "/backup/${VOL}.tar.gz" -C /source .
done

echo "Backup complete: $BACKUP_DIR"
ls -lh "$BACKUP_DIR"

5.7 NFS 与远程存储

使用 NFS 卷

services:
  app:
    image: myapp:latest
    volumes:
      - nfsvol:/data

volumes:
  nfsvol:
    driver: local
    driver_opts:
      type: nfs
      o: addr=192.168.1.100,rw,nfsvers=4,hard,timeo=600
      device: ":/exports/appdata"

使用 CIFS/SMB(Windows 共享)

volumes:
  smbdata:
    driver: local
    driver_opts:
      type: cifs
      o: addr=192.168.1.200,username=user,password=pass,file_mode=0777,dir_mode=0777
      device: "//192.168.1.200/share"

使用 tmpfs 驱动(伪 tmpfs)

volumes:
  ramdisk:
    driver: local
    driver_opts:
      type: tmpfs
      device: tmpfs
      o: size=100m,uid=1000

5.8 数据卷的实际业务场景

场景一:数据库持久化

services:
  postgres:
    image: postgres:16-alpine
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./backups:/backups          # 备份目录
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

  mysql:
    image: mysql:8.0
    volumes:
      - mysqldata:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD_FILE: /run/secrets/mysql_root_password

  redis:
    image: redis:7-alpine
    volumes:
      - redisdata:/data
    command: redis-server --appendonly yes   # 开启 AOF 持久化

volumes:
  pgdata:
  mysqldata:
  redisdata:

场景二:文件上传共享

services:
  api:
    image: myapi:latest
    volumes:
      - uploads:/data/uploads     # API 写入上传文件

  worker:
    image: myworker:latest
    volumes:
      - uploads:/data/uploads     # Worker 处理上传文件

  nginx:
    image: nginx:alpine
    volumes:
      - uploads:/usr/share/nginx/uploads:ro   # Nginx 直接服务静态文件

volumes:
  uploads:

场景三:开发环境代码同步

services:
  frontend:
    image: node:20-alpine
    working_dir: /app
    command: npm run dev
    volumes:
      - ./frontend:/app                   # 源码绑定挂载
      - frontend_modules:/app/node_modules # 依赖用命名卷

  backend:
    image: python:3.12-slim
    working_dir: /app
    command: python -m uvicorn main:app --reload
    volumes:
      - ./backend:/app                    # 源码绑定挂载
      - pip_cache:/root/.cache/pip        # pip 缓存

volumes:
  frontend_modules:
  pip_cache:

5.9 卷的性能考量

场景推荐方式原因
数据库数据Named VolumeDocker 优化的存储路径,性能最佳
开发代码同步Bind Mount实时同步,开发便利
macOS/Windows 开发Named Volume + Bind Mount 混合node_modules 用命名卷避免 I/O 瓶颈
临时缓存tmpfs内存最快
CI/CD 构建缓存Named Volume持久化缓存层,加速构建

macOS/Windows I/O 优化

# macOS/Windows 上的优化写法
services:
  node-app:
    image: node:20-alpine
    volumes:
      - .:/app                          # 代码同步
      - node_modules:/app/node_modules  # 命名卷避免跨文件系统 I/O
      - /app/dist                       # 匿名卷排除构建产物

volumes:
  node_modules:

5.10 常见问题与排错

问题原因解决方案
权限拒绝 (Permission denied)UID/GID 不匹配使用 user 指令或修改挂载目录权限
数据丢失使用了匿名卷或 down -v声明命名卷,避免 -v 参数
挂载路径不存在Bind Mount 源路径不存在Compose 会自动创建目录(bind mount)
node_modules 冲突Bind Mount 覆盖了容器内目录使用命名卷隔离 node_modules
卷空间不足Docker 默认存储空间限制配置 daemon.json 的 storage-opts

解决权限问题

services:
  app:
    image: myapp:latest
    user: "1000:1000"    # 指定容器内运行用户
    volumes:
      - ./data:/app/data
    # 或在 Dockerfile 中处理权限
# 修改挂载目录权限
chmod -R 777 ./data       # 临时方案(不安全)
sudo chown -R 1000:1000 ./data   # 推荐方案

5.11 小结

概念说明
Named VolumeDocker 管理,持久化,推荐用于生产数据
Bind Mount宿主机路径映射,适合开发环境
tmpfs内存存储,适合临时/敏感数据
卷备份使用临时容器导出 + tar 归档
性能优化macOS/Windows 上混合使用命名卷和绑定挂载
安全注意down -v 会删除所有命名卷,谨慎使用

扩展阅读


上一章:第 4 章 · 网络 ← | 下一章:第 6 章 · 环境变量 →