排查 Docker Compose PWD 变量导致 Waline 数据库挂载异常

温馨提醒
记录一次因 Docker Compose 的 ${PWD} 变量在不同运行目录下解析值不同,导致 Waline 评论系统 Volume 挂载路径错误、数据库丢失的排查过程。

问题描述

最近发现博客评论系统(Waline v3)的后台管理页面可以正常打开,但登录时一直提示“用户名或密码错误”。试了试密码重置功能,页面显示“请稍后再试”,也搞不定。前台评论区域也不显示已有评论,整个系统看起来像是丢了所有数据一样。

这种情况在自部署项目中不算少见,我去 GitHub 和技术社区搜了一圈,发现类似问题还真不少——大都是因为 SQLite 数据库文件被意外清空或者指向了空文件,应用启动时自动创建了空表结构,导致原有用户数据全部“消失”,所以才会出现登录失败、重置失败这些看起来莫名其妙的问题。

问题原因

排查下来发现,根本原因是 Docker Compose 中 ${PWD} 变量在不同运行目录下解析出的值不同,导致 Volume 挂载路径异常。

我的 docker-compose 配置是这样的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
services:
  waline:
    container_name: waline
    image: lizheming/waline
    restart: always
    ports:
      - 8360:8360
    volumes:
      - ${PWD}/data:/app/data
    environment:
      # ...

注意这个 ${PWD}/data${PWD} 是一个预定义的环境变量,它的值取决于你执行 docker compose 命令时所在的目录,而不是 compose 文件所在的目录。

  • 在正确目录下执行(/docker/data/waline/)→ ${PWD} 解析为 /docker/data/waline → 挂载 /docker/data/waline/data:/app/data → 数据库正常
  • 在其他目录下执行(比如 /data)→ ${PWD} 解析为 /data → 挂载 /data:/app/data → 但 /data 目录下只有一个 0 字节的空 waline.sqlite,容器读到的就是个空数据库

这就能解释为什么数据“丢了”——不是文件没了,而是挂载到了错误的位置。

Docker Compose 变量替换逻辑

根据 Docker 官方文档,Compose 在解析变量时遵循以下规则:

当没有显式设置 --env-file 时,Compose 会按以下顺序确定项目目录:

  1. --project-directory 如果设置了的话
  2. 否则,第一个通过 -f/--file 指定的 Compose 文件所在的目录
  3. 否则,当前 shell 的工作目录(PWD

来源: Docker Docs - Variable Interpolation

同时,环境变量优先级如下(从高到低):

  1. docker compose run -e 命令行传入
  2. environmentenv_file 属性中从 shell 或 .env 文件插值的变量
  3. Compose 文件中 environment 属性直接设置的值
  4. Compose 文件中 env_file 属性引用的值
  5. Dockerfile 中 ENV 指令设置的值

来源: Docker Docs - Environment Variables Precedence

也就是说,${PWD} 的值完全取决于你在哪个目录敲下 docker compose up。如果 compose 文件里用了 ${PWD},但每次执行的目录不固定,挂载路径就会飘忽不定,这就埋下了隐患。

排查过程

先确认后台地址

我去 Waline 官方文档(waline.js.org)查了一下,确认了:

  • 管理后台路径是 <serverURL>/ui/register
  • 第一个注册的用户自动成为管理员
  • 支持邮箱密码登录,也支持 GitHub、微博、Twitter、Facebook、QQ 等第三方 OAuth

SSH 上服务器看看

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
ssh root@<内网IP>

# 查看 Waline 容器状态
docker ps | grep waline

# 查看容器日志
docker logs waline --tail 20

# 搜索错误日志
docker logs waline | grep -E 'error|Error|ERROR' | tail -10

日志里发现了关键线索:

1
2
NotFoundError: url `/__api__/admin` not found.
NotFoundError: url `/__api__/config` not found.

这些 404 错误说明数据库中的管理相关表不存在或为空,API 路由找不到对应的数据。

对比数据库文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 检查文件体积
ls -la /data/waline.sqlite                        # 0 字节 ❌
ls -la /docker/data/waline/data/waline.sqlite     # 49KB ✅

# 用 sqlite3 查看表结构
sqlite3 /docker/data/waline/data/waline.sqlite ".tables"
# 输出:wl_Comment  wl_Counter  waline-Migrations  wl_Users

# 查看用户数据
sqlite3 /docker/data/waline/data/waline.sqlite \
  -header -csv "SELECT mail,nick,type FROM wl_Users;"

正确的数据库里有 4 个用户(1 个管理员 + 3 个访客),而 /data/waline.sqlite 虽然存在但是空的。

验证 API 连通性

1
2
3
4
5
6
# 测试管理后台页面
curl -s http://localhost:8360/ui/register

# 测试 API 端点
curl -s http://localhost:8360/api/me
# 返回:{"errno":404,"errmsg":"url `/api/me` not found."}

404 进一步确认了数据库虽然加载了,但没有用户数据。

分析 docker-compose 配置

1
cat /docker/data/waline/docker-compose.yml

看到 volumes 里用的是 ${PWD}/data:/app/data${PWD} 在执行 docker compose up 时会被替换为当前 shell 的工作目录。如果之前在某次操作时不在 compose 文件所在目录执行命令,${PWD} 就解析到了其他路径,Volume 也就挂载到了错误的地方。

解决过程

找到原因就好办了。先在正确目录下执行一次,确保 ${PWD} 解析到正确路径:

1
2
3
cd /docker/data/waline
docker-compose down
docker-compose up -d

验证一下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 确认容器在跑
docker ps | grep waline

# 确认数据库用户都在
sqlite3 /docker/data/waline/data/waline.sqlite -header -csv "SELECT mail,nick,type FROM wl_Users;"
# 输出:
# mail,nick,type
# <管理员邮箱>,elisky,administrator
# <QQ用户>,维尼🍯,guest
# <163用户>,测试注册,guest
# <QQ用户>,学的慢,guest

数据全在,问题解决。后台地址还是 https://<博客域名>/ui/,用管理员邮箱密码登录就行。

经验总结

  1. Compose 文件中慎用 ${PWD}${PWD} 依赖执行命令时的当前目录,不够稳定。更推荐用相对路径(Compose 会自动以 compose 文件所在目录为基准),或者用 .env 文件固定变量值。
  2. Docker volume 挂载要特别注意:挂载点下的文件如果被意外覆盖或清空,数据说没就没了。以后得给 SQLite 数据库做个定时备份,不能裸奔。
  3. 排查问题先看日志docker logs 是最直接的线索来源,比瞎猜效率高多了。
  4. sqlite3 挺好用的:对于 SQLite 数据库,直接查询表结构和数据,一目了然。
  5. 现象分析很重要:登录失败 + 重置失败 + 评论消失,这些现象凑在一起,基本就是数据库层面的问题了,而不是密码记错了或者认证服务故障。