目录
[① 导读卡片](#① 导读卡片)
[② 背景与目标](#② 背景与目标)
[③ 概念与原理](#③ 概念与原理)
[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 run和docker-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 的作用是:
-
宿主机目录
./backend先挂载到容器/app -
Docker 创建了一个匿名卷,挂载到
/app/node_modules -
由于更具体的挂载点优先 ,
/app/node_modules使用的是容器内原有的node_modules(包含 npm 安装的包) -
宿主机上可能没有
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. 绑定挂载的数据路径
# 直接去你指定的路径看就好了