Docker 数据存在哪:volume、bind、tmpfs 一次讲透

容器删了数据还在吗?看你怎么存的。小够把 Docker 数据持久化的几种方式掰开讲:具名数据卷、绑定挂载、tmpfs 和匿名卷,分别适合什么场景,备份恢复怎么做,一次搞明白。

我是小够,只推够用的 VPS。

Docker 新手最经典的翻车:容器里辛辛苦苦配好的东西,容器一删全没了。然后得出结论"Docker 不靠谱"。其实不是 Docker 不靠谱,是容器天生就是一次性的,数据得专门安排地方存。今天把几种存法一次讲透,外加备份恢复和迁移。

先理解:容器为什么是临时的

镜像是只读的模板,容器是模板跑起来的实例。你在容器里写文件,写的是容器自己的可写层——容器删掉,可写层跟着消失。所以重要数据永远不要只放在容器内部,这是第一原则。原理细节看上一篇可写层那篇,这里直接讲怎么做。

第一种:具名数据卷(named volume,推荐默认用它)

services:
  db:
    image: mysql:8.0
    volumes:
      - db-data:/var/lib/mysql

volumes:
  db-data:

db-data 是 Docker 帮你管理的一块地方,存在主机的 /var/lib/docker/volumes/ 下。优点:Docker 全权管理,备份、迁移、权限都不用你操心;跨平台行为一致,在哪台机器都一样。缺点:你想直接在主机上改里面的文件,不太顺手。

适合: 数据库、应用数据这种"你不需要直接碰文件"的场景。小够的默认选择。

数据卷的常用管理命令:

docker volume ls              # 列出所有卷
docker volume inspect db-data # 看详情:挂载点、驱动、创建时间
docker volume create my-vol   # 手动建卷
docker volume rm my-vol       # 删卷(删之前确认没用了)
docker volume prune           # 清掉所有没被容器用的卷

prune 会删"孤儿卷",执行前 docker volume ls 看一眼,别误删了还在用的。

第二种:绑定挂载(bind mount)

services:
  web:
    image: nginx:alpine
    volumes:
      - ./html:/usr/share/nginx/html

把主机的 ./html 目录直接映射进容器。主机上改文件,容器里立刻生效;反过来容器写文件,主机上也看得到。双向实时同步,这就是它最大的特点。

适合: 网站源码、配置文件这种"你要经常直接改"的场景。开发调试、放网站文件,用它最顺手。

注意三点:一是主机目录必须存在(Docker 会自动建,但权限可能是 root,建完检查一下);二是路径用相对路径 ./html,可移植性比绝对路径好,换机器直接拷目录就行;三是可以加 :ro 变成只读挂载:

volumes:
  - ./html:/usr/share/nginx/html:ro

Nginx 只需要读网站文件,写权限不给,安全加分。只读挂载是零成本的安全提升,顺手就写上。

第三种:tmpfs(内存盘,知道就行)

services:
  app:
    image: myapp
    tmpfs:
      - /tmp/cache

数据只放内存,容器停了就没。适合放临时缓存、session 这类丢了也无所谓的东西。1G 小内存机器慎用,别把内存吃光了。还可以限制大小:tmpfs: [/tmp/cache:rw,size=100m]。

第四种:匿名卷(知道它,别主动用)

services:
  db:
    image: mysql:8.0
    volumes:
      - /var/lib/mysql   # 没写名字,就是匿名卷

有些镜像的 Dockerfile 里写了 VOLUME 指令,你没声明具名卷时,Docker 会自动建个匿名卷。数据倒是持久化了,但名字是一串哈希,时间长了你都不知道它是干嘛的,docker volume ls 里一堆"孤儿",删都不敢删。小够的建议:看到匿名卷就给它起个名,改成具名卷,一分钟的事,省得以后抓瞎。

四种怎么选,一张表

场景选哪个
数据库(MySQL/Postgres)具名数据卷
网站源码、Nginx 配置绑定挂载(可加 :ro)
临时缓存、sessiontmpfs
要在主机上直接改的文件绑定挂载
换机器要整体迁移具名数据卷(配合备份)
镜像自带的 VOLUME 没声明改成具名卷,别留匿名卷

备份:光存对了还不够

数据卷只是让数据"留下来",机器挂了照样丢。备份分两种情况:

具名数据卷的备份,起个一次性容器来打包:

# 备份
docker run --rm -v db-data:/data -v ~/backup:/backup alpine tar czf /backup/db-data.tar.gz -C /data .

# 恢复
docker run --rm -v db-data:/data -v ~/backup:/backup alpine tar xzf /backup/db-data.tar.gz -C /data

