Docker 容器在运行时会产生大量数据,如何对这些数据进行持久化和管理是一个重要话题。本节将以 Nginx Web 服务器为例,深入介绍 Docker 的三种数据管理方式。
Docker 中常见的三种数据管理方式是:
下面以 Nginx Web 服务器为例,通过在三种方式下都执行同样的操作 ---- 创建一个 HTML 文件并测试其在容器删除后的可用性,来对比它们在数据持久型方面的差异。
我们直接在容器内创建文件,看看数据会发生什么。
首先,运行一个 nginx 容器:
docker run -d --name web-default -p 8000:80 nginx
然后,在容器中创建一个测试页面
docker exec -it web-default sh -c \
'echo "<h1>Hello from Default Storage</h1>" > /usr/share/nginx/html/index.html'
访问页面验证内容:
curl http://localhost:8000
停止并删除容器
docker stop web-default
docker rm web-default
用同样的配置重新运行容器
docker run -d --name web-default -p 8000:80 nginx
再次访问页面,会看到什么?
curl http://localhost:8000
在这个场景中,我们将主机上的目录直接挂载到容器中,看看会发生什么。
首先在宿主机本地创建一个目录:
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 Mount Storage</h1>" > /usr/share/nginx/html/index.html'
验证页面内容:
curl http://localhost:8081
停止并删除容器
docker stop web-bind
docker rm web-bind
用同样的配置再次运行一个新的容器,名称为 web-bind-2:
docker run -d --name web-bind-2 -p 8081:80 \
-v $(pwd)/nginx-content:/usr/share/nginx/html nginx
再次验证页面内容:
curl http://localhost:8081
在这个场景中,我们使用 Docker 管理的卷来存储数据。
首先创建一个 Docker Volume(名称为 nginx_data)
docker volume create nginx_data
接着运行 Nginx 容器,并挂在刚刚创建好的卷。卷被挂在到容器中的 /usr/share/nginx/html 目录下:
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 stop web-volume
docker rm 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 inspect nginx_data查看卷的详细信息,会有什么发现?
默认存储
Bind Mounts(绑定主机目录)
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
MySQL 是最常见的需要数据持久化的服务之一。 在这个案例中,我们将创建一个 Volume 来存储 MySQL 的数据目录, 并验证在容器被删除重建后,数据库中的数据依然完好。
# 创建一个卷,名称为 mysql_data
docker volume create mysql_data
# 查看卷信息
docker volume inspect mysql_data
# 列出所有卷
docker volume ls
首先,运行 MySQL 容器,并挂载 Volume mysql_data。
docker run -d --name mysql_db -e MYSQL_ROOT_PASSWORD=mysecret -v mysql_data:/var/lib/mysql mysql:8.0
接下来进入到容器中,启动 MySQL 客户端,连接到 MySQL 服务:
docker exec -it mysql_db mysql -uroot -pmysecret -h127.0.0.1
在 MySQL 中创建测试数据:
create database test_db;
use test_db
create table users(id int, name varchar(50));
insert into users values(1, 'Mike');
select * from users\G
exit
现在来验证 Volume 的持久化能力:删除当前的 MySQL 容器,再用同一个 Volume 启动一个全新的容器,看数据是否还在。
首先,停止并删除原容器:
docker stop mysql_db
docker rm mysql_db
然后,挂载同一个 Volume mysql_data,运行一个新的 MySQL 容器:
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;"
如果能看到之前插入的记录,说明数据确实存储在 Volume 中,不会因为容器的删除而丢失。
🤔这里的
-e选项是什么含义?与上一个命令中的-e是否相同?
Jupyter Notebook 是一个非常适合用 Bind Mounts 的场景:笔记文件(.ipynb)存放在代码仓库中,通过 Bind Mounts 挂载到容器内,就能在 Jupyter 中直接打开和编辑这些文件。更重要的是,由于 Bind Mounts 是双向同步的,在容器内的修改会实时反映到宿主机,反之亦然。
构建一个 Jupyter Notebook 镜像:
cd jupyter
docker build -t jupyter-notebook .
先查看本地的 .ipynb 文件是否存在:
ls -l notebooks
运行 Jupyter Notebook 容器:
docker run --name jupyter-notebook -p 8888:8888 -d -v $(pwd)/notebooks:/notebooks jupyter-notebook
其中 -v $(pwd)/notebooks:/notebooks 将宿主机当前目录下的 notebooks 文件夹挂载到容器内的 /notebooks 目录(即 Jupyter 的工作目录)。这样容器内对笔记文件的修改会实时同步到宿主机,反之亦然。
查看容器状态:
docker ps
查看容器日志,确认 Jupyter 服务已正常启动:
docker logs jupyter-notebook
在 CNB 上,我们可以通过添加一个端口映射来实现外网访问:

在浏览器中访问 Jupyter Notebook,你应该能看到 notebooks 目录下已有的 .ipynb 文件。
接下来我们验证 Bind Mounts 的双向同步特性:
容器 → 宿主机:在 Jupyter Notebook 界面中编辑并保存一个笔记文件,然后在宿主机上查看:
ls -l notebooks
你会发现文件已经实时同步到宿主机上。
宿主机 → 容器:反过来,在宿主机上直接修改笔记文件,刷新 Jupyter Notebook 页面后也能看到最新内容。
这就是 Bind Mounts 在开发场景下的优势:代码仓库中的文件和容器内的文件始终保持一致,非常适合本地开发和调试。