LeafWiki 二进制与 Compose 部署实战

LeafWiki v0.14.2 双形态部署实测:二进制直跑与 systemd 服务化、Compose 编排(8141 端口/10.89.61.0/24 网段)、首启日志与 schema 迁移、建页/编辑/检索/权限/乐观锁/resync 功能闭环、5 项排障实录、反代 HTTPS、快照备份恢复与资源占用。

LeafWikiLeafWiki文档管理WikiDocs2026/11/01

LeafWiki 二进制与 Compose 部署实战

实测版本 v0.14.2,实测日期 2026-11-01。资源规划:宿主 Web 端口 8141,自定义网段 10.89.61.0/24(服务实例固定地址 10.89.61.10)。本文两种形态均为同一台 Ubuntu 26.04.1 LTS 服务器上的实测记录。

一、部署形态选择

LeafWiki 官方提供五条安装路径(详见《LeafWiki 介绍与选型指南》第四章):单二进制、Linux 安装脚本(systemd 服务)、Compose 编排、Kubernetes、源码构建。本文实测两条主线:

  • B1 二进制直跑:产品定位形态,leafwiki 单个静态链接 ELF(48,427,168 字节),适合裸机/虚机最小运维;
  • B2 Compose 编排:官方推荐的声明式形态,适合已有编排栈管理习惯的服务器。

两条路径产物等价:二进制与官方发布包内 /app/leafwiki 完全同源(实测从官方发布包提取后直跑验证;生产环境请从 GitHub Releases 下载 leafwiki-v0.14.2-linux-amd64 及 .sha256 校验,本实验机外网受限无法直连 Releases 下载,此为如实记录的取证边界)。

二、环境基线

项值
宿主 OSUbuntu 26.04.1 LTS(grep PRETTY /etc/os-release)
内核/架构7.0.0-38-generic x86_64(uname -rm)
编排引擎Docker version 29.9.2, build a82e528
实验目录/opt/data/quzuo/tmp/leafwiki/
端口宿主 8141 → 服务 8080
网段10.89.61.0/24,自定义网络 leafwiki-net

三、B1:二进制直跑

3.1 获取与校验

官方二进制发布物命名 leafwiki-<version>-<os>-<arch>(linux-amd64/arm64/386、darwin-amd64/arm64、windows-amd64),发布页附 .sha256 文件:

sha256sum leafwiki-<version>-<os>-<arch>
cat leafwiki-<version>-<os>-<arch>.sha256
mv leafwiki-<version>-<os>-<arch> leafwiki
chmod +x leafwiki

实测二进制为静态链接 Go ELF,无 glibc 依赖:

$ file leafwiki
leafwiki: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=..., stripped

3.2 首次启动

./bin/leafwiki --jwt-secret=bin-test-secret-0123456789abcdef --admin-password=q1w2e3r4 --allow-insecure --host=127.0.0.1 --port=8099 --data-dir ./bindata

实测启动日志(bin.log 逐行照抄):

time=2026-11-01T22:11:34.915+08:00 level=WARN source=/app/cmd/leafwiki/main.go:209 msg="allow-insecure enabled. Auth cookies may be transmitted over plain HTTP (INSECURE)."
time=2026-11-01T22:11:34.956+08:00 level=INFO source=/app/internal/core/tree/tree_service.go:63 msg="Checking schema version..." component=TreeService
time=2026-11-01T22:11:34.963+08:00 level=INFO source=/app/internal/wiki/wiki_boot.go:214 msg="search indexing started" component=Wiki
time=2026-11-01T22:11:34.963+08:00 level=INFO source=/app/internal/wiki/wiki_boot.go:285 msg="Welcome page already exists, skipping creation" component=Wiki
time=2026-11-01T22:11:34.963+08:00 level=INFO source=/app/cmd/leafwiki/main.go:464 msg="Starting LeafWiki" address=127.0.0.1:8099 data_dir=./bindata

