在这里插入图片描述

导言:那个“一次性”的 Nginx 首页

在 Docker 的世界里,容器被设计为“简洁且高效的”。它们可以被快速创建、启动、停止、分享和销毁,这带来了极大的灵活性。然而,在上一篇博文中,我们想要修改 Ngxin 的 index.html 页面文件,我们:

  1. 通过 docker exec 命令进入 Nginx 容器内部
  2. 大刀阔斧地修改了 /usr/share/nginx/html/index.html,打造了一个全新的首页
  3. 通过 -p 端口映射将 Nginx 默认的容器内部首页端口映射到外部的 88 端口

至此,我们成功修改了 Nginx 首页内容。但是这种方式存在一个严重的问题:如果由于任何原因(比如配置错误、版本更新)这个容器被销毁并重建了,当你再次访问时,你会发现,之前的所有修改均已丢失,这是为什么?

  • 原因其实在第一篇博文中,就已隐藏了答案。“一个镜像可以开启多个容器,每个容器互相独立”,每一个容器都是一个单独的个体,销毁容器,就如同销毁了整个应用,包含应用中修改的数据。

为了解决这个数据持久化的问题,Docker 提供了强大的存储机制,允许我们将数据 持久化(persist) 在容器之外。今天,我们将深入探讨两个最核心的概念:目录挂载卷存储


目录挂载 —— “所见即所得”的开发利器

目录挂载是一种非常直接的机制。它允许你将宿主机(Host)上的一个明确路径(目录或文件)“挂载”到容器内的指定路径上

  • 核心理念: 宿主机和容器共享同一个目录。你在宿主机上的修改会立即反映在容器中,反之亦然。

如何使用目录挂载?

通过在 docker run 命令中使用 -v (volume) 标志或更现代、更清晰的 --mount 标志。

语法(两种方案皆可,没有性能差别):

docker run -v

docker run -d \
  --name my-nginx \
  -p 8080:80 \
  -v /path/on/your/host:/path/in/container \
  nginx

docker run --mount

docker run -d \
  --name my-nginx \
  -p 8080:80 \
  --mount type=bind,source=/path/on/your/host,target=/path/in/container \
  nginx

需要注意的是: 不管是内部路径(容器内部)还是外部路径(宿主机),都必须是绝对路径地址

实例:实时编辑 Nginx 网站

让我们来解决文章开头的问题。

  1. 在宿主机上创建你的网站内容:
# 在你的 home 目录下创建一个目录
mkdir ~/my-nginx-content

# 创建一个首页
echo "<h1>Hello from my Host Machine!</h1>" > ~/my-nginx-content/index.html
  1. 使用目录挂载启动 Nginx 容器:
    我们将宿主机的 ~/my-nginx-content 目录挂载到 Nginx 容器存放网页的 usr/share/nginx/html 目录。
docker run -d \
  --name my-dev-nginx \
  -p 8080:80 \
  --mount type=bind,source=$HOME/my-nginx-content,target=/usr/share/nginx/html \
  nginx
  • $HOME 变量来动态获取 home 目录的绝对路径
  1. 验证效果:
  • 访问 http://localhost:8080,你将看到 “Hello from my Host Machine!”。
  • 测试实时同步:
    echo "<h1>Docker 目录挂载太棒了!</h1>" > ~/my-nginx-content/index.html
    
  • 刷新你的浏览器(无需重启容器)。页面内容立即更新了!
  1. 清理:
docker stop my-dev-nginx
docker rm my-dev-nginx

即使容器被删除了,你的 ~/my-nginx-content 目录和 index.html 文件仍然安全地保留在你的宿主机上。

目录挂载的优缺点

  • 优点:

    • 操作简单: 只需要简单的在 run 命令中增加挂载命令即可实现,操作简单且易于理解。
    • 共享配置: 通过在宿主机中存储,我们不仅可以实现在一个容器中挂载,甚至可以在多个容器中同时通过挂载方式共享我们的配置。
    • 实时同步: 宿主机与容器内部双端实时同步,不管是从哪一端更改,双端都能迅速的自动同步。
  • 缺点:

    • 紧密耦合: 需要注意的是,如果还需拷贝给同事,此时需要将宿主机中的文件同 .tar 文件一起传送,其与容器紧急耦合。
    • 权限问题: 可能会遇到宿主机和容器内部用户的 UID/GID (用户 ID/组 ID) 不匹配导致的“Permission Denied”问题。
    • 安全性: 将容器内部文件暴露在宿主机中,可能会导致一些安全性问题。

卷映射 —— “专业”的数据持久化管理员

宿主机存在一个问题,他会直接覆盖容器内指定的原始文件。如果我们只是需要在原始文件上新增更多内容,这该怎么处理?

  • 核心理念: 卷映射 是由 Docker 创建和管理的存储区域。你告诉 Docker:“我需要一块叫 mysql-data 的持久化空间”,然后将它附加到容器的 /var/lib/mysql 目录。

不需要关心这块数据在宿主机上的具体位置。Docker 会在宿主机的一个特定目录(如 /var/lib/docker/volumes/mysql-data)中处理所有细节。

