Otter Wiki Docker Compose 部署指南

Otter Wiki 2.26.1 Docker Compose 从零部署实录:镜像标签选型与摘要核对、编排文件逐项说明、首启初始化与管理员注册、app-data 三件套持久化与备份、核心环境变量与配置优先级实测、升级运维与 6 个高频 FAQ 排障。

Otter WikiOtter WikiWiki2026/10/30

Otter Wiki Docker Compose 部署指南

目标:在一台 Linux 服务器上用 Docker Compose 从零部署一套 Otter Wiki 2.26.1(Python/Flask、数据存 git 仓库的 Markdown Wiki),覆盖镜像选型、编排文件、首启初始化、管理员注册、数据持久化与备份、核心环境变量、升级与常用运维命令。全部命令与回显已在下述环境逐条实测通过。

一、环境信息

  • 操作系统:Ubuntu 26.04.1 LTS(x86_64,内核 7.0.0-38-generic)
  • Docker:29.9.2;Docker Compose:v5.6.1(v2 命令行语法)
  • Otter Wiki:2.26.1(GitHub Releases 最新稳定版,官方镜像 redimp/otterwiki:2,构建标识 v2.26.1-0-gbb1aef4,2026-10-29 实查)
  • 部署形态:Docker Compose(Otter Wiki 官方唯一主推的部署方式,官方文档 Installation 页以 docker-compose.yml 为标准路径)
  • 本次实测部署参数:项目目录 /opt/otterwiki/,Compose 项目名 otterwiki-2.26.1-stack,宿主端口 8137 → 服务内 80,数据目录 /opt/otterwiki/app-data/
  • 演示账号:admin@otterwiki.local / q1w2e3r4
  • 官方文档实查日期:2026-10-29(otterwiki.com/Installation、otterwiki.com/Configuration、github.com/redimp/otterwiki README)

二、镜像选型说明

Otter Wiki 官方维护 Docker Hub 镜像 redimp/otterwiki,可用标签分三类,选型前先弄清浮动与固定的差别:

标签含义适用场景
2跟随 2.x 最新版的浮动标签官方 compose 示例默认;升级只需 pull
2.26.1精确版本固定标签生产锁定版本、可复现部署
latest跟随最新发布(含未来大版本)不建议,大版本升级可能有破坏性变更

💡 技术拓展:Docker 镜像标签策略常见三种——浮动标签(2/latest,自动获更新但版本不可控)、精确固定(2.26.1,可复现但升级要改文件)、摘要固定(@sha256:...,最强不可变但可读性差)。本文实际采用官方默认的 redimp/otterwiki:2 并配合文中记录的版本核对命令,兼顾官方路径与版本可追溯。

镜像元数据与标签一致性(2026-10-29 实测):

docker images --digests redimp/otterwiki --format '{{.Tag}}\t{{.ID}}\t{{.Digest}}'

实测回显:

2	8728fb977194	sha256:8728fb97719414be4790bf8e9eabc345d794876dcdf55dddd164fc9eb8e0cbcb
2.26.1	8728fb977194	sha256:8728fb97719414be4790bf8e9eabc345d794876dcdf55dddd164fc9eb8e0cbcb

:2 与 :2.26.1 镜像 ID、仓库摘要完全一致(约 680 MB,amd64/linux),即当日 :2 浮动标签恰好指向 2.26.1。镜像内部组件可通过启动日志与版本命令核实——官方镜像以内置 nginx(1.22.1)反代 uWSGI 方式运行 Flask 应用,由 supervisord 托管(PID 1),元数据声明 EXPOSE 80 8080 两个端口。

⚠️ 注意::2 属浮动标签,上游发布新版本后重新 pull 会前移。生产若要求版本严格锁定,把 compose 中 image: 改为 redimp/otterwiki:2.26.1 即可,其余配置不变。

三、前置条件

  1. 已安装 Docker(24+ 均可)与 Docker Compose v2 命令行,验证:
docker --version && docker compose version

实测回显:

Docker version 29.9.2, build a82e528
Docker Compose version v5.6.1
  1. 宿主端口 8137 未被占用(可换成 8128–8152 任意空闲端口):