要点:

  • 默认只绑 127.0.0.1:8080,暴露到网络需显式 --host=0.0.0.0;
  • 明文 HTTP 必须 --allow-insecure,否则登录 Cookie 不工作;
  • 首次启动自动创建 admin 用户(用户名默认 admin,口令取 --admin-password,至少 8 字符)并写入 Welcome to LeafWiki 初始页;
  • 数据目录自动完成 schema 迁移(0→5)并初始化检索索引。

验证服务与登录:

$ curl -s -o /dev/null -w 'code=%{http_code}\n' http://127.0.0.1:8099/
code=200
$ curl -s -X POST http://127.0.0.1:8099/api/auth/login -H 'Content-Type: application/json' -d '{"identifier":"admin","password":"q1w2e3r4"}'
{"accessTokenExpiresAt":1793543196,"message":"Login successful","user":{"id":"c1e2T2qvR","username":"admin","email":"admin@localhost","role":"admin","totpEnabled":false,"mustSetPassword":false}}

3.3 数据目录结构(两种形态通用)

首次运行后 ./bindata 实测布局:

bindata
bindata/tags.db-shm
bindata/avatars
bindata/search.db-shm
bindata/sessions.db-shm
bindata/tags.db-wal
bindata/tags.db
bindata/favorites.db-wal
bindata/schema.json
bindata/usersettings.db-wal
bindata/properties.db
bindata/search.db

另有 users.db、links.db、usersettings.db 等 SQLite 库(WAL 模式),页面正文在 root/ 子目录按树形层级落为 .md 文件,修订历史在 .leafwiki/revisions/<页面ID>/,快照 ZIP 在 snapshots/。元数据在 SQLite、正文在文件系统是理解 LeafWiki 持久化的关键:备份必须整目录打包,只拷 *.md 会丢权限/标签/修订。

3.4 systemd 服务化(生产形态)

官方 Linux 安装脚本(install.sh --non-interactive --env-file ./.env)即装成 systemd 服务;手工单元等价写法:

# /etc/systemd/system/leafwiki.service
[Unit]
Description=LeafWiki
After=network.target

[Service]
ExecStart=/opt/leafwiki/leafwiki --data-dir=/var/lib/leafwiki
EnvironmentFile=/etc/leafwiki/.env
User=leafwiki
Restart=on-failure

[Install]
WantedBy=multi-user.target

注意:交互模式安装脚本会把环境变量明文写入 /etc/leafwiki/.env,需收紧文件权限(官方 README 安全提示)。32 位 x86 Linux 仅二进制路径可用(无 CGO 构建兼容 musl),安装脚本与升级脚本不适用于该架构。

四、B2:Compose 编排部署

4.1 编排文件(实测全文)

# docker-compose.yml
name: leafwiki-0.14.2-stack

services:
  leafwiki:
    image: ghcr.io/perber/leafwiki:v0.14.2
    container_name: leafwiki-0.14.2-web
    restart: unless-stopped
    ports:
      - "8141:8080"
    environment:
      - LEAFWIKI_JWT_SECRET=s9-l34fw1k1-jwt-secret-2026-long-enough
      - LEAFWIKI_ADMIN_PASSWORD=q1w2e3r4
      - LEAFWIKI_ALLOW_INSECURE=true
      - LEAFWIKI_ENABLE_REVISION=true
      - LEAFWIKI_LOG_LEVEL=info
    volumes:
      - ./data:/app/data
    networks:
      leafwiki-net:
        ipv4_address: 10.89.61.10

networks:
  leafwiki-net:
    driver: bridge
    ipam:
      config:
        - subnet: 10.89.61.0/24

4.2 启动与首启日志

$ docker compose up -d
 Network leafwiki-0142-stack_leafwiki-net Created
 Container leafwiki-0.14.2-web Created
 Container leafwiki-0.14.2-web Started
$ docker compose ps
leafwiki-0.14.2-web 0.0.0.0:8141->8080/tcp, [::]:8141->8080/tcp Up 3 seconds

首启日志逐行照抄(含官方发布包入口脚本行为):

