- 容器的可写层会随着容器一起删除,所以重要的数据需要挂载。
- 命名卷由 Docker 管理,是数据库和生产数据的通常选择。
- 绑定安装映射主机文件夹,这使得它们非常适合开发期间的实时代码。
docker compose down -v会删除卷,因此请小心使用,并保留经过测试的备份。
您在容器中启动数据库,添加一些数据,删除容器并启动一个新容器。数据没了。几乎每个人都经历过惨痛的教训才学到这一课。 Docker 卷 和绑定挂载的存在是为了在任何一个容器的生命周期之外保持数据的活动。
本指南解释了每种方法的工作原理以及如何选择。这里的命令和文件是标准示例,基于 Docker 记录的行为,我们没有在本文中运行它们,因此请首先在一次性数据上尝试它们。 Docker 版本之间和 Docker Desktop 上的详细信息可能有所不同。如果您不熟悉容器,请从我们的 Docker 初学者教程 开始。
为什么容器数据会消失
Docker 镜像由只读层组成。当容器启动时,Docker 在顶部添加一个薄的可写层,任何更改都在那里。该层在停止和重新启动后仍然存在,但在删除容器时会被删除。
docker run -d --name db -e POSTGRES_PASSWORD=devonly postgres:17
# ... create tables and add data ...
docker rm -f db
docker run -d --name db -e POSTGRES_PASSWORD=devonly postgres:17
# The data you added is not there
这是设计使然,因为它可以让您自由更换容器。这确实意味着您需要保留的任何东西都必须位于容器之外。有三种方法可以做到这一点:
| 类型 | 存储在 | 容器拆除后还能幸存吗? |
|---|---|---|
| 命名卷 | 由 Docker 管理的区域 | 是的 |
| 绑定挂载 | 您选择的主机路径 | 是的 |
| 临时文件系统挂载 | 主机内存 | 否 |
Docker 卷与并排绑定安装
| 方面 | 命名卷 | 绑定挂载 |
|---|---|---|
| 管理人 | 码头工人 | 你 |
| 地点 | Docker的存储区域 | 主机上的任意路径 |
| 取决于主机文件夹? | 否 | 是的 |
| 由主机编辑 | 设计上很尴尬 | 简单,变化立即显现 |
| 从图像中预填充空目标? | 是的 | 不,主机路径隐藏了它 |
| Docker CLI 命令 | 是的,docker volume |
否 |
| 远程存储驱动程序 | 是的 | 否 |
| 最适合 | 数据库、生产数据 | 源代码正在开发中 |
命名卷
卷是由 Docker 创建和管理的存储。您可以通过名称来引用它,而无需关心它的实际位置。
docker volume create pgdata
docker run -d --name db \
-e POSTGRES_PASSWORD=devonly \
-v pgdata:/var/lib/postgresql/data \
postgres:17
现在数据库文件位于 pgdata 中。删除容器并启动一个具有相同卷的新容器,您的数据仍将存在。在 Linux 上,卷通常位于 /var/lib/docker/volumes/ 下,但您不应直接编辑这些文件。在 Docker Desktop 上,它们位于虚拟机内,而不是普通文件夹中。
容器内的路径取决于图像,因此请检查图像的文档。安装到错误的路径是一个典型的错误:一切似乎都正常,但数据仍在写入容器层。映像的较新主要版本也可能会更改此路径,因此在升级之前请阅读发行说明。
如果命名卷不存在,Docker 会自动创建。没有名称创建的匿名卷也可以工作,但它有一个很长的随机ID,并且很容易丢失。始终为您的卷命名。
绑定坐骑
绑定挂载将特定主机路径直接映射到容器中。双方立即看到相同的文件。这使得它非常适合开发,因为编辑器中的编辑会在正在运行的容器中生效,而无需重建:
docker run -d --name web \
-p 8080:80 \
-v "$(pwd)/site:/usr/share/nginx/html:ro" \
nginx:1.27
编辑site/中的文件,刷新浏览器,您将看到更改。 :ro使挂载在容器内只读,当容器不需要在那里写入时,这是一个好习惯。
绑定安装需要一些权衡:
- 它们将您与主机联系起来。 该路径必须存在于每台计算机上,因此设置的可移植性较差。
- 它们隐藏图像的内容。如果您安装在
/app之上,则图像中的任何内容都会变得不可见。一个常见的例子是node_modules失踪。 - 它们暴露主机。具有可写挂载的容器可以更改或删除真实的主机文件。除非您完全了解风险,否则切勿安装
/或 Docker 套接字等敏感路径。 - 性能可能会有所不同。 在适用于 macOS 和 Windows 的 Docker Desktop 上,文件共享跨越虚拟化边界,并且可能比 Linux 上慢。
tmpfs 挂载
tmpfs 挂载存在于主机内存中,并且永远不会写入容器层。当容器停止时它就会消失,因此它适合临时文件、缓存和短暂的敏感数据:
docker run -d --tmpfs /app/cache:size=64m myapp:1.0
tmpfs 可在 Linux 主机上使用。它还可以加快一次性测试环境中的数据库速度,在这种环境中您不关心保存数据。
-v 或 –mount?
两个标志都有效。 --mount 更长,但更明确,很多人更喜欢在脚本中使用它:
# Short form
docker run -v pgdata:/var/lib/postgresql/data postgres:17
# Explicit form
docker run --mount type=volume,source=pgdata,target=/var/lib/postgresql/data postgres:17
# Bind mount, read-only
docker run --mount type=bind,source="$(pwd)"/site,target=/usr/share/nginx/html,readonly nginx:1.27
有一个有意义的区别。使用 -v,会为您创建一个缺失的主机路径作为一个空文件夹,这可以隐藏拼写错误。使用 --mount 和绑定类型,Docker 会报告错误。另外,对于-v,左侧决定类型:名称表示卷,路径表示绑定安装。所以-v data:/x和-v ./data:/x做了非常不同的事情。
Docker Compose 中的卷
大多数实际项目都在 Compose 文件中声明存储。这个使用了两种类型,每种都有其正确的工作:
services:
web:
build: .
ports:
- "3000:3000"
volumes:
- ./src:/app/src # bind mount: live code
depends_on:
- db
db:
image: postgres:17
environment:
POSTGRES_PASSWORD: devonly
volumes:
- pgdata:/var/lib/postgresql/data # named volume: data
volumes:
pgdata:
命名卷必须在顶级 volumes 部分中声明。 Compose 在实际名称前加上项目名称作为前缀。请参阅我们的 Docker Compose 教程 以获取完整的演练。
提示: docker compose down 保留命名卷,但 docker compose down -v 将它们连同数据一起删除。请特别注意包含 -v 的脚本和 shell 历史记录。
有用的音量命令
| 命令 | 它的作用 |
|---|---|
docker 卷 ls |
列出卷 |
docker 卷创建名称 |
创建一个卷 |
docker 卷检查名称 |
显示详细信息,包括其位置 |
docker 卷 rm 名称 |
删除卷及其数据 |
docker 卷修剪 |
删除未使用的卷 |
docker 系统 df -v |
显示磁盘使用情况,包括卷 |
在确认之前请检查 prune 将删除哪些内容,因为此操作无法撤消。当前没有容器使用的卷可能仍保存重要数据。
备份和恢复卷
可以通过运行安装卷和主机文件夹的短期容器来备份卷:
# Back up
docker run --rm \
-v pgdata:/data:ro \
-v "$(pwd)":/backup \
alpine tar czf /backup/pgdata.tar.gz -C /data .
# Restore into a new volume
docker volume create pgdata_restored
docker run --rm \
-v pgdata_restored:/data \
-v "$(pwd)":/backup \
alpine tar xzf /backup/pgdata.tar.gz -C /data
对于实时数据库,像这样复制文件可以捕获不一致的状态。首先停止容器,或者更好地使用数据库自己的工具,例如 PostgreSQL 的 pg_dump。而您从未恢复过的备份实际上只是一个希望,因此请测试恢复。
文件权限
“权限被拒绝”是安装时最常见的问题。文件在 Linux 上保留其数字用户和组 ID,因此如果容器以用户 1000 运行并且主机文件夹属于另一个用户,写入将会失败。解决这个问题的一些方法:
- 使用匹配的用户运行容器:
docker run --user "$(id -u):$(id -g)" ... - 按照我们的 Dockerfile 最佳实践 中所述,在映像中设置正确的所有权。
- 首选数据命名卷,因为新卷在首次使用时将承担图像文件夹的所有权。
- 在使用 SELinux 的系统上,绑定挂载可能需要
:z或:Z标签。
避免 chmod 777 的诱惑,正如我们的 Linux 命令指南 所解释的那样。
你应该使用哪个?
| 情况 | 使用 |
|---|---|
| 数据库数据 | 命名卷 |
| 实时编辑源代码 | 绑定挂载 |
| 来自存储库的配置文件 | 只读绑定挂载 |
| 在容器之间共享数据 | 命名卷 |
| 临时缓存或临时文件 | 临时文件系统 |
| 一次性测试数据库 | tmpfs,或者不挂载 |
| 生产中的应用程序代码 | 将其构建到图像中 |
最后一行很重要。在生产中,代码属于映像,因此您测试的就是您运行的。在集群中,数据存储的工作方式再次不同,通过持久卷,这在更广泛的 Kubernetes 文档中进行了介绍。我们的 Docker 与 Kubernetes 指南解释了两者之间的关系。
常见错误
- 挂载到错误的路径。数据仍然存在于容器层中,稍后就会消失。
- 随意运行
down -v。这将删除您的命名卷。 - 将相对路径与
-v. 等形式一起使用-v data:/x创建卷而不是文件夹映射。 - 将机密放入可写绑定挂载中。 将它们挂载为只读,或使用机密功能。
- 将两个数据库容器指向一个卷。这可能会损坏数据。
- 没有备份。 卷不是备份。它位于同一个磁盘上。
最后的话
经验法则很简短:命名卷用于数据,绑定挂载用于您正在编辑的代码,tmpfs 用于不应该持久的东西。在一次性数据库上尝试一下。创建一个命名卷,添加一行,删除容器,启动一个新容器,然后确认您的行仍然存在。然后在需要之前练习备份和恢复。
常见问题
Docker 卷和绑定挂载有什么区别?
卷由 Docker 管理并存储在自己的区域中。绑定挂载将您控制的特定主机路径直接映射到容器中。
停止容器会删除其数据吗?
不会。可写层中的数据在停止和重新启动后仍然存在。当容器被删除时它就会丢失,除非它存储在卷或绑定安装中。
docker compose down 后 Docker 卷是否仍然存在?
是的,命名卷在 docker compose down 后仍然保留。添加 -v 标志会删除它们以及它们包含的任何数据。
Docker 卷存储在哪里?
在 Linux 上,通常位于 /var/lib/docker/volumes 下。在 Docker Desktop 上,它们位于虚拟机内。使用 docker Volume Inspect 查看位置。
多个容器可以共享一个卷吗?
是的,多个容器可以安装一个卷。应用程序必须安全地处理并发访问,数据库通常不应以这种方式共享数据文件。
继续学习
需要测试、WordPress 或 SEO 方面的帮助?
我们帮助团队交付更好的软件并发展他们的网站。告诉我们您正在做什么,我们将建议明确的下一步。
更多指南 测试、人工智能和现代开发
喜欢这篇文章吗?选择一个主题并直接跳至我们最新的教程。
人工智能幻觉解释:语言模型为何会产生幻觉以及如何减少它
人工智能幻觉解释:它们是什么,为什么语言模型产生它们,常见类型,如何通过基础和提示来减少它们,以及我们进行的简单检查。
阅读文章 →DevOps环境变量解释:.env 文件、秘密和配置正确完成
使用我们运行的 Python 示例解释环境变量:读取和设置它们、为什么每个值都是字符串、.env 文件、进程继承、秘密和验证。
阅读文章 →软件开发API 速率限制解释:算法、标头和处理 429 错误
API 速率限制通过我们运行的模拟进行了解释:令牌桶、固定窗口、滑动窗口、429 响应、速率限制标头、带回退的重试和测试限制。
阅读文章 →测试自动化Python 中的模拟:unittest.mock、补丁以及导致测试谎言的错误
使用unittest.mock 在Python 中进行模拟,经过测试:模拟和return_value、side_effect、修补使用名称的位置、spec 和autospec、模拟日期以及常见错误。
阅读文章 →软件测试代码覆盖率解释:行覆盖率与分支覆盖率以及 100% 的真正含义
用真实的coverage.py运行来解释代码覆盖率:行、分支和条件覆盖率,阅读报告,为什么100%覆盖率仍然会错过错误,以及合理的目标。
阅读文章 →数据库与 SQLSQL 中的 DELETE、TRUNCATE 和 DROP:差异、回滚以及何时使用它们
DELETE、TRUNCATE 和 DROP 通过经过测试的 SQL 进行了解释:各自删除什么、回滚、触发器、身份重置、速度,以及 PostgreSQL、MySQL、SQL Server 和 SQLite 有何不同。
阅读文章 →数据库与 SQLSQL 视图解释:创建、使用和避免陷阱(附示例)
SQL 视图通过我们运行的查询进行解释:创建视图、实时结果、隐藏列、性能、可更新和物化视图、破坏的依赖关系和最佳实践。
阅读文章 →软件开发TypeScript 接口与类型:真正的差异,使用编译器进行测试
TypeScript 接口与类型,使用真实编译器进行检查:两者都可以做什么,只有一个可以做什么,声明合并,扩展与交集以及要遵循的简单规则。
阅读文章 →软件开发Python *args 和 **kwargs 解释:灵活的函数和清晰的示例
Python *args 和 **kwargs 用我们运行的代码进行了解释:打包和解包、参数顺序、仅关键字和仅位置参数、装饰器和常见错误。
阅读文章 →软件开发Python Lambda 函数:语法、实际用途以及何时不使用它们
Python lambda 函数用我们运行的代码进行了解释:语法、使用键排序、映射和过滤器、闭包、后期绑定陷阱,以及何时普通 def 是更好的选择。
阅读文章 →人工智能什么是人工智能代理?它们是如何工作的,只需运行一个简单的代理循环
什么是人工智能代理?关于代理循环、工具、内存和护栏的简单指南,以及我们运行的一个小型 Python 循环、真正的风险以及何时简单的聊天机器人是更好的选择。
阅读文章 →DevOps监控与可观察性:真正的区别,包括日志、指标和跟踪
监控与可观察性解释:已知和未知的故障、日志、指标和跟踪、检查事件演练、黄金信号、OpenTelemetry 和有用的警报。
阅读文章 →DevOpsDocker Volumes 与 Bind Mounts:以正确的方式保留容器数据
Docker 卷与绑定挂载解释:为什么容器数据消失、命名卷、绑定挂载、tmpfs、Compose 示例、备份、权限以及何时使用它们。
阅读文章 →测试自动化TDD 与 BDD:主要区别、以两种方式构建的一个功能以及何时使用每种方式
TDD 与 BDD 的解释是通过一种双向构建的折扣功能:使用我们运行的代码进行红绿重构、Given-When-Then 场景、工具、权衡以及何时使用每种方案。
阅读文章 →软件开发Git merge 与 rebase 的区别:示例及如何选择
用真实的 log 输出、对比表、rebase 黄金法则和安全的团队工作流,讲清 Git merge 与 rebase 的区别。
阅读文章 →软件开发Python 列表推导式:语法、示例与何时避免使用
Python 列表推导式详解:语法、过滤、if-else、嵌套循环、实测耗时,以及何时普通 for 循环更合适。
阅读文章 →数据库与 SQLSQL JOIN 详解:附示例(INNER、LEFT、RIGHT、FULL)
通过示例表和查询,一步步讲解 SQL JOIN:INNER、LEFT、RIGHT、FULL、CROSS 和 SELF,并介绍常见错误与性能技巧。
阅读文章 →