ss -ltn | grep 8137 || echo '8137 free'

实测回显:

8137 free
  1. 无需外部数据库与外部 git 服务:Otter Wiki 内置 SQLite(用户/偏好)与 git(页面版本库),数据全部落在一个数据目录。
  2. 无需预装 git 之外的其他组件;数据目录所在分区预留至少 1 GB。

四、部署步骤

4.1 创建项目目录与编排文件

新建项目目录并写入 docker-compose.yml。项目名通过顶层 name: 显式固定为 otterwiki-2.26.1-stack(避免多套部署共处一机时项目重名互相覆盖):

mkdir -p /opt/otterwiki && cd /opt/otterwiki

/opt/otterwiki/docker-compose.yml:

name: otterwiki-2.26.1-stack
services:
  otterwiki:
    image: redimp/otterwiki:2
    container_name: otterwiki-2.26.1-server
    restart: unless-stopped
    ports:
      - "8137:80"
    environment:
      - TZ=Asia/Shanghai
      - SITE_NAME=Ops Wiki
      - SITE_DESCRIPTION=Demo wiki powered by python, markdown and git
      - GIT_WEB_SERVER=True
    volumes:
      - ./app-data:/app-data
    networks:
      - otterwiki-net

networks:
  otterwiki-net:
    driver: bridge
    ipam:
      config:
        - subnet: 10.89.56.0/24

逐项说明:

  • name::Compose 项目名,决定网络/资源命名前缀,多实例隔离必配。
  • image:官方镜像,标签策略见第二节。
  • container_name:固定实例名,便于 docker exec 运维命令引用。
  • ports "8137:80":宿主 8137 映射服务内 80(nginx 入口);换端口只改宿主侧。
  • environment:TZ 时区(影响新建提交时间显示,见十一、问题 2);SITE_NAME/SITE_DESCRIPTION 站点名与副标题;GIT_WEB_SERVER=True 开启 git over HTTP 远端(详见《Otter Wiki 2.26.1 常用命令与页面语法详解》)。
  • volumes ./app-data:/app-data:数据持久化,见第五节。
  • networks:显式自定义网桥与网段,便于多套服务做网段规划;内网无规划诉求可删去 networks: 两段,使用默认网络。

4.2 启动并确认健康状态

docker compose up -d

实测回显(首次部署):

 Container otterwiki-2.26.1-server Creating
 Container otterwiki-2.26.1-server Created
 Container otterwiki-2.26.1-server Starting
 Container otterwiki-2.26.1-server Started

镜像内置健康检查(访问 /-/healthz),约 10 秒后确认:

docker compose ps --format 'table {{.Name}}\t{{.Status}}\t{{.Ports}}'

实测回显:

NAME                      STATUS                    PORTS
otterwiki-2.26.1-server   Up 15 seconds (healthy)   8080/tcp, 0.0.0.0:8137->80/tcp, [::]:8137->80/tcp

状态列为 Up ... (healthy) 即部署成功。

4.3 首次启动日志验证

docker compose logs --tail 12

实测回显(节选):

otterwiki-2.26.1-server  | *** Starting An Otter Wiki 2.26.1 v2.26.1-0-gbb1aef4
otterwiki-2.26.1-server  | [2026-10-29 23:16:00,042] INFO in server: server: Created initial page /home.
otterwiki-2.26.1-server  | WSGI app 0 (mountpoint='') ready in 1 seconds on interpreter 0x721b24467af8 pid: 17 (default app)
otterwiki-2.26.1-server  | 2026-10-29 23:16:01,107 INFO success: nginx entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
otterwiki-2.26.1-server  | 2026-10-29 23:16:01,107 INFO success: uwsgi entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
otterwiki-2.26.1-server  | 127.0.0.1 - - [29/Oct/2026:23:16:03 +0800] "GET /-/healthz HTTP/1.1" 200 2 "-" "docker-healthcheck"

三个关键信息:应用版本行(2.26.1 v2.26.1-0-gbb1aef4,与所选镜像标签一致)、Created initial page /home.(首启自动在 git 仓库中创建首页)、nginx/uWSGI 进入 RUNNING 且健康检查 200。