[entrypoint] Starting entrypoint script...
[entrypoint] No --host argument or LEAFWIKI_HOST environment variable found. Setting LEAFWIKI_HOST to '0.0.0.0'
[entrypoint] Executing /app/leafwiki
time=2026-11-01T14:07:24.430Z level=WARN source=/app/cmd/leafwiki/main.go:209 msg="allow-insecure enabled. Auth cookies may be transmitted over plain HTTP (INSECURE)."
time=2026-11-01T14:07:24.498Z level=INFO source=/app/internal/core/auth/user_service.go:146 msg="user created" component=UserService userID=H_sIThqDR role=admin
time=2026-11-01T14:07:24.503Z level=WARN source=/app/internal/core/auth/session_manager.go:46 msg="JWT secret is too short; a minimum of 32 characters is strongly recommended" component=SessionManager length=27
time=2026-11-01T14:07:24.544Z level=INFO source=/app/internal/core/tree/tree_service.go:63 msg="Checking schema version..." component=TreeService
time=2026-11-01T14:07:24.544Z level=INFO msg="Schema file not found, assuming version 0"
time=2026-11-01T14:07:24.544Z level=INFO source=/app/internal/core/tree/tree_service.go:106 msg="Migrating schema" component=TreeService fromVersion=0 toVersion=5
time=2026-11-01T14:07:24.544Z level=INFO msg="Saving schema version 1 to data/schema.json"
...(schema 2/3/4/5 逐版保存)
time=2026-11-01T14:07:24.568Z level=INFO source=/app/internal/wiki/wiki_boot.go:214 msg="search indexing started" component=Wiki
time=2026-11-01T14:07:24.571Z level=INFO source=/app/internal/wiki/wiki_boot.go:224 msg="search indexing completed" component=Wiki
time=2026-11-01T14:07:24.572Z level=INFO source=/app/internal/core/tree/tree_mutations.go:337 msg="updating node content" component=TreeService nodeID=89sSohqvR
time=2026-11-01T14:07:24.578Z level=INFO source=/app/cmd/leafwiki/main.go:464 msg="Starting LeafWiki" address=0.0.0.0:8080 data_dir=./data

入口脚本行为:未提供 --host/LEAFWIKI_HOST 时自动置 0.0.0.0(编排形态需要对外监听,与二进制默认 127.0.0.1 相反)。官方发布包默认以 root 运行,README 给出非特权写法:user: 1000:1000 + 数据目录对该用户可写。

五、部署后基础功能实测(两种形态一致)

以 8141 实例、示例账号 admin / q1w2e3r4 走通核心闭环(API 全链路细节见《LeafWiki REST API 实测》):

  1. 建页:POST /api/pages 传 {title,slug,parentId,kind},返回新页 RRpOT2qvR,初始内容 # Deploy Verify\n;
  2. 编辑:PUT /api/pages/{id} 传 {version,title,slug,content,tags,properties} 全字段,保存成功并返回新 version;
  3. 磁盘同步:编辑后 data/root/deploy-verify.md 实测更新,文件头带 leafwiki_id/leafwiki_title/leafwiki_created_at/leafwiki_updated_at/leafwiki_creator_id/leafwiki_last_author_id 与 tags 前置元数据,正文即 Markdown 原文;
  4. 检索:GET /api/search?q=callout 命中并高亮,返回 tags 字段;GET /api/tags 实测 [{"tag":"s9","count":1},{"tag":"verify","count":1}];
  5. 权限:POST /api/users 建 viewer 角色用户 viewer01,其登录后可读页面,但建页请求实测返回:
HTTP/1.1 403 Forbidden
{"error":"Editor or Admin role required"}
  1. 乐观锁:携带过期 version 的 PUT 实测返回 page_version_conflict(「Page was changed by another request」),并发编辑保护生效;
  2. 外部编辑:直接追加磁盘 .md 内容后 POST /api/admin/resync 返回 {"ok":true},随后 API 读取页面内容已包含外部段落(「EXTERNAL」校验通过)。