如何使用卷映射?

语法(推荐使用 --mount):

docker run -d \
  --name my-database \
  --mount type=volume,source=my-volume-name,target=/path/in/container \
  mysql

语法(传统 -v):

# 冒号:前面是 卷 的名字,而不是宿主机路径
docker run -d \
  --name my-database \
  -v my-volume-name:/path/in/container \
  mysql

卷映射与目录挂载的区别非常明显:

  • 目录挂载: /path/on/host (包含 /,是绝对路径)
  • 卷映射: my-volume-name (不包含 /,只是一个名字)

实例:为 MySQL 数据库持久化数据

数据库是卷映射的完美应用场景。我们绝不希望数据库文件随容器的销毁而丢失。

  1. (可选)创建一个命名卷 (Named Volume): 虽然 Docker 会在需要时自动创建卷,但我们推荐先“显式”创建,更清晰。
    docker volume create mysql-persistent-data
    
  2. 使用卷启动 MySQL 容器: MySQL 默认将其数据存储在 /var/lib/mysql 目录。
    MySQL 默认将其数据存储在 /var/lib/mysql 目录。
    docker run -d \
      --name my-production-db \
      -e MYSQL_ROOT_PASSWORD=mysecretpassword \
      --mount type=volume,source=mysql-persistent-data,target=/var/lib/mysql \
      mysql:8
    
  3. 卷的神奇特性:自动数据初始化
    在你对比卷映射与目录挂载的时候,不仅会思考这个关键的问题:
    • 当我挂载一个空的 mysql-persistent-data 卷到一个非空的 /var/lib/mysql 目录(镜像中自带了默认文件)时,发生了什么?

答案是:Docker 会自动将容器镜像中 /var/lib/mysql 目录的默认内容,复制到空的卷中。

这太重要了!这意味着你的 MySQL 容器启动时,会发现 mysql-persistent-data 卷 是空的,于是它会把自己的默认数据库结构“初始化”到这个卷中。下一次启动时,它发现卷不再是空的,就会跳过初始化,直接使用现有的数据。这点是卷映射与目录挂载的本质区别。

管理你的卷

Docker 提供了一整套命令来管理这些“卷映射”:

  • docker volume create <name> 创建一个卷
  • docker volume ls 列出所有卷
  • docker volume inspect <name> 查看卷的详细信息 (包括它在宿主机上的真实路径 Mountpoint)
  • docker volume rm <name> 删除一个卷 (前提是没有容器在使用它)
  • docker volume prune 删除所有“孤儿”卷 (即未被任何容器使用的卷),非常适合清理环境

卷的优缺点

优点:

  • 解耦与可移植: 完美解耦。你的 docker-compose.yml 只需要定义卷的名字,它在任何安装了 Docker 的机器上都能运行。
  • Docker 托管: 由 Docker 管理生命周期,更安全,更易于备份、迁移和管理。
  • 高性能: 在某些系统(如 Docker Desktop for Mac/Windows)上,卷映射 的 I/O 性能优于目录挂载
  • 自动初始化: 如上所述,非常适合有状态应用。
  • 驱动支持: 卷不一定只存在本地,你可以使用插件驱动(Drivers)将卷存储在云上(如 AWS S3, Azure Blob)或 NFS。

缺点:

  • 不易宿主机访问: 数据存储在 Docker 管理的“黑盒”路径中,从宿主机直接编辑相对麻烦(虽然可以通过 docker volume inspect 找到路径)。

终极对比:目录挂载 vs. 卷映射,我该用哪个?

一张图表胜过千言万语。

特性目录挂载卷映射
存储位置宿主机的任意地址,由用户指定Docker 管理的专属路径
核心目的代码同步、配置共享、开发调试数据持久化、数据共享、数据备份
生命周期与宿主机路径绑定,删除容器不影响主机文件由 Docker 管理,删除容器后卷默认保留,需手动删除
性能在非 Linux 操作系统下通过虚拟机转发,性能损失所有平台上都稳定且高效、接近原生
数据初始化如果挂载到空目录,会清空容器内原有目录如果挂载到空卷,会复制容器内原有目录的内容到卷中
可移植性,严重依赖宿主机的目录结构,难以迁移,不依赖宿主机,可以轻松备份和迁移到其他机器

所以,在这么多对比下,我们可以说:如果用户在一个应用的开发阶段,建议使用目录挂载。而当这个应用已然成型,准备上生产环境,那么建议用户使用来统一管理。


结论:黄金法则

恭喜你,现在你已经掌握了 Docker 数据持久化的两大支柱!
记住这个简单的“黄金法则”:

  • 目录挂载: 开发时,把宿主机的文件夹“借”给容器用,双向同步,方便调试。
  • 卷: 生产时,让 Docker 帮你管理一个专门存数据的“仓库”,容器死了数据还在,安全可靠。

别再让你的数据“随用随弃”了。从今天起,为你的容器数据找一个安全的、持久的家!


2025.10.31 金融街

更多推荐