HTTP 层再验证一次(首页与注册页均应 200):

curl -s -o /dev/null -w 'home:%{http_code}\n' http://127.0.0.1:8137/
curl -s -o /dev/null -w 'register:%{http_code}\n' http://127.0.0.1:8137/-/register

实测回显:

home:200
register:200

4.4 注册管理员账号

浏览器访问 http://<服务器IP>:8137/,点击右上角 Register(或直达 /-/register)注册第一个账号。第一个注册的账号自动成为管理员,拥有 Settings 设置页权限。本文演示环境注册的账号为:

  • 邮箱:admin@otterwiki.local
  • 密码:q1w2e3r4

注册成功后以该账号登录,右上角出现 Settings 入口即管理员身份生效。若服务器在内网无法用浏览器,也可先用默认权限(匿名可读写)直接编辑页面,再按十一、问题 1 收紧权限。

五、数据持久化说明

全部持久化数据集中在挂载进 /app-data 的一个目录(本文为 /opt/otterwiki/app-data/),首启后结构如下:

ls -la /opt/otterwiki/app-data/

实测回显:

total 60
drwxr-xr-x 3 www-data www-data  4096 Oct 29 23:16 .
drwxr-xr-x 3 root     root      4096 Oct 29 23:15 ..
-rw-r--r-- 1 www-data www-data 40960 Oct 29 23:16 db.sqlite
drwxr-xr-x 3 www-data www-data  4096 Oct 29 23:16 repository
-rw-r--r-- 1 www-data www-data   188 Oct 29 23:15 settings.cfg
路径内容说明
/app-data/db.sqliteSQLite 用户库注册用户、偏好设置(Settings 界面保存的配置存在这里)
/app-data/repository/git 版本库所有页面与附件,home.md 等工作树文件 + .git/ 版本历史
/app-data/settings.cfg应用配置首启自动生成,见第八节

版本库本身是非裸 git 仓库(工作树与 .git 同在,默认分支 main),可用宿主机 git 直接浏览版本历史:

git -C /opt/otterwiki/app-data/repository branch --show-current
git -C /opt/otterwiki/app-data/repository log --oneline

实测回显:

main
4e55fd2 Initial commit

💡 技术拓展:git 仓库分「裸仓库」(只有版本历史,无工作树,通常作远端)与「非裸仓库」(历史 + 工作树)。Otter Wiki 页面数据用非裸仓库,页面文件可直接 ls 看见;其 .git/config 中由应用自动写入 receive.denyCurrentBranch = updateInstead,使外部推送能同步更新工作树(见《Otter Wiki 2.26.1 常用命令与页面语法详解》实测)。

备份:整体打包数据目录即可(先 docker compose stop 更稳妥,避免打包时 git 正在写入):

cd /opt/otterwiki && tar czf /backup/otterwiki-$(date +%F).tar.gz app-data/

异地备份:因数据本质是 git 仓库,可用 git clone(或开启 GIT_WEB_SERVER 后 git fetch)把整个版本库克隆到远端机器,SQLite 中的用户数据另行拷贝 db.sqlite。恢复时解包 tar 到 app-data/ 再启动即可。

六、环境变量表

Otter Wiki 的可配置项约 70 个(全部键见官方 Configuration 页与《Otter Wiki 2.26.1 配置参数详解》),均可用环境变量注入。部署最常用的核心子集如下(默认值实测自官方镜像 2.26.1):