六、排障实录

#现象根因与处置
1首启日志 WARN:JWT secret is too short ... length=27实验密钥 27 字符 < 建议 32。更换 ≥32 字符密钥重建后告警消失。服务仍可运行,但生产必须满足长度建议
2PUT /api/pages/{id} 返回 page_invalid_request只传 {content,version} 不够;实测必须全字段 {version,title,slug,content,tags,properties}。从发布包内嵌前端产物反查出 payload 结构后通过
3二进制实例启动即退:Failed to start server ... bind: address already in use前一次测试进程仍占用 8099。pkill -f 'bin/leafwiki' 清理后重启成功。同一数据目录不允许双实例并发写
4直接编辑磁盘 .md 后 API 仍读旧内容LeafWiki 以 SQLite 元数据+内存索引为准,外部修改需触发 POST /api/admin/resync(管理端「外部编辑与重同步」文档行为一致)
5明文 HTTP 登录后 Cookie 不下发必须显式 --allow-insecure(或环境变量 LEAFWIKI_ALLOW_INSECURE=true);HTTPS 反代场景去掉该参数并确保转发 X-Forwarded-Proto: https

七、反向代理与 HTTPS 要点

  • 子路径挂载:--base-path=/wiki(v0.8.2+),反代保留前缀转发;
  • 反代认证:--enable-http-remote-user + --http-remote-user-header-name(默认 Remote-User)+ --trusted-proxy-ips(必填可信来源,防伪造头);可配 --enable-http-remote-user-auto-create 自动开户并指定默认角色(不得为 admin);
  • SSO 跳转:--login-url/--logout-url 替换内置登录页;
  • 生产 HTTPS:去掉 --allow-insecure,反代需透传 X-Forwarded-Proto: https。

八、备份与恢复

  • 自动快照:默认开启(--snapshot,间隔 24h,保留 10 份),实测 data/snapshots/ 生成 snapshot-20261101-140724.zip(3,287 字节,含 SQLite 全库)与同名 .json 清单;
  • 离线恢复:停服后执行,实测通过:
$ ./bin/leafwiki --data-dir ./bindata restore-snapshot bindata/snapshots/snapshot-20261101-141121.zip
Snapshot restored successfully. Start the server normally to pick up the restored data.
  • 冷备份:停服后整目录 cp -r data/(官方口径);开启 --snapshot 时管理界面 Settings → Full Backup 还支持在线恢复(恢复换文件期间写入返回 503);
  • Git 备份(实验性,v0.11.3):--git-backup --git-backup-remote=<url> 定时推送 root/ 与 assets/ 到远端仓库,默认间隔 60m。

九、升级

  • 二进制/systemd:官方 update.sh 仅适用于安装脚本部署;手工路径为下载新版本二进制 → 停服 → 替换 → 起服(schema 自动迁移,实测 0→5 逐版落 schema.json);
  • Compose:改 image 标签 → docker compose up -d,数据卷不动;
  • 升级前建议先手动触发一次快照(管理界面或等待定时快照),v0.x 大版本间行为变化需核对 Release Notes。

十、资源占用与结论

稳定运行(含检索索引、1 页面 2 用户)实测:

$ docker stats leafwiki-0.14.2-web --no-stream
leafwiki-0.14.2-web CPU=0.00% MEM=10.08MiB / 15.07GiB PIDS=11
$ du -sh data
1000K	data

约 10 MiB 常驻内存、空载零 CPU、数据目录 1 MB 内——与「单二进制 + SQLite」定位高度一致,树莓派级硬件即可承载小团队 Wiki。两条部署主线全部实测通过,无功能性阻塞;B1 的取证边界(Releases 直连下载在本实验机不可达,改用官方发布包同源二进制验证)已如实记录。

延伸阅读:《LeafWiki 配置参数详解》(全量 CLI/环境变量逐项)、《LeafWiki REST API 实测》(curl 全链路实录)。

LeafWiki 二进制与 Compose 部署实战 - Quzuo