Docker 数据卷实战:volume、bind mount、tmpfs 到底怎么选,数据持久化与权限坑

Docker 数据卷实战:volume、bind mount、tmpfs 到底怎么选,数据持久化与权限坑

容器一删,数据库里的数据全没了------这是几乎每个人上手 Docker 都会栽的第一个跟头。原因很简单:容器的可写层是跟着容器生命周期走的,docker rm 之后就灰飞烟灭。想让数据活过容器,你得把它放到「卷」里。

但 Docker 的挂载方式有三种:volumebind mounttmpfs,新手常常混着用,结果要么数据没持久化,要么本地文件被容器改乱,要么权限报错启动不了。这篇把三种方式的区别、适用场景和真实的坑一次讲透。

先看最常见的错误:数据说没就没

bash 复制代码
# 起一个 MySQL,不挂任何卷
docker run -d --name mydb -e MYSQL_ROOT_PASSWORD=123456 mysql:8

# 建库、写数据......然后手滑
docker rm -f mydb

# 重新起一个,进去一看:库没了
docker run -d --name mydb -e MYSQL_ROOT_PASSWORD=123456 mysql:8

数据写进了容器的可写层,容器删了数据就删了。任何需要「重启/重建容器后还在」的数据,都必须落到卷上。

三种挂载方式各是什么

用一张表先建立整体认知:

类型 数据存哪 谁管理 典型用途
volume Docker 管理的目录(/var/lib/docker/volumes/) Docker 数据库、生产持久化数据
bind mount 你指定的宿主机任意路径 你自己 开发时挂源码、挂配置文件
tmpfs 宿主机内存,不落盘 Docker 临时敏感数据(密钥、session)

关键区别一句话:volume 让 Docker 替你管目录,bind mount 是你自己指定宿主机路径,tmpfs 根本不落盘。

volume:生产持久化的默认选择

创建并挂载一个命名卷:

bash 复制代码
# 显式创建(可选,run 时不存在会自动建)
docker volume create pgdata

# 挂到容器里。-v 卷名:容器内路径
docker run -d --name pg \
  -e POSTGRES_PASSWORD=secret \
  -v pgdata:/var/lib/postgresql/data \
  postgres:16

# 现在删容器再重建,数据还在
docker rm -f pg
docker run -d --name pg -e POSTGRES_PASSWORD=secret \
  -v pgdata:/var/lib/postgresql/data postgres:16

更推荐用语义更清晰的 --mount 写法(生产环境建议统一用它,参数拼错会直接报错而不是默默给你新建目录):

bash 复制代码
docker run -d --name pg \
  -e POSTGRES_PASSWORD=secret \
  --mount type=volume,source=pgdata,target=/var/lib/postgresql/data \
  postgres:16

管理命令:

bash 复制代码
docker volume ls                  # 列出所有卷
docker volume inspect pgdata      # 看卷的真实路径、驱动
docker volume rm pgdata           # 删卷(卷被容器占用时会拒绝)
docker volume prune               # 清理没有被任何容器引用的卷

docker volume prune 很好用,但它会删掉所有「悬空」卷------跑之前一定确认没有停着的容器还指望这些数据,否则一条命令抹掉你的测试库。

bind mount:开发时挂源码,改代码不用重建镜像

bind mount 把宿主机的某个路径直接映射进容器,最典型的用途是本地开发热更新:

bash 复制代码
# 把当前目录的源码挂进容器,改代码容器里立刻生效
docker run -d --name web \
  -v "$(pwd)":/app \
  -w /app \
  node:20 npm run dev

注意几个实战要点:

  • 路径必须写绝对路径-v ./src:/app 里的 ./src 在某些场景不被解析,用 $(pwd)/src 更稳。
  • bind mount 会「盖住」容器内原有内容 。如果容器镜像里 /app 本来有 node_modules,你把宿主机 /app(没装依赖)挂上去,容器里的 node_modules 就被空目录盖掉了。解法是给 node_modules 单独再挂一个匿名 volume 保护起来:
bash 复制代码
docker run -d --name web \
  -v "$(pwd)":/app \
  -v /app/node_modules \
  -w /app node:20 npm run dev

后一个 -v /app/node_modules(只写容器内路径)是匿名卷,优先级高于前面的 bind mount,把宿主机的空目录挡在外面,容器里镜像自带的依赖得以保留。

只读挂载:配置文件不想被容器改

挂配置文件时,加 :ro 让容器无法写回,防止程序或误操作改坏你的宿主机文件:

bash 复制代码
docker run -d --name nginx \
  -v "$(pwd)/nginx.conf":/etc/nginx/nginx.conf:ro \
  -p 80:80 nginx

