Docker 容器无限重启?只因 v3.7.0 少了一个 serve —— 思源笔记 CLI 破坏性变更修复实录
先说结论
把思源笔记从 v3.6.x 升级到 v3.7.0 后,Docker 容器陷入无限重启循环。
docker logs显示Error: unknown flag: --accessAuthCode。根因是 v3.7.0 引入了 CLI 子命令架构,原来的顶级 flag 现在需要加一个serve子命令。修复只需在 docker-compose.yml 的command字段最前面加上'serve',多 7 个字符,从无限重启到正常运行。本文给出完整排查过程、三种操作系统下的一键修复脚本(Windows 11 / Ubuntu 26.04 / macOS 26),以及人工执行和 AI Agent 自动配置两种方法。所有脚本只调用 Docker CLI 和 SSH,不依赖第三方服务。

图 1:AI 生成封面。容器重启循环的修复,往往只需要一行改动。
一、问题背景:思源笔记与 Docker 部署
思源笔记(SiYuan) 是一款开源的个人知识管理系统,支持 Markdown 编辑、双向链接、数据同步和 Docker 部署。很多用户选择用 Docker 在服务器上运行思源,通过浏览器随时随地访问自己的笔记。
Docker 部署的典型配置如下:
version: "3.9"
services:
main:
image: b3log/siyuan:v3.7.0
command: ['--workspace=/siyuan/workspace/', '--accessAuthCode=xxx']
ports:
- 6806:6806
volumes:
- /docker/siyuan_data:/siyuan/workspace
restart: always
environment:
- PUID=1001
- PGID=1002
- SIYUAN_LANG=zh-CN
这个配置在 v3.6.x 及之前的版本中工作良好。然而,2026 年 6 月 30 日发布的 v3.7.0 是一个大版本更新,带来了全新的用户界面、内核插件系统和命令行接口(CLI)重构。正是这个 CLI 重构,导致了许多 Docker 用户的容器在升级后无法启动。
v3.7.0 带来了什么?
根据官方发布说明,v3.7.0 是一次全面的版本革新:
- 重新设计的用户界面:更精致的视觉层次
- 移动端快捷方式:长按应用图标即可快速记录灵感
- 内核插件系统:插件在单一内核中常驻运行,消除多窗口数据冲突
- 命令行接口(CLI):无需启动思源桌面端即可连接内核数据层,用于批量导入导出、定时处理和外部系统集成
- AI 知识库:思源 Agent 和向量嵌入搜索进入公开测试
其中第四项 —— CLI 命令行接口,就是本文要讨论的"罪魁祸首"。它带来了更强大的自动化能力,但也引入了一个破坏性变更。

图 2:思源 Docker 部署架构。Portainer 管理 Stack,容器内 entrypoint.sh 启动 kernel,kernel 再通过 serve 子命令启动 HTTP 服务。
二、问题表现:容器无限重启循环
某天,我在 Portainer 中把思源笔记的镜像从 v3.6.1 升级到了 v3.7.0,点击"重新部署"后,发现容器状态不对劲:
$ docker ps --filter name=siyuan
NAMES STATUS
siyuan-qiniu Restarting (1) 8 seconds ago
容器不断经历 启动 → 退出(exit code 1)→ Docker 自动重启 → 又退出 的死循环。从外面看,端口映射存在但无法访问,浏览器打开对应地址一直转圈。

图 3:真实终端截图 — docker ps 显示 siyuan 容器处于 Restarting 状态。
这就好比你家小区新换了一个门禁系统(升级镜像),结果门禁主机不断重启 —— 刚启动、闪一下屏、然后黑掉、再重启。你站在门口进不去,干着急。
用生活场景理解"容器重启循环"
打比方 —— 汽车启动故障:
你换了新发动机(升级镜像),但点火方式不对。每次转动钥匙,发动机"咔"响一声就熄火了。车载电脑自动尝试重新点火(restart: always),于是你听到"咔-咔-咔"不断重复。要修好它,不能只拔电瓶,得看故障码 —— 对容器来说,故障码就是 docker logs。
三、问题分析:从 docker logs 找到第一线索
容器出问题,第一反应不是重启、不是回滚、不是重装,而是 看日志。
$ docker logs --tail 40 siyuan-qiniu
Error: unknown flag: --accessAuthCode
Usage:
kernel [command]
Available Commands:
asset Manage assets
attr Manage block attributes
block Block operations
bookmark Manage bookmarks
completion Generate the autocompletion script
serve Start kernel HTTP server
export Export documents
...
Flags:
--dry-run dry run mode
-h, --help help for kernel
-w, --workspace string workspace path

