WikiDocs 2.0 Docker Compose 部署指南
目标:在一台 Linux 服务器上用 Docker Compose 从 0 到 1 部署 WikiDocs(Wiki|Docs)2.0.2 无数据库 Markdown Wiki,覆盖镜像选型、数据目录权限、服务编排、初始化向导、public/private 模式切换、升级与备份,并沉淀部署全程踩到的 5 个真实故障的定位与修复过程。已在 Ubuntu 26.04.1 LTS + Docker 29
WikiDocs 2.0 Docker Compose 部署指南
目标:在一台 Linux 服务器上用 Docker Compose 从 0 到 1 部署 WikiDocs(Wiki|Docs)2.0.2 无数据库 Markdown Wiki,覆盖镜像选型、数据目录权限、服务编排、初始化向导、public/private 模式切换、升级与备份,并沉淀部署全程踩到的 5 个真实故障的定位与修复过程。已在 Ubuntu 26.04.1 LTS + Docker 29.9.2 + Compose v5.6.1 环境实测通过。
一、环境信息
本文所有命令与回显均在以下真实环境逐条实测,读者在相近环境可按原文复现。
- 操作系统:Ubuntu 26.04.1 LTS(内核 7.0.0-38-generic)
- Docker:29.9.2(引擎)+ Docker Compose v5.6.1(插件)
- 应用版本:WikiDocs 2.0.2(官方镜像
zavy86/wikidocs:2,镜像体积 411MB) - 后端运行时:Node.js v22.23.4(应用自报,见初始化后
/api/information回显) - 服务端口:宿主机 8132 → 容器内 3210(3210 为 2.0 系列官方默认监听端口)
- 数据目录:
/opt/wikidocs/datasets(挂载到容器内/var/lib/wikidocs/datasets) - 部署目录:
/opt/wikidocs(存放docker-compose.yml)
💡 说明:WikiDocs 2.0 由 NestJS 后端 + Angular Web 客户端构成,单个 Docker 镜像同时提供 Web 界面与 REST API,无外部数据库依赖,数据集就是一个目录。选型背景与版本线说明见 《WikiDocs 介绍与选型指南》。
二、镜像选型说明
官方在 Docker Hub 发布 zavy86/wikidocs,README 明确「Running Wiki|Docs in a container is the recommended way to host it yourself」(容器自托管是官方推荐方式)。标签选择策略如下:
| 标签 | 内容 | 适用场景 |
|---|---|---|
2 | 2.0 系列最新构建(本文实测时对应 2.0.2) | 常规部署,自动获得 2.x 修订 |
2.0.2 | 锁定到具体版本 | 生产环境版本锁定 |
1.x 系列 / legacy | 旧 PHP 实现 | 存量 1.x 迁移过渡,不建议新部署 |
本文采用 zavy86/wikidocs:2 并实测其元数据:
docker image inspect zavy86/wikidocs:2 --format '{{json .Config.Env}}'
["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","NODE_VERSION=22.23.4","YARN_VERSION=1.22.22","NODE_ENV=production","MODE=public","PORT=3210","FRONTEND=/app/frontend","SECRET=wikidocs-insecure-secret-to-be-changed","DATASETS=/var/lib/wikidocs/datasets"]
docker image ls zavy86/wikidocs --format '{{.Repository}}:{{.Tag}} {{.Size}}'
zavy86/wikidocs:2 411MB
镜像内置 5 个可覆盖的环境变量(MODE/SECRET/PORT/DATASETS/FRONTEND),这正是第四章环境变量表的实测依据;SECRET 默认值 wikidocs-insecure-secret-to-be-changed 明确提示必须替换。
三、前置条件
- 服务器已安装 Docker 引擎与 Compose 插件(验证命令与回显):
docker compose version
Docker Compose version v5.6.1
- 规划并确认宿主机端口未被占用。WikiDocs 容器内固定监听 3210,对外映射端口按需选择,本文用 8132。先查占用再定端口:
ss -ltn | grep -E ':(8132|3210)\b' || echo '端口空闲'
端口空闲
- 创建数据目录并预置属主。这是本组件部署的第一个关键点:容器内进程以
node用户(uid=1000)运行,宿主数据目录若属 root,容器启动会因数据目录不可写而无限重启(故障实录见第七章 7.1):
mkdir -p /opt/wikidocs/datasets && chown -R 1000:1000 /opt/wikidocs/datasets
ls -ld /opt/wikidocs/datasets
drwxr-xr-x 2 1000 1000 4096 Oct 30 14:45 /opt/wikidocs/datasets
- 拉取镜像(网络不佳时可配置镜像加速或代理后再拉):
docker pull zavy86/wikidocs:2
2: Pulling from zavy86/wikidocs
d4d21753bd21: Pull complete
bf077dfaf22d: Pull complete
Digest: sha256:a40eb007b1841aed43b8892341965edbd14f589f2b0c4a4fb65ccf6cd6497f0a
Status: Downloaded newer image for zavy86/wikidocs:2
docker.io/zavy86/wikidocs:2
四、部署步骤
4.1 编写 docker-compose.yml
在 /opt/wikidocs 下创建编排文件。name: 显式指定 Compose 项目名,避免与服务器上其他 Compose 栈同名互相干扰:
name: wikidocs-2.0.2-stack
services:
wikidocs:
image: zavy86/wikidocs:2
container_name: wikidocs-2.0.2-web
environment:
MODE: private
SECRET: q1w2e3r4-instance-secret-key
PORT: "3210"
volumes:
- /opt/wikidocs/datasets:/var/lib/wikidocs/datasets
ports:
- "8132:3210"
restart: unless-stopped
逐项说明:
MODE: private:私有模式,所有页面需登录访问;对外公开的知识库改为public(游客免登录只读),详见 4.5;SECRET:登录令牌(JWT)的签名密钥,必须替换为随机长串,绝不能沿用镜像默认值wikidocs-insecure-secret-to-be-changed;PORT:容器内监听端口,保持官方默认 3210 即可,对外暴露靠ports映射;volumes:把宿主数据目录挂载到容器内数据集位置,所有文档、账号、设置都落在这个目录;restart: unless-stopped:服务器重启后自动拉起。
⚠️ 注意:项目名
wikidocs-2.0.2-stack中的点号会被 Compose 规范化剥掉,实际生成的网络名为wikidocs-202-stack_default,docker compose down等命令在部署目录内执行即可自动对上,无需手工处理。
4.2 启动服务
cd /opt/wikidocs && docker compose up -d
Container wikidocs-2.0.2-web Created
Container wikidocs-2.0.2-web Starting
Container wikidocs-2.0.2-web Started
docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
wikidocs-2.0.2-web zavy86/wikidocs:2 "docker-entrypoint.s…" wikidocs 13 minutes ago Up 13 minutes 0.0.0.0:8132->3210/tcp, [::]:8132->3210/tcp
PORTS 列显示宿主 8132 已正确映射到容器 3210。
4.3 观察启动日志
docker compose logs --tail 8 wikidocs
输出节选(实测首尾):
wikidocs-2.0.2-web [Nest] 1 - 10/30/2026, 6:45:28 AM LOG [RouterExplorer] Mapped {/api/document, GET} route
wikidocs-2.0.2-web [Nest] 1 - 10/30/2026, 6:45:28 AM LOG [RouterExplorer] Mapped {/api/attachment, POST} route
wikidocs-2.0.2-web [Nest] 1 - 10/30/2026, 6:45:28 AM LOG [RouterExplorer] Mapped {/api/trash, DELETE} route
wikidocs-2.0.2-web [Nest] 1 - 10/30/2026, 6:45:28 AM LOG [NestApplication] Nest application successfully started
wikidocs-2.0.2-web [Nest] 1 - 10/30/2026, 6:45:28 AM LOG [Bootstrap] Wiki|Docs is listening on http://[::1]:3210
看到 Nest application successfully started 与 listening on 即启动完成。日志里 http://[::1]:3210 只是日志展示格式,服务实际监听 :::3210(全部接口,IPv4/IPv6 双栈),不要据此误判为仅环回可用——端口映射后的宿主访问是正常的(实测见 4.4)。
⚠️ 注意:若日志反复刷
FATAL [Bootstrap] DATASETS directory </var/lib/wikidocs/datasets> is not writable并无限重启,是数据目录属主问题,处理方法见第七章 7.1。
4.4 HTTP 探活验证
curl -s -o /dev/null -w 'GET / -> %{http_code}\n' http://127.0.0.1:8132/
curl -s -o /dev/null -w 'GET /api/ -> %{http_code}\n' http://127.0.0.1:8132/api/
curl -s -o /dev/null -w 'GET /api/health -> %{http_code}\n' http://127.0.0.1:8132/api/health
GET / -> 200
GET /api/ -> 200
GET /api/health -> 204
三个探针全部通过:/ 是 Web 客户端,/api/ 是 Swagger 文档页,/api/health 是健康检查端点(无内容、204 即健康)。
4.5 首次初始化(建管理员)
首次访问 http://<服务器IP>:8132/ 时 Web 客户端会引导完成站点初始化;也可以直接调初始化接口(与向导等效),管理员账号按演示规范使用 admin@lab.local / q1w2e3r4:
curl -s -X POST http://127.0.0.1:8132/api/initialize \
-H 'Content-Type: application/json' \
-d '{"title":"Lab Wiki","account":"admin@lab.local","firstname":"Admin","lastname":"User","password":"q1w2e3r4"}'
返回 201(无响应体)。初始化成功后数据目录会落盘四个文件:
ls -la /opt/wikidocs/datasets/
-rw-r--r-- 1 1000 1000 240 Oct 30 14:50 settings.json
-rw-r--r-- 1 1000 1000 208 Oct 30 14:50 accounts.json
-rw-r--r-- 1 1000 1000 3 Oct 30 14:50 pinned.json
drwxr-xr-x 4 1000 1000 4096 Oct 30 14:50 documents
drwxr-xr-x 2 1000 1000 4096 Oct 30 14:50 trash
初始化是否完成可用公开端点复核(initialized 字段):
curl -s http://127.0.0.1:8132/api/information
{"mode":"private","initialized":true,"service":"wikidocs-backend","version":"2.0.2","host":"10.89.58.2","platform":"linux","engine":"v22.23.4","pid":1,"uptime":157,"memory":38514688}
⚠️ 注意:初始化接口只能成功调用一次,重复调用返回 400(实例已初始化);入参邮箱格式不合法时同样返回 400,实测报错回显为
{"message":["account must be an email"],"error":"Bad Request","statusCode":400}。私钥、管理员邮箱与密码务必在初始化时确定,后续修改走应用内设置页。
4.6 登录验证
用初始化的管理员账号换取访问令牌,验证账号体系已生效:
curl -s -X POST http://127.0.0.1:8132/api/authenticate \
-H 'Content-Type: application/json' \
-d '{"account":"admin@lab.local","password":"q1w2e3r4"}'
输出节选(实测首尾):
{"jwt":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkdXJhdGlvbiI6OTAwLCJnZW5lcmF0aW9uIjoiMjAyNi0xMC0zMFQwNjo1MTowOC40MTZaIiwiZXhwaXJhdGlvbiI6IjIwMjYtMTAtMzBUMDc6MDY6MDguNDE2WiIsImFjY291bnQiOiJhZG1pbkBsYWIubG9jYWwiLCJyb2xlIjoiYWRtaW5pc3RyYXRvciIsImF1dGhvcml6YXRpb25zIjpbInJlYWQiLCJ3cml0ZSIsImRlbGV0ZSIsInN5bmMiLCJtYW5hZ2UiXSwiaWF0IjoxNzkzMzQzMDY4fQ.rDzLBd…"}
令牌有效期 900 秒(15 分钟),携带 read/write/delete/sync/manage 全量授权。浏览器访问 http://<服务器IP>:8132/ 用同一账号登录即可开始写作。
4.7 模式切换(private ↔ public)
私有模式下游客访问接口被拒(实测回显):
curl -s http://127.0.0.1:8132/api/guest
{"message":"Guest access is enabled only in public mode","error":"Unauthorized","statusCode":401}
要开放公开只读浏览,修改 docker-compose.yml 中 MODE: public 后重建服务:
cd /opt/wikidocs && docker compose up -d
Container wikidocs-2.0.2-web Recreate
Container wikidocs-2.0.2-web Recreated
Container wikidocs-2.0.2-web Started
复核模式与游客令牌(public 模式下游客免登录领取只读令牌,有效期 86400 秒):
curl -s http://127.0.0.1:8132/api/information | head -c 120; echo
curl -s http://127.0.0.1:8132/api/guest | head -c 120; echo
{"mode":"public","initialized":true,"service":"wikidocs-backend","version":"2.0.2"
{"jwt":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkdXJhdGlvbiI6ODY0MDAsImdlbmVyYXRpb24iOiIyMDI2LTEwLTMwVDA2OjU1OjMyLjMxMloiLCJhY2NvdW50IjoiZ3Vlc3Qi…
⚠️ 注意:
MODE=public docker compose up -d这种「命令行塞环境变量」的方式不会覆盖 compose 文件里的environment:值(实测/api/information仍返回private),必须直接修改docker-compose.yml后再up -d。
五、数据持久化说明
WikiDocs 是无数据库文件型应用,/opt/wikidocs/datasets 就是全部状态的存放处,结构如下(初始化 + 建页后的实测布局):
datasets/
├── settings.json # 站点设置(标题/语言/主题色等 9 字段)
├── accounts.json # 账号表(密码为 sha256 摘要)
├── pinned.json # 置顶文档列表
├── documents/ # 文档树:每页一个目录,content.md 为正文
│ └── guides/quick-start/
│ ├── content.md
│ ├── note.txt # 附件与正文同目录
│ └── _versions/1793343178711.md # 版本快照(毫秒时间戳命名)
└── trash/ # 回收站(删除页带毫秒时间戳前缀存放)
备份与恢复因此非常直接——备份即打包目录,恢复即解包还原:
tar czf /opt/backup/wikidocs-datasets-$(date +%F).tar.gz -C /opt/wikidocs datasets
升级应用版本不影响该目录(2.0.2 官方迁移说明明确数据文件完全兼容),建议升级前照上例做一次快照。设置项与账号文件的字段级说明见 《WikiDocs 2.0 配置参数详解》。
六、环境变量表
镜像可识别的全部环境变量(依据镜像元数据与官方 Compose 样例,默认值实查):
| 变量 | 默认值 | 取值与作用 |
|---|---|---|
MODE | public | local(桌面端本地形态)/ private(登录访问)/ public(游客免登录只读)三态;决定游客令牌端点是否可用 |
SECRET | wikidocs-insecure-secret-to-be-changed | 登录令牌签名密钥,生产必须改为随机长串;更换后所有已发令牌失效 |
PORT | 3210 | 容器内监听端口;改端口需同步调整 ports 映射右值与容器内探活地址 |
DATASETS | /var/lib/wikidocs/datasets | 数据集根目录;仅当自定义卷挂载点时同步调整 |
FRONTEND | /app/frontend | 前端静态资源目录,镜像内已固定,无需修改 |
模式语义补充(实测结论):public 模式下 /api/guest 可匿名领取 24 小时只读令牌;private 模式下同一端点返回 401(回显见 4.7)。local 供桌面应用本地形态使用,服务器部署不使用该值。
七、常见问题(FAQ)
以下 5 个问题全部是本次部署实测踩到并解决的真实故障,现象、定位、修复均给出原始回显。
7.1 启动即崩溃重启:DATASETS directory is not writable
现象:容器反复重启,日志连续刷同一行 FATAL:
[Nest] 1 - 10/30/2026, 6:45:01 AM FATAL [Bootstrap] DATASETS directory </var/lib/wikidocs/datasets> is not writable
定位:容器进程以 node(uid=1000)运行,而宿主数据目录由 root 创建(drwxr-xr-x root root),容器内无写权限:
docker inspect wikidocs-2.0.2-web --format 'User: [{{.Config.User}}]'
User: [node]
docker run --rm --entrypoint sh zavy86/wikidocs:2 -c 'id'
uid=1000(node) gid=1000(node) groups=1000(node),1000(node)
修复:宿主上把数据目录属主改为 1000:1000 后重启服务:
chown -R 1000:1000 /opt/wikidocs/datasets
cd /opt/wikidocs && docker compose restart
复核容器转为稳定运行(docker compose ps 状态 Up,日志出现 Nest application successfully started)。
7.2 端口映射后 curl 得到空回复
现象:服务日志正常,但宿主机 curl http://127.0.0.1:8132/ 返回码 000(Empty reply from server)。
定位:用 ss -ltnp 查该端口的监听进程,发现 8130 已被服务器上另一个常驻进程占用(本例为 IDE 组件):
LISTEN 0 511 127.0.0.1:8130 0.0.0.0:* users:(("trae-cn",pid=49440,fd=70))
端口被抢占导致请求打到了别的进程上。
修复:换一个 ss -ltn 确认空闲的端口(本文最终用 8132),改 docker-compose.yml 的 ports 后 docker compose up -d 重建。教训:端口预检要在启动前后各做一次——启动前查规划端口是否空闲,启动后用 docker compose ps 核对映射确实落在预期端口。
7.3 命令行注入的 MODE 环境变量不生效
现象:执行 MODE=public docker compose up -d 后 /api/information 仍返回 "mode":"private"。
定位与修复:Compose 文件 environment: 段的值优先于命令行 Shell 变量;Shell 变量只能填充 docker-compose.yml 里的 ${VAR} 占位符。直接修改文件中的 MODE: 值再 up -d 重建即可(实测切换过程回显见 4.7)。
7.4 日志显示监听 [::1] 是否只能本机访问
现象:日志出现 Wiki|Docs is listening on http://[::1]:3210,担心仅绑定环回。
结论:这是 Nest 日志的展示格式。进入容器内核实实际监听地址:
docker exec wikidocs-2.0.2-web netstat -ltn
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 :::3210 :::* LISTEN
:::3210 表示监听全部接口(IPv4/IPv6 双栈),宿主经端口映射访问正常(4.4 实测 200/204)。
7.5 启动日志出现 Unable to refresh latest release: fetch failed
现象:启动后偶见:
[Nest] 1 - 10/30/2026, 6:45:33 ERROR [ReleaseService] Unable to refresh latest release: fetch failed
结论:应用会到 GitHub 检查最新版本号,服务器无外网(或出网受限)时该检查失败并打告警日志,不影响 Wiki 功能;网络恢复后 GET /api/release 会正常返回当前与最新版本(实测 {"current":"2.0.2","latest":"2.0.2"})。离线环境可忽略该告警。
八、参考链接
- 官方自托管文档(README Setup 章节):https://github.com/Zavy86/WikiDocs#self-hosted
- Docker Hub 镜像页:https://hub.docker.com/r/zavy86/wikidocs
- 官方 docker-compose 样例:https://github.com/Zavy86/WikiDocs/blob/main/docker/compose.yml
- 本系列文档:《WikiDocs 介绍与选型指南》、《WikiDocs 2.0 配置参数详解》、《WikiDocs 2.0 API 使用详解》
- 同类组件部署实测:《Otter Wiki Docker Compose 部署指南》、《Ubuntu 22.04 安装 WiKiss 0.3 指南(PHP 8.1 兼容修复实录)》