环境变量默认值说明
TZUTC时区;设 Asia/Shanghai 后新建提交按北京时间记录显示
SITE_NAMEAn Otter Wiki站点名,显示在每页标题与邮件
SITE_DESCRIPTION无站点副标题
SITE_LOGO无Logo 图片 URL(也可用附件路径)
READ_ACCESSANONYMOUS读权限:ANONYMOUS / REGISTERED / APPROVED
WRITE_ACCESSANONYMOUS写权限,取值同上
ATTACHMENT_ACCESSANONYMOUS附件上传权限,取值同上
AUTO_APPROVALTrue注册自动批准(配合 APPROVED 权限档)
DISABLE_REGISTRATIONFalse关闭自助注册(账户交由管理员创建)
GIT_WEB_SERVERFalse开启 git over HTTP 克隆/推送端点
COMMIT_MESSAGEREQUIRED保存页面时提交说明是否必填(REQUIRED/OPTIONAL/DISABLED)
PUID / PGID33 / 33数据目录属主 uid/gid(默认 www-data)
NGINX_MAX_UPLOAD0nginx client_max_body_size,0 为不限;如 10m 限制 10 MB
NGINX_WORKER_PROCESSES1nginx worker 数

⚠️ 注意:默认三个权限均为 ANONYMOUS,即匿名访客可以读写页面与上传附件,公网部署必须先收紧(见十一、问题 1)。

七、配置参数说明

同一配置项存在三个入口,生效优先级为:Settings 界面(存数据库)> 环境变量 > settings.cfg。以 SITE_NAME 为例:先按第四节以环境变量 SITE_NAME=Ops Wiki 启动,再在 Settings 界面把站点名改为其他值并保存,重启后日志会明确提示覆盖关系:

docker compose logs 2>&1 | grep -i overridden

实测回显:

[2026-10-29 23:12:57,199] WARNING in server: server: app.config["SITE_NAME"] set via environment variable is overridden by the database preference

结论:Settings 界面改过的项以数据库为准,环境变量仅对「界面从未改过」的项生效。因此推荐分工——环境变量管部署期基线(端口类、权限档、Git 开关),界面管运营期微调(站名、Logo、侧栏)。

环境变量的值类型由应用按默认值类型自动转换:布尔型接受 true/yes/on/1(大小写不敏感),整型传入非数字会被忽略并回退默认值并打 WARNING 日志。

八、配置文件挂载

/app-data/settings.cfg 在首次启动时由镜像 entrypoint 自动生成(已实测生成内容如下,SECRET_KEY 为每次安装随机生成的 64 位十六进制串):

DEBUG = False
REPOSITORY = '/app-data/repository'
SECRET_KEY = '2a177f11a9ad10d58204a807c2d5f069e72bb30d3d4273a863c107173cd82372'
SQLALCHEMY_DATABASE_URI = 'sqlite:////app-data/db.sqlite'
  • SECRET_KEY 用于会话 Cookie 签名,随机生成且必须保密;数据目录已随 tar 备份一同保留。
  • REPOSITORY 指向 git 版本库路径,SQLALCHEMY_DATABASE_URI 指向 SQLite;两者与数据目录布局对应,除非迁移数据库(如换 PostgreSQL),一般不改。
  • 如需手工预置配置(例如部署前就写好邮件参数),可在项目目录准备 settings.cfg 并增加一行挂载 ./settings.cfg:/app-data/settings.cfg——文件已存在时 entrypoint 跳过生成,直接使用挂载版本。注意其优先级低于环境变量与 Settings 界面。
  • 数据库切换示例(切换前备份数据目录):SQLALCHEMY_DATABASE_URI = 'postgresql://user:pass@db-host/otterwiki'(需可达的 PostgreSQL 实例)。

九、等价 docker run

不用 Compose 的最小等价启动命令:

docker run -d --name otterwiki-2.26.1-server \
  -p 8137:80 \
  -e TZ=Asia/Shanghai \
  -e SITE_NAME=Ops Wiki \
  -v /opt/otterwiki/app-data:/app-data \
  redimp/otterwiki:2

与 Compose 方案的差异:docker run 没有 name: 项目名与自定义网络编排,升级/重建全靠手工 docker stop/rm;数据目录同样挂 /app-data,两种方式可共用同一数据目录(不要同时运行两个实例指向同一目录)。除一次性验证外建议统一用 Compose。

十、常用运维命令

均在 /opt/otterwiki/(compose 文件所在目录)下执行:

# 状态与健康检查
docker compose ps
# 跟踪日志(应用/访问日志都在这里)
docker compose logs -f --tail 50
# 重启
docker compose restart
# 停止与彻底移除(数据目录保留)
docker compose down