图 4:真实截图 — docker logs 显示的 kernel CLI 错误信息。

图 5:真实截图 — 修复后思源笔记 v3.7.0 的锁屏登录页面。输入 accessAuthCode 即可访问笔记。
日志非常清楚地告诉我们:--accessAuthCode 是一个未知的 flag。而且,kernel 列出了它认识的所有子命令(serve、asset、block 等),其中 serve 的描述正是 “Start kernel HTTP server”。
这个输出本身就是一个巨大的提示:kernel 期望的第一个参数是一个子命令,而不是 --workspace 或 --accessAuthCode 这样的 flag。
排查链路
完整的排查链路如下:
docker ps -a→ 发现容器Restartingdocker logs→ 看到unknown flag: --accessAuthCodedocker inspect→ 确认 compose 配置路径和 command 字段- 查阅官方 README → 发现 v3.7.0 起必须显式传入
serve子命令 - 定位根因 → compose 文件中
command缺少serve

图 6:Docker 容器故障排查标准流程。docker logs 是第一线索,不要只看 docker ps。
四、问题根因:v3.7.0 CLI 子命令架构变更
打比方 —— 餐厅点餐系统升级
旧系统(v3.6.x 及之前):你走进餐厅,直接对服务员说"宫保鸡丁、米饭、可乐"。服务员知道你在点餐,就帮你下单了。
新系统(v3.7.0):餐厅装了新的 POS 系统。现在你必须先说"点餐",然后再说"宫保鸡丁、米饭、可乐"。如果你直接说"宫保鸡丁",系统会疑惑:“宫保鸡丁是什么操作?我只认识’点餐’、‘结账’、‘打包’这些操作。”
这里的"点餐"就是 serve 子命令,“宫保鸡丁"就是 --workspace 和 --accessAuthCode 参数。
技术层面
在 v3.7.0 之前,思源的 kernel 入口直接接受顶级 flag:
kernel --workspace=/siyuan/workspace/ --accessAuthCode=xxx
v3.7.0 引入了完整的 CLI 子命令架构(这是 Issue #17674 的实现),kernel 现在需要一个子命令:
kernel serve --workspace=/siyuan/workspace/ --accessAuthCode=xxx
Docker 镜像的 ENTRYPOINT 是 /opt/siyuan/entrypoint.sh,这个脚本负责设置 PUID/PGID 然后调用 kernel。但它本身不会自动加上 serve 子命令 —— 这个责任落在了 CMD 或 compose 的 command 字段上。
在 docker-compose.yml 中,原来的配置是:
command: ['--workspace=/siyuan/workspace/', '--accessAuthCode=xxx']
这些参数直接传给了 entrypoint.sh,然后传给 kernel。kernel 把 --workspace 当成子命令名来解析,发现不认识,于是报错退出。Docker 的 restart: always 策略又把它拉起来,如此往复。

图 7:v3.6.x vs v3.7.0 CLI 参数结构对比。新增的 serve 子命令是唯一的区别。
注意:官方 README 第 216 行已经明确写了这个变更: “自 v3.7.0 起,必须显式传入
serve子命令(例如docker run b3log/siyuan serve --workspace=...)。”但在升级已有部署时,很多人(包括我)不会重新读一遍 README,导致踩坑。
五、如何解决问题
5.1 修复原理
修复极其简单:在 compose 文件的 command 数组最前面加一个 'serve'。
- command: ['--workspace=/siyuan/workspace/', '--accessAuthCode=xxx']
+ command: ['serve', '--workspace=/siyuan/workspace/', '--accessAuthCode=xxx']
仅 7 个字符的改动,从无限重启到正常运行。

图 8:docker-compose.yml 修改内容 —— 只在 command 数组最前面加了 ‘serve’。
5.2 一键修复脚本
以下脚本适用于三种操作系统。它们通过 SSH 连接到 Docker 宿主机,自动检测思源容器、备份原配置、修补 command 字段、重建容器。
安全提醒:脚本会自动备份原 docker-compose.yml(带时间戳),不会覆盖数据卷,可安全回滚。
Windows 11(PowerShell)
# siyuan-fix.ps1 — 思源 v3.7.0 容器修复脚本
# 用法:.\siyuan-fix.ps1 -ServerHost <docker-host> -ServerUser root -StackName siyuan-note-qiniu
param(
[Parameter(Mandatory=$true)][string]$ServerHost,
[string]$ServerUser = "root",
[string]$StackName = "siyuan-note-qiniu"
)
$ErrorActionPreference = "Stop"
Write-Host "=== 思源 v3.7.0 Docker 容器修复 ===" -ForegroundColor Cyan
# 1. 检测容器状态
Write-Host "[1/5] 检查容器状态..." -ForegroundColor Yellow
$status = ssh "${ServerUser}@${ServerHost}" "docker ps --filter name=$StackName --format '{{.Status}}'"
Write-Host " 容器状态: $status"
# 2. 查看日志确认问题
Write-Host "[2/5] 查看容器日志..." -ForegroundColor Yellow
$logs = ssh "${ServerUser}@${ServerHost}" "docker logs --tail 20 $($StackName)_main_1 2>&1 || docker logs --tail 20 siyuan-qiniu 2>&1 || docker logs --tail 20 \$(docker ps -a --filter name=siyuan --format '{{.Names}}') 2>&1"
if ($logs -match "unknown flag.*accessAuthCode") {
Write-Host " ✓ 确认为 v3.7.0 CLI 变更导致的问题" -ForegroundColor Green
} else {
Write-Host " ⚠ 错误信息与预期不符,请手动检查" -ForegroundColor Red
Write-Host $logs
exit 1
}
# 3. 定位 compose 文件
Write-Host "[3/5] 定位 docker-compose.yml..." -ForegroundColor Yellow
$composePath = ssh "${ServerUser}@${ServerHost}" "docker inspect --format '{{index .Config.Labels \"com.docker.compose.project.config_files\"}}' \$(docker ps -a --filter name=siyuan --format '{{.ID}}' | head -1) 2>/dev/null"
if (-not $composePath) {
# 备用方案:搜索 Portainer 目录
$composePath = ssh "${ServerUser}@${ServerHost}" "grep -rl 'siyuan' /docker/portainer_data/compose/*/docker-compose.yml 2>/dev/null | head -1"
}
Write-Host " Compose 文件: $composePath"
# 4. 备份并修复
Write-Host "[4/5] 备份并修复 compose 文件..." -ForegroundColor Yellow
$fixCmd = @"
cp '$composePath' '$composePath.bak.\$(date +%Y%m%d-%H%M%S)' && \
sed -i \"s|command: \\[\"--workspace|command: \\[\"serve\", \"--workspace|g\" '$composePath' && \
echo 'FIXED' && cat '$composePath' | grep -A 2 'command:'
"@
$result = ssh "${ServerUser}@${ServerHost}" $fixCmd
Write-Host $result
if ($result -match "FIXED") {
Write-Host " ✓ compose 文件已修复" -ForegroundColor Green
} else {
Write-Host " ⚠ 修复可能未生效,请手动检查" -ForegroundColor Red
exit 1
}
# 5. 重建容器
Write-Host "[5/5] 重建容器..." -ForegroundColor Yellow
$composeDir = Split-Path $composePath -Parent
$projectName = "siyuan-note-qiniu"
$rebuildResult = ssh "${ServerUser}@${ServerHost}" "cd '$composeDir' && docker compose -p $projectName up -d --force-recreate 2>&1"
Write-Host $rebuildResult
# 验证
Start-Sleep -Seconds 5
$finalStatus = ssh "${ServerUser}@${ServerHost}" "docker ps --filter name=siyuan --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'"
Write-Host "`n=== 最终状态 ===" -ForegroundColor Cyan
Write-Host $finalStatus
$healthCheck = ssh "${ServerUser}@${ServerHost}" "curl -sf -o /dev/null -w '%{http_code}' http://127.0.0.1:6001/ 2>&1 || echo 'port may differ'"
Write-Host "HTTP 状态码: $healthCheck (401 = 正常,锁屏密码生效)"
Write-Host "`n✅ 修复完成!" -ForegroundColor Green
Ubuntu 26.04 / Debian
#!/usr/bin/env bash
# siyuan-fix.sh — 思源 v3.7.0 容器修复脚本
# 用法: bash siyuan-fix.sh <docker-host> [ssh-user] [stack-name]
set -euo pipefail
SERVER_HOST="${1:?请指定 Docker 宿主机地址}"
SERVER_USER="${2:-root}"
STACK_NAME="${3:-siyuan-note-qiniu}"
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
ssh_cmd() { ssh -o StrictHostKeyChecking=accept-new "${SERVER_USER}@${SERVER_HOST}" "$@"; }
echo -e "${CYAN}=== 思源 v3.7.0 Docker 容器修复 ===${NC}"
# 1. 检测容器状态
echo -e "${YELLOW}[1/5] 检查容器状态...${NC}"
ssh_cmd "docker ps -a --filter name=siyuan --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'"
# 2. 确认日志中的错误
echo -e "${YELLOW}[2/5] 确认错误日志...${NC}"
CONTAINER_NAME=$(ssh_cmd "docker ps -a --filter name=siyuan --format '{{.Names}}' | head -1")
LOGS=$(ssh_cmd "docker logs --tail 20 ${CONTAINER_NAME} 2>&1" || true)
if echo "$LOGS" | grep -q "unknown flag.*accessAuthCode"; then
echo -e "${GREEN} ✓ 确认为 v3.7.0 CLI 变更导致的问题${NC}"
else
echo -e "${RED} ⚠ 错误信息与预期不符,请手动检查${NC}"
echo "$LOGS"
exit 1
fi
# 3. 定位 compose 文件
echo -e "${YELLOW}[3/5] 定位 docker-compose.yml...${NC}"
COMPOSE_PATH=$(ssh_cmd "docker inspect --format '{{index .Config.Labels \"com.docker.compose.project.config_files\"}}' \$(docker ps -a --filter name=siyuan --format '{{.ID}}' | head -1) 2>/dev/null" || true)
if [[ -z "$COMPOSE_PATH" ]]; then
COMPOSE_PATH=$(ssh_cmd "grep -rl 'siyuan' /docker/portainer_data/compose/*/docker-compose.yml 2>/dev/null | head -1" || true)
fi
if [[ -z "$COMPOSE_PATH" ]]; then
echo -e "${RED}无法定位 docker-compose.yml,请手动指定${NC}"
exit 1
fi
echo " Compose 文件: $COMPOSE_PATH"
# 4. 备份并修复
echo -e "${YELLOW}[4/5] 备份并修复 compose 文件...${NC}"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
ssh_cmd "cp '$COMPOSE_PATH' '$COMPOSE_PATH.bak.$TIMESTAMP'" && echo " ✓ 已备份到 .bak.$TIMESTAMP"
ssh_cmd "sed -i \"s|command: \\\\[\\\"--workspace|command: \\\\[\\\"serve\\\", \\\"--workspace|g\" '$COMPOSE_PATH'"
echo -e "${GREEN} ✓ compose 文件已修复${NC}"
# 5. 重建容器
echo -e "${YELLOW}[5/5] 重建容器...${NC}"
COMPOSE_DIR=$(dirname "$COMPOSE_PATH")
ssh_cmd "cd '$COMPOSE_DIR' && docker compose -p $STACK_NAME up -d --force-recreate 2>&1"
# 验证
sleep 5
echo -e "\n${CYAN}=== 最终状态 ===${NC}"
ssh_cmd "docker ps --filter name=siyuan --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'"
HTTP_CODE=$(ssh_cmd "curl -sf -o /dev/null -w '%{http_code}' http://127.0.0.1:6001/ 2>&1" || echo "N/A")
echo -e "HTTP 状态码: ${HTTP_CODE} (401 = 正常,锁屏密码生效)"
echo -e "\n${GREEN}✅ 修复完成!${NC}"
macOS 26
#!/usr/bin/env bash
# siyuan-fix-mac.sh — 思源 v3.7.0 容器修复脚本 (macOS)
# 用法: bash siyuan-fix-mac.sh <docker-host> [ssh-user] [stack-name]
# 前置条件: brew install coreutils (提供 gsed)
set -euo pipefail
SERVER_HOST="${1:?请指定 Docker 宿主机地址}"
SERVER_USER="${2:-root}"
STACK_NAME="${3:-siyuan-note-qiniu}"
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
ssh_cmd() { ssh -o StrictHostKeyChecking=accept-new "${SERVER_USER}@${SERVER_HOST}" "$@"; }
echo -e "${CYAN}=== 思源 v3.7.0 Docker 容器修复 (macOS) ===${NC}"
# 检查必要工具
if ! command -v ssh &>/dev/null; then echo -e "${RED}需要 SSH 客户端${NC}"; exit 1; fi
# 1. 检测
echo -e "${YELLOW}[1/5] 检查容器状态...${NC}"
ssh_cmd "docker ps -a --filter name=siyuan --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'"
# 2. 确认错误
echo -e "${YELLOW}[2/5] 确认错误日志...${NC}"
CONTAINER_NAME=$(ssh_cmd "docker ps -a --filter name=siyuan --format '{{.Names}}' | head -1")
LOGS=$(ssh_cmd "docker logs --tail 20 ${CONTAINER_NAME} 2>&1" || true)
if echo "$LOGS" | grep -q "unknown flag.*accessAuthCode"; then
echo -e "${GREEN} ✓ 确认为 v3.7.0 CLI 变更问题${NC}"
else
echo -e "${RED} ⚠ 错误信息不符${NC}"; echo "$LOGS"; exit 1
fi
# 3. 定位 compose
echo -e "${YELLOW}[3/5] 定位 compose 文件...${NC}"
COMPOSE_PATH=$(ssh_cmd "docker inspect --format '{{index .Config.Labels \"com.docker.compose.project.config_files\"}}' \$(docker ps -a --filter name=siyuan --format '{{.ID}}' | head -1) 2>/dev/null" || true)
[[ -z "$COMPOSE_PATH" ]] && COMPOSE_PATH=$(ssh_cmd "grep -rl 'siyuan' /docker/portainer_data/compose/*/docker-compose.yml 2>/dev/null | head -1" || true)
[[ -z "$COMPOSE_PATH" ]] && { echo -e "${RED}无法定位 compose 文件${NC}"; exit 1; }
echo " Compose: $COMPOSE_PATH"
# 4. 备份并修复 (macOS 用 gsed 或 python)
echo -e "${YELLOW}[4/5] 备份并修复...${NC}"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
ssh_cmd "cp '$COMPOSE_PATH' '$COMPOSE_PATH.bak.$TIMESTAMP'" && echo " ✓ 已备份"
# 使用 python 做替换,避免 macOS sed 兼容问题
ssh_cmd "python3 -c \"
import re
p = '$COMPOSE_PATH'
c = open(p).read()
c = c.replace('command: [\\\"--workspace', 'command: [\\\"serve\\\", \\\"--workspace')
open(p, 'w').write(c)
print('FIXED')
\""
echo -e "${GREEN} ✓ compose 文件已修复${NC}"
# 5. 重建
echo -e "${YELLOW}[5/5] 重建容器...${NC}"
COMPOSE_DIR=$(dirname "$COMPOSE_PATH")
ssh_cmd "cd '$COMPOSE_DIR' && docker compose -p $STACK_NAME up -d --force-recreate 2>&1"
sleep 5
echo -e "\n${CYAN}=== 最终状态 ===${NC}"
ssh_cmd "docker ps --filter name=siyuan --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'"
HTTP_CODE=$(ssh_cmd "curl -sf -o /dev/null -w '%{http_code}' http://127.0.0.1:6001/ 2>&1" || echo "N/A")
echo -e "HTTP: ${HTTP_CODE} (401 = 正常)"
echo -e "\n${GREEN}✅ 修复完成!${NC}"
5.3 手动修复步骤(无需脚本)
如果你不想用脚本,手动修复也很简单:
# 1. SSH 到 Docker 宿主机
ssh root@<your-docker-host>
# 2. 找到 compose 文件(通常在 Portainer 数据目录下)
grep -rl "siyuan" /docker/portainer_data/compose/*/docker-compose.yml
# 3. 备份原文件
cp /path/to/docker-compose.yml /path/to/docker-compose.yml.bak.$(date +%Y%m%d-%H%M%S)
# 4. 修改 command 行(在数组最前面加上 'serve')
# 原: command: ['--workspace=/siyuan/workspace/', '--accessAuthCode=xxx']
# 改: command: ['serve', '--workspace=/siyuan/workspace/', '--accessAuthCode=xxx']
# 可以用 sed 一键替换:
sed -i "s|command: \\[\"--workspace|command: \\[\"serve\", \"--workspace|g" /path/to/docker-compose.yml
# 5. 验证修改
grep "command:" /path/to/docker-compose.yml
# 6. 重建容器
cd $(dirname /path/to/docker-compose.yml)
docker compose -p siyuan-note-qiniu up -d --force-recreate
# 7. 验证
docker ps --filter name=siyuan
curl -o /dev/null -w "%{http_code}\n" http://127.0.0.1:6001/
# 返回 401 表示锁屏密码生效,服务正常!

图 9:真实终端截图 — 重建容器后 Status 变为 Up,curl 返回 401(锁屏密码生效)。
六、人工执行 vs AI Agent 自动配置
6.1 人工执行
人工修复这个问题的预估时间是 15-30 分钟:
- 发现容器重启(1 分钟)
docker logs查看日志(1 分钟)- 不理解错误信息,Google 搜索(5-15 分钟)
- 找到官方 README 或 GitHub Issue(5 分钟)
- 修改 compose 并重建(3 分钟)
- 验证(2 分钟)
但实际经验是,大部分时间花在"理解这个错误是什么意思"上。unknown flag 这个提示对于不熟悉 CLI 子命令概念的人来说不够直观。
6.2 AI Agent 自动配置
如果让 AI Agent 来修复,流程大致如下:
给 Agent 的提示词:
---
我的思源笔记 Docker 容器一直在重启。请帮我排查和修复。
约束条件:
1. 不要执行大版本 OS 升级
2. 不要下载不明安装器
3. 不要输出真实内网 IP、完整计算机名、私有域名或密钥
4. 修改配置文件前先备份
5. 修复后验证容器状态和 HTTP 可达性
---
Agent 的执行流程:
docker ps -a→ 发现容器 Restartingdocker logs→ 看到unknown flag: --accessAuthCodedocker inspect→ 确认 compose 配置和镜像版本- 查阅官方 README → 发现 v3.7.0 的
serve子命令要求 - 备份 → 修改 compose → 重建容器 → 验证
实测效果:从开始排查到修复完成,Agent 用时约 10 分钟。如果人工来做,考虑到搜索、阅读文档和理解 CLI 架构变更,通常需要 20-30 分钟。

图 10:AI Agent 远程修复 Docker 容器故障的完整流程。
6.3 给 Agent 的护栏
让 Agent 操作生产环境,以下护栏必不可少:
| 护栏 | 说明 |
|---|---|
| 备份优先 | 修改任何配置文件前必须先备份 |
| 不执行 OS 升级 | Agent 只能操作容器层,不能动宿主系统 |
| 不泄露隐私 | 日志、配置、路径中不能包含真实 IP/域名/密钥 |
| 先诊断后动手 | 必须通过 logs/inspect 确认根因后再修改 |
| 验证闭环 | 修复后必须检查容器状态和 HTTP 可达性 |
| 可回滚 | 保留备份文件和回滚命令 |
七、修复后的验证
修复完成后,思源笔记的服务会正常运行。访问 http://<your-host>:<port>/(本例中端口为 6001),会看到思源的锁屏页面:

图 11:真实截图 — 修复后思源笔记 v3.7.0 锁屏登录页面(大尺寸)。输入 accessAuthCode 即可进入笔记。
输入在 --accessAuthCode 中设置的密码即可进入笔记。如果看到 HTTP 401 响应(用 curl 测试),说明锁屏密码生效,服务完全正常。
$ curl -o /dev/null -w "%{http_code}\n" http://<host>:<port>/
401
HTTP 401 在这里不是错误 —— 它表示思源的访问控制正在工作,只有输入正确密码才能进入。
八、Q&A
Q1:为什么思源要在 v3.7.0 引入这个破坏性变更?
因为 v3.7.0 新增了完整的命令行接口(CLI),kernel 现在不仅是一个 HTTP 服务器,还是一个多功能的命令行工具。除了 serve(启动 HTTP 服务),还有 export(导出文档)、import(导入文件)、sync(同步数据)等子命令。把功能拆成子命令是 CLI 设计的标准做法,就像 git commit、git push 一样。
Q2:我的思源还是 v3.6.x,该不该升级?
v3.7.0 带来了全新 UI、内核插件系统和 AI 知识库,是非常值得升级的版本。升级前只需确认你的 docker-compose.yml 中 command 字段包含 serve 子命令即可。
Q3:如果我不止一个思源容器要修怎么办?
上面提供的一键脚本可以通过参数指定不同的 Stack 名称。如果你有多个思源实例,遍历每个 compose 目录执行修复即可。
Q4:Portainer 里怎么改?
在 Portainer 中找到对应的 Stack,点击 Editor,在 command 数组最前面加上 'serve',,然后点击 “Update the stack” 即可。Portainer 会自动重建容器。
Q5:修复后数据会不会丢?
不会。数据存储在 /siyuan/workspace(或你挂载的对应目录)中,修复只改了 compose 文件的 command 字段,不涉及数据卷。而且修复前会自动备份 compose 文件。
Q6:有没有比改 compose 更简单的方法?
如果你用 docker run 而不是 compose,只需在命令中加上 serve:
docker run -d \
-v /siyuan/workspace:/siyuan/workspace \
-p 6806:6806 \
b3log/siyuan \
serve \
--workspace=/siyuan/workspace/ \
--accessAuthCode=xxx
Q7:其他 Docker 镜像也会有这种问题吗?
任何从"无子命令 CLI"升级到"有子命令 CLI"的软件都可能出现类似问题。升级前看一下 CHANGELOG 中是否有 “CLI”、“command”、“breaking change” 等关键词是个好习惯。
九、总结
这次排查的教训可以浓缩为三句话:
- 容器出问题,先看
docker logs。docker ps只能告诉你"容器在重启”,docker logs才能告诉你"为什么重启"。 - 升级前看一眼 CHANGELOG。v3.7.0 的 README 已经明确写了需要
serve子命令,但升级已有部署时很容易漏掉。 - 修复前先备份。
cp file.yml file.yml.bak.$(date)花不了 2 秒,但能在出问题时救命。
从技术角度看,这次问题的本质是 CLI 接口的破坏性变更。思源团队在 v3.7.0 做了正确的架构决策(引入子命令架构),也写了文档,但破坏性变更对已有部署的影响总是最大的。好在这个修复极其简单 —— 多写一个 serve,7 个字符。
从运维角度看,这个案例完美展示了 “日志优先"排查方法论的价值:不要猜、不要试、不要急着回滚 —— 先看日志,日志会告诉你一切。
如果你也遇到了类似问题,希望这篇文章能帮你少走 15 分钟的弯路。