原理:起一个一次性的 alpine 容器,把数据卷挂到 /data、备份目录挂到 /backup,在里面打包。--rm 让容器用完即删,干干净净。注意卷名前面的项目名前缀(compose 建的卷一般是 目录名_卷名),docker volume ls 确认。

绑定挂载的备份更简单,主机目录直接 tar 打包就行:

tar czf ~/backup/html-$(date +%F).tar.gz -C ~/apps/web html

备份文件一定要拷到另一台机器或对象存储——这是底线中的底线。放同一台机器上,机器挂了备份跟着没。小够建议:备份脚本 + cron 每周跑 + 每月手动拷一份到本地,三重保险。

恢复演练:备份了不等于能恢复

每季度做一次恢复演练,找台测试机(或本地虚拟机):

  1. 新机器装 Docker。
  2. 建同名数据卷:docker volume create db-data。
  3. 用恢复命令把 tar 包解进去。
  4. docker run --rm -v db-data:/data alpine ls /data 确认文件在。
  5. 起个测试容器挂上验证应用能读到数据。

没演练过的备份,等于没备份。这话小够说三遍都不嫌多。

迁移:换机器怎么办

  1. 老机器按上面打出 tar 包。
  2. 新机器建好同名数据卷,compose 文件拷过去。
  3. 解 tar 包到卷里,docker compose up -d。
  4. 验证应用正常,DNS 切过去。

绑定挂载的迁移更简单:整个项目目录 rsync 过去就行,compose 文件、.env、html 全在里面。

磁盘占用:定期看一眼

docker system df        # 镜像、容器、数据卷各占多少
docker system df -v     # 详细版,具体到每个卷

数据卷是"只增不减"的,日志、数据库 binlog、上传的文件都会让它变大。小硬盘 VPS(20-30G)建议每月看一次,超过 70% 就该清理或扩容了。数据库的 binlog 尤其要注意,长期不清理能涨到几十 GB,MySQL 里设个过期时间:

SET GLOBAL binlog_expire_logs_seconds = 604800;  -- 保留7天

实战:给没配数据卷的老项目补上

很多人是容器跑起来才发现"当初没挂卷"。别慌,数据还在容器里,迁出来就行:

# 1. 看容器里数据在哪(以 MySQL 为例)
docker inspect 老容器名 -f '{{.Mounts}}'

# 2. 没挂卷?把数据拷出来
docker cp 老容器名:/var/lib/mysql /tmp/mysql-data

# 3. 建卷,把数据灌进去
docker volume create db-data
docker run --rm -v db-data:/data -v /tmp/mysql-data:/src alpine cp -a /src/. /data/

# 4. 改 compose 文件加上 volumes,up -d 重建

四步,数据无损迁移。迁完验证应用能读到数据,再删老容器。先验证再删,顺序别反。

备份策略总表

数据类型存法备份方式频率
数据库具名卷打 tar 包每天/每周
网站文件绑定挂载tar 主机目录每周
配置文件绑定挂载/:ro进代码仓库随改随提交
临时缓存tmpfs不备份-

备份脚本放项目目录,cron 定时跑,备份包异地存一份。这张表贴在你的运维笔记里,换项目直接套用。

卷驱动:local 之外还有什么

默认的 local 驱动把卷存在本机。还有 nfs 驱动可以把卷存到 NAS/远端,多机共享数据用得上:

volumes:
  shared-data:
    driver: local
    driver_opts:
      type: nfs
      o: addr=192.168.1.100,rw
      device: ":/mnt/shared"

个人用户基本用不上,知道有这个选项就行。单机 local + 备份,就是标准答案。

常见问题

Q:docker compose down -v 是干嘛的?为什么大家都说危险? A:down 停容器,-v 顺手把具名数据卷也删了。数据卷一删,数据库就没了。没备份就别加 -v,小够见过太多人手滑。日常 down 就够了。

Q:数据卷存在主机哪个位置?能直接改吗? A:docker volume inspect 卷名 看 Mountpoint,一般在 /var/lib/docker/volumes/。能改,但不推荐直接改——权限容易乱,用容器挂载的方式操作更稳。

Q:绑定挂载和具名卷,性能有差别吗? A:Linux 上几乎没差别。macOS/Windows 的 Docker Desktop 上绑定挂载会慢一些,但咱们是 VPS,都是 Linux,不用纠结。

Q:一个容器能同时用多种吗? A:能,一个服务挂多个 volumes 很常见:数据库用具名卷,配置文件用绑定挂载,各取所长。上面生产模板那篇就是这么配的。

Q:容器里写的数据,我没挂任何卷,能找回来吗? A:容器还在就能 docker cp 容器名:/路径 本地路径 拷出来;容器删了就没了。所以重要数据一开始就要规划好存哪,别等删了再后悔。docker cp 是救急手段,不是常规流程。