docker compose down 实测回显:

 Container otterwiki-2.26.1-server Stopped
 Container otterwiki-2.26.1-server Removing
 Container otterwiki-2.26.1-server Removed
 Network otterwiki-2261-stack_otterwiki-net Removing
 Network otterwiki-2261-stack_otterwiki-net Removed

(网络名中项目名的 . 被剥除,2.26.1 → 2261,属 Compose 资源命名规则。)

升级:浮动标签 :2 下先拉新镜像再重建实例,数据目录结构跨 2.x 兼容,升级前照例打包备份:

docker compose pull && docker compose up -d

实测回显(2026-10-29,当日已是最新,无新层下载):

 Image redimp/otterwiki:2 Pulling
 Image redimp/otterwiki:2 Pulled

若上游有新版本,Pulled 之后 up -d 会以新镜像重建实例,启动日志的版本行可确认生效版本。

容器内 CLI:用户管理走容器内 flask user 命令组,逐条实测见《Otter Wiki 2.26.1 常用命令与页面语法详解》。

十一、常见问题(FAQ)

Q1:部署完公网可访问,发现匿名访客能改页面? 默认三权限均为 ANONYMOUS(匿名可读写),这是 Otter Wiki 的出厂默认而非漏洞。收紧为仅注册用户可读写:在 compose 的 environment 增加三行后 docker compose up -d 重建:

      - READ_ACCESS=REGISTERED
      - WRITE_ACCESS=REGISTERED
      - ATTACHMENT_ACCESS=REGISTERED

实测效果:匿名访问首页被 302 到登录页,匿名 git 推送被要求认证:

302 -> http://127.0.0.1:8137/-/login?next=/?
fatal: could not read Username for 'http://127.0.0.1:8137': terminal prompts disabled

Q2:Changelog 里的提交时间与本地时间对不上? Otter Wiki 没有站点级时区设置,变更历史按每条提交自身记录的时区偏移原样显示。实测:镜像默认 UTC 时,容器内保存的页面显示为 UTC 时间(如 14:48:53);在 compose 加 TZ=Asia/Shanghai 重建后,之后容器内新建的提交即显示北京时间(实测新提交显示 23:10:30),历史提交维持各自原偏移。要让存量 UTC 时间转北京时间,只能改 git 提交数据,不建议动;实操上装好就配 TZ 即可。

Q3:改了环境变量但界面显示没变? 优先级是 Settings 界面(数据库)> 环境变量 > settings.cfg。界面上保存过的项,环境变量不再生效,启动日志会出现 overridden by the database preference 的 WARNING(见第七节回显)。处理:要么在 Settings 界面改回,要么清掉数据库里对应偏好记录(db.sqlite 的 preferences 表)。

Q4:镜像 EXPOSE 了 80 和 8080 两个端口,都要映射吗? 不用。nginx 监听 80 作为唯一对外入口;8080 是 entrypoint 自动追加的同应用直连口(listen 8080;),供容器网络内部直访调试用。官方 compose 示例也只映射 80。实测容器内监听情况:

LISTEN 0      511          0.0.0.0:8080       0.0.0.0:*
LISTEN 0      511          0.0.0.0:80         0.0.0.0:*

Q5:多用户共用服务器,想把数据目录属主改成指定用户? 用 PUID/PGID。默认 33/33(www-data),entrypoint 启动时用 usermod/groupmod 改写镜像内 www-data 的 uid/gid 再 chown -R 数据目录。注意 PGID 不能为 0,entrypoint 会直接拒绝启动,报错原文:Fatal: PGID must not be 0 (root).。设值前确认目标 gid 未被宿主镜像内其他组占用(占用时 entrypoint 会打印 Warning: Deleting group ... 并删除该组,属预期行为)。

Q6:附件上传报 413? 未显式配置时 nginx client_max_body_size 默认 0(不限);若设置过 NGINX_MAX_UPLOAD(如 10m),超限附件会被 nginx 以 413 拒绝。按需调大该变量并重建实例即可。

十二、参考链接

站内延伸阅读