Docker 命名卷与绑定挂载详解:到底该用哪个?

目录

[① 导读卡片](#① 导读卡片)

[② 背景与目标](#② 背景与目标)

容器数据为什么需要持久化?

不理解这个问题的后果

学完你能做什么?

[③ 概念与原理](#③ 概念与原理)

[3.1 什么是卷(Volume)?](#3.1 什么是卷(Volume)?)

[3.2 Docker 的两种卷类型](#3.2 Docker 的两种卷类型)

[3.3 Docker Compose 是如何区分两者的?](#3.3 Docker Compose 是如何区分两者的?)

[④ 逻辑与对比](#④ 逻辑与对比)

[命名卷 vs 绑定挂载,最全对比](#命名卷 vs 绑定挂载,最全对比)

命名卷的完整关系图

[⑤ 核心详解](#⑤ 核心详解)

[5.1 命名卷(Named Volume)深入](#5.1 命名卷(Named Volume)深入)

怎么创建命名卷?

命名卷的数据藏在哪?

命名卷的生命周期管理

[5.2 绑定挂载(Bind Mount)深入](#5.2 绑定挂载(Bind Mount)深入)

绑定挂载的权限问题

[5.3 命名卷的高级配置](#5.3 命名卷的高级配置)

[5.4 多命名卷实战](#5.4 多命名卷实战)

[⑥ 案例实战](#⑥ 案例实战)

[实战一:Jenkins 数据持久化(命名卷 vs 绑定挂载的对比)](#实战一:Jenkins 数据持久化(命名卷 vs 绑定挂载的对比))

实战二:开发环境热加载(绑定挂载典型场景)

实战三:查看数据位置(排错技巧)

[⑦ 避坑 & 最佳实践](#⑦ 避坑 & 最佳实践)

常见坑点


① 导读卡片

一句话定位:Docker 数据持久化的两种核心方式------命名卷(Named Volume)和绑定挂载(Bind Mount)到底有什么区别、怎么选、怎么用,一文讲透。

  • 适合人群 :刚入门 Docker,被 volumes 写法搞晕的开发者

  • 难度:⭐⭐☆☆☆

  • 阅读时长:12 分钟

  • 前置知识 :知道 docker rundocker-compose.yml 基本语法


② 背景与目标

容器数据为什么需要持久化?

Docker 容器的文件系统是临时性的------容器被删除后,里面产生的所有数据都会丢失。这对以下场景是灾难性的:

  • 数据库数据(PostgreSQL、MySQL 的数据文件)

  • 应用配置(Jenkins、GitLab 的配置)

  • 日志文件(访问日志、错误日志)

为了解决这个问题,Docker 提供了**卷(Volume)**机制:把容器内的某个目录映射到宿主机上,容器删了,数据还在。

不理解这个问题的后果

你在看教程时一定会遇到两种写法:

复制代码
version: '3.8'
services:
  jenkins:
    volumes:
      - jenkins_home:/var/jenkins_home   # 写法 A
      - /var/lib/jenkins:/var/jenkins_home # 写法 B

两种写法看上去差不多,但背后的行为完全不同。不理解它们的区别,会导致:

  • 找不到数据存在哪里

  • 权限问题反复报错

  • 教程里说得对但你运行结果不对

学完你能做什么?

  • ✅ 一眼分辨命名卷和绑定挂载

  • ✅ 知道数据实际存在宿主机的哪个位置

  • ✅ 根据场景选择正确的持久化方式

  • ✅ 彻底理解为什么有的文件需要 sudo 才能访问

  • ✅ 在一个 docker-compose.yml 里灵活混用两种卷


③ 概念与原理

3.1 什么是卷(Volume)?

= 容器内目录与宿主机目录之间的映射桥梁。

复制代码

3.2 Docker 的两种卷类型

Docker 提供两种卷,区别只在于谁来指定宿主机上的路径

类型 宿主路径由谁指定 语法特征
命名卷(Named Volume) Docker 名字开头,不带 /
绑定挂载(Bind Mount) 用户 /./../ 开头

3.3 Docker Compose 是如何区分两者的?

这是 Docker Compose 的语法规则,写死了:

复制代码
volumes:
  - jenkins_home:/var/jenkins_home         # ✅ 命名卷(名字不带 /)
  - /var/lib/jenkins:/var/jenkins_home     # ✅ 绑定挂载(/ 开头)
  - ./data:/app/data                       # ✅ 绑定挂载(./ 开头)
  - ../shared:/shared                     # ✅ 绑定挂载(../ 开头)
  - myproject_jenkins_home:/data          # ✅ 命名卷(下划线不算路径分隔符)

判定规则极简:

左侧内容/./../ 开头 → 绑定挂载 左侧内容不是以上述开头 → 命名卷

这就是你写 jenkins_home:/var/jenkins_home 时,Docker 把它当作卷名的原因。


④ 逻辑与对比

命名卷 vs 绑定挂载,最全对比

维度 命名卷(Named Volume) 绑定挂载(Bind Mount)
语法 卷名:容器路径 /宿主机路径:容器路径
数据存放 /var/lib/docker/volumes/卷名/_data/ 你指定的路径
创建方式 Docker 自动创建 需手动确保路径存在
权限 Docker 管理,需 sudo 宿主机当前用户权限
备份 需要知道系统路径 路径明确,直接备份
共享性 多容器可共享 多容器可共享
便携性 需导出/导入卷 直接拷贝目录
适用场景 数据库、生产环境 开发调试、配置文件注入

命名卷的完整关系图

复制代码
Docker 引擎
  │
  ├── 卷管理
  │     ├── vol_jenkins_home  → 数据在 /var/lib/docker/volumes/vol_jenkins_home/_data/
  │     ├── vol_postgres_data → 数据在 /var/lib/docker/volumes/vol_postgres_data/_data/
  │     └── vol_redis_data    → 数据在 /var/lib/docker/volumes/vol_redis_data/_data/
  │
  ├── 容器 A(挂载 vol_jenkins_home、vol_redis_data)
  ├── 容器 B(挂载 vol_jenkins_home)
  └── 容器 C(挂载 vol_postgres_data)

核心特点:

  • 一个命名卷可被多个容器同时挂载

  • 一个容器可挂载多个命名卷

  • 卷的生命周期独立于容器------容器删了,卷还在

  • Docker 可以管理任意数量的命名卷,没有上限


⑤ 核心详解

5.1 命名卷(Named Volume)深入

怎么创建命名卷?

方式一:docker volume create 独立创建

复制代码
docker volume create my_custom_volume
# 查看卷列表
docker volume ls
# 查看卷详情(可以看到真实路径)
docker volume inspect my_custom_volume

输出示例:

复制代码
[
    {
        "CreatedAt": "2024-01-15T10:00:00Z",
        "Driver": "local",
        "Mountpoint": "/var/lib/docker/volumes/my_custom_volume/_data",
        "Name": "my_custom_volume",
        "Options": null,
        "Scope": "local"
    }
]

方式二:docker run -v 时自动创建

复制代码
docker run -v my_data:/app/data alpine
# Docker 检查到 my_data 卷不存在,自动创建

方式三:docker-compose.yml 中自动创建

复制代码
services:
  jenkins:
    volumes:
      - jenkins_home:/var/jenkins_home
​
volumes:               # 显式声明(推荐)
  jenkins_home:
命名卷的数据藏在哪?
复制代码
# 默认路径
/var/lib/docker/volumes/<项目名>_<卷名>/_data/

# 例如:项目文件夹叫 jenkins,卷名 jenkins_home
# 实际位置:/var/lib/docker/volumes/jenkins_jenkins_home/_data/

⚠️ 注意: 这个路径默认只有 root 可读。如果你直接用普通用户去访问会报 Permission denied

解决办法:

复制代码
# 方式一:用 sudo
sudo ls /var/lib/docker/volumes/jenkins_jenkins_home/_data/

# 方式二:改用绑定挂载(推荐调试用)
# 把数据丢到自己用户目录下
命名卷的生命周期管理
复制代码
# 列出所有卷
docker volume ls

# 删除卷(卷必须不被任何容器使用)
docker volume rm my_custom_volume

# 删除所有未被使用的卷
docker volume prune

# 备份卷(方法:临时容器打包)
docker run --rm -v my_data:/source -v $(pwd):/backup alpine tar czf /backup/my_data_backup.tar.gz -C /source .

5.2 绑定挂载(Bind Mount)深入

绑定挂载的关键好处是路径由你掌控

复制代码
services:
  nginx:
    volumes:
      # 挂载单个配置文件
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro

      # 挂载整个目录(开发时热加载)
      - ./html:/usr/share/nginx/html

      # 挂载日志目录
      - ./logs:/var/log/nginx

ro 标志:挂载为只读,防止容器内修改配置文件。

绑定挂载的权限问题
复制代码
# ❌ 可能出错:宿主目录不存在
volumes:
  - /my/custom/path:/app     # 如果 /my/custom/path 不存在,Docker 会创建它(但可能权限不对)

# ✅ 安全做法:先创建目录
mkdir -p /home/user/jenkins_data
chown -R 1000:1000 /home/user/jenkins_data   # Jenkins 容器以 UID 1000 运行

5.3 命名卷的高级配置

docker-compose.yml 的顶级 volumes 中,你可以做更多配置:

复制代码
volumes:
  # 基本声明
  basic_volume:

  # 使用外部已存在的卷(不由 Compose 管理生命周期)
  external_volume:
    external: true

  # 指定卷驱动(如 NFS、云存储驱动)
  nfs_volume:
    driver: local
    driver_opts:
      type: nfs
      o: addr=192.168.1.100,rw
      device: ":/path/to/nfs/share"

  # 给卷加标签
  labeled_volume:
    labels:
      environment: production
      backup: daily

外部卷(external: true)的应用场景:

复制代码
# 场景:多个 Compose 项目共享同一个数据库卷
# 先用 docker volume create 创建
# docker volume create shared_postgres_data

services:
  postgres:
    image: postgres:14
    volumes:
      - shared_postgres_data:/var/lib/postgresql/data

volumes:
  shared_postgres_data:
    external: true    # 不创建新卷,使用已存在的

5.4 多命名卷实战

一个 docker-compose.yml 里可以写任意多个命名卷:

复制代码
version: '3.8'

services:
  jenkins:
    image: jenkins/jenkins:lts
    volumes:
      - jenkins_home:/var/jenkins_home
      - jenkins_logs:/var/log/jenkins

  postgres:
    image: postgres:14
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - postgres_backup:/backup

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data

volumes:               # 所有命名卷在此声明
  jenkins_home:
  jenkins_logs:
  postgres_data:
  postgres_backup:
  redis_data:

即使不在顶级 volumes 中声明,Compose 也会隐式创建卷。但显式声明是好习惯,尤其是:

  • 你需要在多个服务间共享卷

  • 你需要设置外部卷或驱动选项

  • 你想让 docker compose down -v 可预测地删除卷


⑥ 案例实战

实战一:Jenkins 数据持久化(命名卷 vs 绑定挂载的对比)

命名卷写法:

复制代码
version: '3.8'
services:
  jenkins:
    image: jenkins/jenkins:lts
    privileged: true
    user: root
    ports:
      - "8080:8080"
    volumes:
      - jenkins_home:/var/jenkins_home
      - /var/run/docker.sock:/var/run/docker.sock

volumes:
  jenkins_home:

数据位置:/var/lib/docker/volumes/<项目名>_jenkins_home/_data/

绑定挂载写法:

复制代码
version: '3.8'
services:
  jenkins:
    image: jenkins/jenkins:lts
    privileged: true
    user: root
    ports:
      - "8080:8080"
    volumes:
      - /opt/jenkins_data:/var/jenkins_home
      - /var/run/docker.sock:/var/run/docker.sock

数据位置:/opt/jenkins_data/(你可以随时访问)

权限修复(绑定挂载常见问题):

复制代码
# Jenkins 容器内进程以 UID 1000 运行
# 如果宿主机目录权限不对,会报 Permission denied
sudo chown -R 1000:1000 /opt/jenkins_data

实战二:开发环境热加载(绑定挂载典型场景)

复制代码
version: '3.8'

services:
  frontend:
    image: node:18
    working_dir: /app
    command: npm run dev
    volumes:
      - ./frontend:/app           # 绑定挂载:代码修改立即同步到容器
      - /app/node_modules         # 匿名卷:保护容器内 npm 安装的模块

  backend:
    build: ./backend
    volumes:
      - ./backend:/app            # 绑定挂载:热加载
      - /app/node_modules         # 匿名卷保护
    environment:
      - NODE_ENV=development

关键技巧说明:

复制代码
volumes:
  - ./backend:/app          # 宿主机项目的 ./backend 覆盖容器 /app
  - /app/node_modules       # 注意:这个没有宿主机路径

第二行 - /app/node_modules 的作用是:

  1. 宿主机目录 ./backend 先挂载到容器 /app

  2. Docker 创建了一个匿名卷,挂载到 /app/node_modules

  3. 由于更具体的挂载点优先/app/node_modules 使用的是容器内原有的 node_modules(包含 npm 安装的包)

  4. 宿主机上可能没有 node_modules,避免了空目录覆盖导致模块丢失

实战三:查看数据位置(排错技巧)

复制代码
# 1. 找到命名卷的实际路径
docker volume inspect myproject_db_data
# → Mountpoint: /var/lib/docker/volumes/myproject_db_data/_data/

# 2. 查看容器使用的挂载
docker inspect my_container | grep -A 10 Mounts

# 3. 绑定挂载的数据路径
# 直接去你指定的路径看就好了