:ro = read-only。容器内对该路径的写操作会直接失败,这在挂密钥、挂共享配置时是一道很值的保险。

tmpfs:敏感临时数据不落盘

有些数据你既不想留在容器里,也不想写到宿主机磁盘上(比如临时解密出来的密钥、大量临时文件)。tmpfs 把它放内存,容器一停就蒸发:

bash 复制代码
docker run -d --name app \
  --mount type=tmpfs,destination=/app/cache,tmpfs-size=64m \
  myapp:latest

内存有限,记得用 tmpfs-size 设上限,别让临时文件把内存吃爆。

最容易卡住的坑:权限不匹配

这是 bind mount 的高频翻车点。容器里的进程通常以某个特定 UID 运行(比如官方 postgres 镜像用 UID 999、很多镜像用非 root 的 UID 1000),而你宿主机目录的属主是你自己(比如 UID 501)。挂进去之后,容器内进程对这个目录没有写权限,启动就报 Permission denied

先定位问题------看容器里进程是谁、目录属主是谁:

bash 复制代码
# 容器内进程的 UID
docker exec app id
# 宿主机目录属主
ls -ln ./data

三种常见解法,按推荐程度排:

bash 复制代码
# 方法一:把宿主机目录的属主改成容器进程的 UID(最干净)
sudo chown -R 999:999 ./pgdata

# 方法二:启动时用 --user 让容器进程以你的 UID 跑
docker run --user "$(id -u):$(id -g)" -v "$(pwd)/data":/data myapp

# 方法三(仅限本地开发图省事,别上生产):放开目录权限
chmod -R 777 ./data

生产环境优先方法一或方法二。方法三的 777 等于对所有用户放开读写执行,是安全红线,只在本机临时调试用。

补充一个容易忽略的点:volume 不会有这个权限烦恼。因为命名卷第一次挂载到一个空卷时,Docker 会把容器镜像里目标目录的属主和权限「复制」到卷上,属主自动对齐。这也是「数据库用 volume 而不是 bind mount」的一个隐藏理由。

备份与迁移:卷里的数据怎么导出

命名卷不像 bind mount 那样能直接 cp,但可以起一个临时容器把它打包出来:

bash 复制代码
# 把 pgdata 卷的内容打包成宿主机当前目录的 backup.tar.gz
docker run --rm \
  -v pgdata:/data \
  -v "$(pwd)":/backup \
  alpine tar czf /backup/backup.tar.gz -C /data .

# 恢复到另一个卷
docker run --rm \
  -v pgdata_new:/data \
  -v "$(pwd)":/backup \
  alpine tar xzf /backup/backup.tar.gz -C /data

思路:用一个轻量的 alpine 容器同时挂上「源卷」和「宿主机备份目录」,在容器里做 tar 打包,--rm 让临时容器用完即删。

小结

  • 持久化数据(数据库等)用命名 volume:Docker 管理、权限自动对齐、迁移备份有标准套路。
  • 本地开发挂源码/配置用 bind mount:改文件立即生效;注意绝对路径、注意会盖住容器内原有目录、注意用匿名卷保护 node_modules。
  • 敏感临时数据用 tmpfs:只在内存、不落盘、记得限大小。
  • 挂配置文件加 :ro:防止容器写坏宿主机文件。
  • 权限报错先 docker exec app id 对比目录属主 :优先 chown 对齐或 --user,777 只在本机调试用。

一句话记忆点:容器是牛,数据是奶------牛可以随时宰(重建),但奶(数据)得先挤到卷里存好。

相关推荐
天道jimmy1 小时前
VulnHub 系列:Prime,1
linux·运维·服务器
Lumistory1 小时前
从OPPO长安研发中心看头部科技企业的照明运维选择
大数据·运维·人工智能·光照贴图
唐青枫2 小时前
Docker diff 详解:看清容器里到底改了什么
docker
好评1242 小时前
【Linux】数据链路层
linux·运维·网络
新时代牛马3 小时前
Linux 信号处理完整篇:从sigaction、掩码到内核发送与可重入排障
linux·运维·信号处理
lpfasd1233 小时前
Docker存储清理与防膨胀实践
运维·docker·容器
每天被AI救一次3 小时前
WorkBuddy 会议自动化实战:从腾讯会议转写到待办系统的完整链路(附全套提示词)
运维·自动化·腾讯会议
程序员梅雨3 小时前
Linux & Shell 实用干货
linux·运维·服务器·后端·面试·php
吴声子夜歌3 小时前
编写Shell脚本——自顶向下设计
linux·运维·shell