Administrator
发布于 2026-10-01 / 0 阅读
0
0

Docker 部署 Halo 博客:一个差点让我丢站的挂载错误

一次例行检查,我发现博客的数据根本不在我以为的地方。如果那天点了"升级镜像",整个站会安静地消失。

起因:一个"看起来很对"的 compose 文件

部署 Halo 博客时,我的 docker-compose.yml 是这样的:

services:
  halo:
    image: halohub/halo:2.21
    container_name: halo
    restart: unless-stopped
    ports:
      - "127.0.0.1:8090:8090"
    environment:
      HALO_INITIAL_ADMIN_EMAIL: admin@example.com
    volumes:
      - /opt/halo:/root/halo        # 问题在这一行

看起来没问题:数据目录挂到了宿主机 /opt/halo,容器销毁数据还在。

但是它有 bug。

发现:宿主机目录是空的

一次例行检查:

$ du -sh /opt/halo/
8.0K    /opt/halo/

8K——里面只有一个 compose 文件。

那数据在哪儿?

$ docker exec halo du -sh /root/.halo2
8.1M    /root/.halo2

8.1M,包含 db/、themes/、plugins/。

数据在容器的可写层里,根本没落盘。

根因:挂载点写错了

Halo 2.x 的工作目录是 /root/.halo2,不是 /root/halo。

compose 里写的 - /opt/halo:/root/halo,把宿主机目录挂到了一个 Halo 压根不用的路径。而 Halo 真正读写的 /root/.halo2,没有被任何 volume 覆盖,于是落在了容器可写层。

怎么确认?看容器里的环境变量:

$ docker inspect halo --format '{{range .Config.Env}}{{println .}}{{end}}' | grep -i halo
HALO_WORK_DIR=/root/.halo2
SPRING_CONFIG_LOCATION=optional:classpath:/;optional:file:/root/.halo2/

HALO_WORK_DIR 才是真相。别信 compose 里写了什么,信容器里的环境变量。

为什么这很危险

容器可写层的生命周期等于容器的生命周期。以下操作都会静默删光数据:

  • docker compose down(删除容器)
  • docker compose up -d --force-recreate
  • 镜像升级(本质是删旧容器、建新容器)

没有报错、没有警告,站点直接回到初始化状态:管理员账号没了、文章没了、主题设置没了。

我是因为"还没有正式内容"才逃过一劫。如果这发生在积累了三个月文章之后……

修复:四步安全迁移

第 1 步:备份(永远先备份)

docker exec halo tar czf /tmp/backup.tgz -C /root .halo2
docker cp halo:/tmp/backup.tgz /root/halo-backup-$(date +%F).tgz

第 2 步:停容器,把数据迁到宿主机

docker stop halo
docker cp halo:/root/.halo2 /opt/halo/.halo2

小技巧:docker cp 拷贝目录时,会把目录内容直接放进目标路径,不会多套一层。拷完检查 ls /opt/halo/.halo2/ 确认结构对得上。

第 3 步:改挂载点

volumes:
  - /opt/halo/.halo2:/root/.halo2      # 宿主机数据目录 → 容器工作目录

第 4 步:重建容器——注意不是 restart

docker compose down && docker compose up -d

关键点:修改 volume 挂载必须重建容器。docker restart 只是在现有容器里重启进程,挂载配置是创建时固化的,改不了。

验证:三个必须做的检查

1. 挂载点对不对

$ docker inspect halo --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{println}}{{end}}'
/opt/halo/.halo2 -> /root/.halo2

2. 数据是否真的落盘

$ ls -la /opt/halo/.halo2/db/
halo-next.mv.db

并且启动后这个文件的 mtime 应该会变——说明 Halo 正在读写挂载目录,而不是可写层。这一条最能说明问题。

3. 服务是否正常

curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8090/login

返回 200,并且用原账号能登录成功——说明数据库被完整带过来了。

事后加固

在 compose 里显式声明工作目录,让"挂载点"和"应用实际使用的路径"不再可能对不上:

environment:
  HALO_WORK_DIR: /root/.halo2
volumes:
  - /opt/halo/.halo2:/root/.halo2

小结

这个 bug 的可怕之处在于它不报错。容器正常运行、博客访问正常,只有"数据不落盘"这一个后果——而这个后果只在容器被销毁时才显现。

排查口诀:部署任何有状态服务后,做一次"宿主机目录体检":

du -sh <宿主机挂载目录>                                   # 应该是"像样的体积",不是几 KB
docker inspect <容器> --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{println}}{{end}}'
docker exec <容器> env | grep -i work                     # 应用认为自己该写哪里

三者对齐,才算真正持久化。


评论