Docker 容器在运行时会产生大量数据,这些数据如何持久化和管理是一个重要的话题。 本节我们将通过一个 Nginx Web 服务器的案例,来深入探讨 Docker 的三种数据管理方式。
Docker 提供了三种主要的数据管理方式:
让我们通过一个 Nginx Web 服务器的例子来理解这三种方式的区别。我们将在每种方式下执行相同的操作:创建一个 HTML 文件,然后测试数据的持久性。
在这个场景中,我们直接在容器内创建文件,看看数据会发生什么:
# 运行一个 nginx 容器
docker run -d --name web-default -p 8000:80 nginx
# 在容器中创建一个测试页面
docker exec -it web-default /bin/sh
echo "<h1>Hello from Default Storage</h1>" > /usr/share/nginx/html/index.html
exit
# 访问页面验证内容
curl http://localhost:8000
# 获取容器在宿主机上的实际存储路径(MergedDir)
docker inspect -f '{{.GraphDriver.Data.MergedDir}}' <container_id>
# 删除容器
docker stop web-default
docker rm web-default
# 用同样的配置重新运行容器
docker run -d --name web-default-2 -p 8000:80 nginx
# 再次访问页面,内容不存在
curl http://localhost:8000
关于 MergedDir:容器目录本质上是宿主机目录的一个子目录,通过 Linux 的 chroot 技术实现隔离。当容器使用联合文件系统(如 overlay2)时,
MergedDir是所有镜像层最终合并后在宿主机上的实际路径。容器目录对宿主机隔离,一般禁止直接访问。
Docker 使用 OverlayFS(联合文件系统) 来管理容器的文件系统,它由多个层组成:
┌─────────────────────────────────┐ │ 容器可写层(Container Layer) │ ← 你在容器中创建/修改的文件都在这里 │ 生命周期 = 容器生命周期 │ ← 容器删除时,这一层也被删除! ├─────────────────────────────────┤ │ 镜像只读层 3(Nginx 配置) │ ├─────────────────────────────────┤ │ 镜像只读层 2(Nginx 程序) │ ← 这些层是只读的,多个容器共享 ├─────────────────────────────────┤ │ 镜像只读层 1(基础系统) │ └─────────────────────────────────┘
工作原理:
docker run 时,Docker 在镜像层之上创建一个可写层docker rm 删除容器时,可写层随之删除,所有修改都丢失这就是为什么:
web-default 中创建的 index.html 存储在容器的可写层web-default 后,可写层被清除web-default-2 是一个全新的容器,有自己的空白可写层,所以看不到之前的文件Volume 和 Bind Mount 的本质:它们都是绕过可写层,将数据直接存储在容器外部(宿主机),因此不受容器生命周期影响。
Bind Mount 将宿主机上的指定目录直接映射到容器内部。数据实际存储在宿主机的文件系统上,容器删除后宿主机上的文件依然存在。
# 创建本地目录
mkdir nginx-content
# 运行 Nginx 容器并挂载本地目录
docker run -d --name web-bind \
-p 8081:80 \
-v $(pwd)/nginx-content:/usr/share/nginx/html nginx
# 在容器中创建一个测试页面
docker exec -it web-bind sh -c 'echo "<h1>Hello from Bind Mounts</h1>" > /usr/share/nginx/html/index.html'
# 访问页面验证内容
curl http://localhost:8081
# 删除容器
docker rm -f web-bind
# 用同样的配置重新运行容器
docker run -d --name web-bind-2 -p 8081:80 \
-v $(pwd)/nginx-content:/usr/share/nginx/html nginx
# 再次访问页面,内容仍然存在
curl http://localhost:8081
Volume 是由 Docker 引擎管理的存储区域,数据存储在 Docker 的管理目录中(默认 /var/lib/docker/volumes/),完全独立于容器生命周期。相比 Bind Mount,Volume 不依赖宿主机的目录结构,更适合生产环境。
# 创建一个 Docker volume
docker volume create nginx_data
# 运行 Nginx 容器并挂载卷
docker run -d --name web-volume -p 8082:80 -v nginx_data:/usr/share/nginx/html nginx
# 在容器中创建一个测试页面
docker exec -it web-volume sh -c 'echo "<h1>Hello from Volume Storage</h1>" > /usr/share/nginx/html/index.html'
# 访问页面验证内容
curl http://localhost:8082
# 删除容器
docker rm -f web-volume
# 用同样的配置重新运行容器
docker run -d --name web-volume-2 -p 8082:80 \
-v nginx_data:/usr/share/nginx/html nginx
# 再次访问页面,内容仍然存在
curl http://localhost:8082
# 查看卷的详细信息
docker volume ls
docker volume inspect nginx_data
默认存储
Bind Mount
Volume
完成实验后,可以进行清理:
# 清理容器
docker rm -f web-default web-volume web-volume-2 web-bind web-bind-2
# 清理卷
docker volume rm nginx_data
# 清理本地目录
rm -rf nginx-content
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 数据库持久化 | Volume | 性能好、Docker 管理、便于备份 |
| 开发环境代码同步 | Bind Mount | 实时修改、IDE 友好 |
| 配置文件注入 | Bind Mount(只读) | 安全、灵活 |
| 临时缓存 | tmpfs | 内存存储、容器停止即清理 |
| 容器日志 | 默认 + 日志驱动 | 避免存储膨胀 |
tmpfs vs 默认存储:两者都是非持久化存储,但有本质区别:
对比维度 默认存储(容器可写层) tmpfs 存储位置 宿主机磁盘 宿主机内存 生命周期 容器删除( docker rm)时丢失容器停止( docker stop)时即丢失读写速度 磁盘 I/O 内存级别(快数个数量级) 是否落盘 是 否,数据从不写入磁盘 数据残留风险 有: docker rm仅标记磁盘块为可用,实际数据未被覆写,理论上可通过磁盘恢复工具找回无:数据仅存在于内存,停止即彻底消失 适用场景 普通临时文件(日志、pid) 敏感信息临时存放、高频读写缓存 # tmpfs 示例:在容器内 /app/cache 挂载 64MB 内存文件系统 docker run -d --name tmpfs-demo --tmpfs /app/cache:rw,size=64m nginx安全建议:密码、token、密钥等敏感信息应使用 tmpfs 而非默认存储,确保数据从不落盘,避免容器删除后敏感数据仍残留在宿主机磁盘上。
我们将通过一个 MySQL 数据库的例子来演示如何使用 Volume 持久化数据。
# 创建一个数据卷,名称为 mysql_data
docker volume create mysql_data
# 列出所有卷
docker volume ls
# 查看卷信息
docker volume inspect mysql_data
# 运行 MySQL 容器并挂载卷
# 备注:MYSQL_ROOT_PASSWORD 是环境变量,用于设置 MySQL 的 root 用户密码
docker run -d \
--name mysql_db \
-e MYSQL_ROOT_PASSWORD=mysecret \
-v mysql_data:/var/lib/mysql \
mysql:8.0
安全提示:此处密码仅用于演示。生产环境中应使用 Docker Secrets 或外部密钥管理服务来注入敏感信息,避免明文暴露。
# 进入容器创建测试数据
docker exec -it mysql_db mysql -uroot -pmysecret -h127.0.0.1
# 在 MySQL 中创建测试数据
mysql> CREATE DATABASE test_db;
mysql> USE test_db;
mysql> CREATE TABLE users (id INT, name VARCHAR(50));
mysql> INSERT INTO users VALUES (1, 'John Doe');
mysql> exit
# 删除原容器
docker rm -f mysql_db
# 使用同一个卷启动新容器
docker run -d \
--name mysql_db2 \
-e MYSQL_ROOT_PASSWORD=mysecret \
-v mysql_data:/var/lib/mysql \
mysql:8.0
# 验证数据是否存在
docker exec -it mysql_db2 \
mysql -uroot -pmysecret -e "USE test_db; SELECT * FROM users;"
Docker 提供了多种数据管理方式,核心原则是:需要持久化的数据不应存放在容器可写层中。开发环境优先使用 Bind Mount 实现代码实时同步,生产环境优先使用 Volume 保障数据安全和可管理性。根据实际场景选择合适的存储方式,是构建稳定容器化应用的关键。