Many Notes Docker Compose 部署指南

Many Notes v0.18.1 官方 Docker Compose 部署全流程实测:单容器全家桶(nginx+PHP-FPM+队列+Reverb+Typesense)编排、四卷持久化、首用户超管注册、健康检查、Vault/笔记/容错搜索/导出端到端验证、三类部署变体对比与排障实录。

Many NotesMany NotesDocker Compose自托管PHP2026/11/02

Many Notes Docker Compose 部署指南

目标:在一台 Linux 服务器上以官方推荐的 Docker Compose 形态从零部署 Many Notes v0.18.1,完成注册、建库、写笔记、全文搜索与导出的端到端验证。本文全部命令与回显均已在 Ubuntu 24.04 服务器(Docker 29.x + Compose v2)实测通过,采集日期 2026-11-01。

一、环境信息

  • 操作系统:Ubuntu 24.04(x86_64)
  • 应用:Many Notes v0.18.1(镜像 brufdev/many-notes:latest,实测 digest sha256:4a61723eca842b644ced386b17e189d143e97096c65ac80eadca13c6cd89a739,镜像内 composer.json 的 version 字段为 0.18.1)
  • 镜像基座:serversideup/php v5.0.2(fpm-nginx),PHP 8.4.27,Debian 13 (trixie),s6 进程监督
  • 运行时组件:nginx(HTTP 8080 / HTTPS 8443)、PHP-FPM(9000)、队列 worker(database 驱动)、Laravel Reverb WebSocket(6001)、Typesense(8108)——全部聚合在同一容器内
  • 数据库:SQLite(默认,database/sqlite/database.sqlite),可选 MySQL/MariaDB/PostgreSQL
  • 对外端口:宿主 8144 → 应用 8080(部署前用 ss -ltn 实查确认 8144 空闲)
  • 持久化目录(命名卷):database、logs、private、typesense 四卷
  • 演示账号:admin / q1w2e3r4(邮箱 admin@quzuo.net,首个注册用户自动成为超级管理员)

二、镜像选型说明

官方 Docker Hub 仓库为 brufdev/many-notes,README 及三份安装文档统一使用 :latest 标签。实测注意两点:

  1. 版本标签回退:UPGRADING.md 提到历史版本可用对应 tag 回退,但本次实测环境中 brufdev/many-notes:v0.18.1 经镜像加速通道拉取返回「不在白名单」错误、直连超时,latest 正常。生产部署建议:拉取 latest 后以 digest 锁版(brufdev/many-notes@sha256:4a6172...),升级时再换 digest。
  2. 镜像体积:docker images 实测 1.49GB——全家桶单容器(含 Typesense 二进制与完整 PHP 扩展)的代价,磁盘规划按 2GB 起。
docker pull brufdev/many-notes:latest

拉取回显(实测节选,末行为准):

ab1a6b05fcdd: Pull complete
b56223bdcbe4: Pull complete
Digest: sha256:4a61723eca842b644ced386b17e189d143e97096c65ac80eadca13c6cd89a739
Status: Downloaded newer image for brufdev/many-notes:latest
docker.io/brufdev/many-notes:latest

⚠️ 网络说明:镜像仓库官方源直连超时,实际经 Docker 守护进程配置的代理通道拉取成功;latest 标签可正常获取,版本标签经该通道不在白名单。国内源(DaoCloud 公共加速)对 latest 可用。

三、前置条件

docker --version && docker compose version

回显(实测):

Docker version 29.0.1, build 8c41158
Docker Compose version v2.39.1
ss -ltn | grep -E ':8144\b' || echo "PORT-8144-FREE"

回显(实测):

PORT-8144-FREE

规划部署目录(本文以 /opt/many-notes 为例):

mkdir -p /opt/many-notes && cd /opt/many-notes

四、部署步骤

4.1 编写 compose.yaml

采用官方推荐的命名卷形态,叠加两项生产加固:显式项目名 name:(避免多栈同名互删)与固定容器名:

name: many-notes-0.18.1-stack

services:
  php:
    image: brufdev/many-notes:latest
    container_name: many-notes-0.18.1-app
    restart: unless-stopped
    environment:
      - APP_URL=http://localhost:8144
    volumes:
      - database:/var/www/html/database/sqlite
      - logs:/var/www/html/storage/logs
      - private:/var/www/html/storage/app/private
      - typesense:/var/www/html/typesense
    ports:
      - "8144:8080"

volumes:
  database:
  logs:
  private:
  typesense:

逐项说明:

  • APP_URL:应用访问地址,默认 http://localhost。改了宿主端口必须同步改它(官方 README:Custom URL),否则资源绝对链接与 OIDC 回调都会指错地址。
  • 四个卷的挂载路径是官方固定契约:database/sqlite(SQLite 库文件)、storage/logs(应用日志)、storage/app/private(Vault 文件实体,最重要)、typesense(搜索索引数据)。漏挂 typesense 卷的后果官方 FAQ 已写明:重启后搜索无结果。
  • ports 冒号右侧 8080 是镜像内 nginx 的 HTTP 端口(NGINX_HTTP_PORT 默认值),不要改动;左侧宿主端口自选空闲值。

4.2 启动

docker compose up -d

回显(实测节选):

 Network many-notes-0181-stack_manynotes-net Created
 Volume many-notes-0181-stack_database Created
 Volume many-notes-0181-stack_typesense Created
 Container many-notes-0.18.1-app Created
 Container many-notes-0.18.1-app Started

4.3 观察启动过程

sleep 25 && docker compose ps

回显(实测):

NAME                   IMAGE                      STATUS                   PORTS
many-notes-0.18.1-app  brufdev/many-notes:latest  Up 25 seconds (healthy)  8443/tcp, 9000/tcp, 0.0.0.0:8144->8080/tcp

(healthy) 来自镜像自带健康检查(探测路径 HEALTHCHECK_PATH=/healthcheck)。启动日志完整呈现了自动初始化链(实测节选,按时间序):

📦 Versions
• Image:         v5.0.2-39644565049
• PHP:           8.4.27
• OS:            Debian GNU/Linux 13 (trixie)
...
ℹ️ NOTICE (init-webserver-config): Enabling NGINX site with SSL "off"...
🚀 Clearing Laravel cache before attempting migrations...
⚡️ Attempting connection to default database...
✅ Database connection successful
🚀 Running migrations for default database
  Creating migration table ....................................... 4.12ms DONE
  0001_01_01_000000_create_users_table ........................... 8.67ms DONE
  ...
  2024_07_16_025200_create_vaults_table .......................... 1.62ms DONE
  2024_08_16_010335_create_vault_nodes_table ..................... 1.66ms DONE
  2025_08_25_205738_remove_access_token_field_from_social_accounts_table  2.04ms DONE
🚀 Running optimize command: "php artisan optimize"...
All notes were successfully synced to disk
All starter vaults were successfully processed
The first registered user will be assigned as Super Admin
[01-Nov-2026 15:44:20] NOTICE: fpm is running, pid 165
✅ NGINX + PHP-FPM is running correctly.
   INFO  Starting server on 0.0.0.0:6001.

关键事实:建表、缓存优化、数据同步全部自动完成,无需手工执行任何安装命令;日志行 "The first registered user will be assigned as Super Admin" 预告了首用户特权规则。末行 0.0.0.0:6001 是内置 Reverb WebSocket 服务。

⚠️ 排障实录:首次启动日志中出现两行 Something went wrong reimporting existing data into Typesense。定位为初始化时序问题——数据重导入步骤先于 Typesense 进程就绪执行。该报错不影响使用,待健康检查转 healthy 后补一次官方 FAQ 给出的重导入命令即可:

docker compose exec php php artisan upgrade:reimport-data-into-typesense

回显:All data was successfully reimported into Typesense(实测)。

4.4 验证服务可用

curl -s http://localhost:8144/healthcheck; echo
curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' http://localhost:8144/

回显(实测):

OK
302 http://localhost:8144/login

/healthcheck 返回 OK 即应用就绪;根路径 302 跳转登录页为正常行为(未认证)。浏览器打开 http://<服务器IP>:8144/ 应看到 Many Notes 登录页,含 Sign up 入口。

五、首用户注册与功能验收

5.1 注册超级管理员

登录页进入 Register,用户名 admin、邮箱 admin@quzuo.net、密码 q1w2e3r4 提交。注册成功后自动进入应用,并自动生成一个 Starter Vault(官方入门模板库,含 Start Here.md 与 00-03 编号目录)。

服务器侧验证用户与角色(实测):

docker compose exec php php artisan tinker --execute="\$u=App\Models\User::first(); echo \$u->id,PHP_EOL,\$u->name,PHP_EOL,\$u->email,PHP_EOL,\$u->role->value,PHP_EOL,'vaults: ',\$u->vaults()->count(),PHP_EOL;"
1
admin
admin@quzuo.net
3
vaults: 1

role 值 3 即 SUPER_ADMIN(官方枚举 USER=1 / ADMIN=2 / SUPER_ADMIN=3),证实「首用户自动超管」。

5.2 创建 Vault 与笔记

界面上 Vaults → New vault 创建「工作笔记」,在其中新建文件夹「项目」与笔记「部署清单」,编辑器输入正文后自动保存。

用服务器侧命令验证双写特性——文件系统立即可见真实 .md:

docker compose exec php find storage/app/private/vaults -maxdepth 3 | head

回显(实测):

storage/app/private/vaults
storage/app/private/vaults/1
storage/app/private/vaults/1/Starter Vault
storage/app/private/vaults/1/Starter Vault/Start Here.md
storage/app/private/vaults/1/工作笔记
storage/app/private/vaults/1/工作笔记/项目

数据库侧同步存在节点行(vault_nodes 表),文件与库互为镜像;官方 FAQ 语义:数据库损坏时 private 卷仍是全量副本。

5.3 全文搜索(含容错)

搜索接口实测(会话认证下 GET /vaults/2/search):

GET /vaults/2/search?q=quzuo-netlab
→ 200 {"data":{"files":[{"id":17,"type":"note","name":"部署清单","content":"# 部署清单\n\nMany Notes 实测笔记:Typesense 全文检索验证关键词 quzuo-netlab。",...}]}}

故意拼错(quzonetlab,漏连字符且 o/a 错位)仍命中同一篇——typos tolerant 实测成立。

Typesense 索引核对(服务内置,端口 8108,默认 API key xyz):

docker compose exec php curl -s -H "x-typesense-api-key: xyz" http://localhost:8108/collections

回显(实测节选):

[{"name":"vault_nodes","num_documents":9,"fields":[{"name":"vault_id","type":"string"},{"name":"name","type":"string"},{"name":"content","type":"string"},{"name":"updated_at","type":"int64","sort":true}],...}]

5.4 Vault 导出

界面 Vault 菜单 Export,或会话下请求 GET /vaults/2/export,得到 ZIP(实测):

Archive:  vault-export.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
        0  2026-11-01 15:49   项目/
       95  2026-11-01 15:49   项目/部署清单.md
---------                     -------

空 Vault 导出被正确拒绝:422 {"message":"Your vault is empty"}(实测)。

六、数据持久化说明

卷容器内路径内容备份优先级
private/var/www/html/storage/app/private全部 Vault 文件实体 + 导出临时 ZIP★★★(全量数据)
database/var/www/html/database/sqliteSQLite 库(用户/树/标签/链接/会话)★★★(结构数据)
typesense/var/www/html/typesense搜索索引★(可重建:upgrade:reimport-data-into-typesense)
logs/var/www/html/storage/logslaravel.log 应用日志运维留存

💡 技术拓展:容器数据持久化不止命名卷一种。对比——

方式特点适用
命名卷(本文)由 Docker 管理生命周期,权限无坑生产默认,官方推荐
bind mount宿主路径直观可见需宿主直接读写 Vault 时
对象存储同步卷内容定时快照到 S3异地容灾

本文采用命名卷:Many Notes 镜像以非特权用户 www-data(UID 33)运行,bind mount 需额外构建步骤对齐宿主 UID/GID(见 8.2),复杂度更高。

七、环境变量表

应用级(官方 README/文档定义):

变量默认值说明
APP_URLhttp://localhost应用访问地址,改端口/域名必改
APP_TIMEZONEUTC时区(如 Asia/Shanghai)
DB_CONNECTIONsqlite可换 mariadb/mysql/pgsql
DB_HOST/DB_PORT/DB_DATABASE/DB_USERNAME/DB_PASSWORD—外置数据库连接参数
SETTINGS_LOCAL_AUTH_ENABLEDtrue关闭后仅走 OAuth/OIDC
OIDC_NAME/OIDC_CLIENT_ID/OIDC_CLIENT_SECRET/OIDC_REDIRECT_URI/OIDC_BASE_URL—单个 OIDC 提供方;v0.18.0 起多提供方用 OIDC_PROVIDERS + OIDC_<NAME>_* 前缀
OIDC_POST_LOGOUT_REDIRECT_URI—登出后跳转 IdP
MAIL_MAILER/MAIL_HOST/MAIL_PORT/MAIL_USERNAME/MAIL_PASSWORD/MAIL_ENCRYPTION/MAIL_FROM_ADDRESS/MAIL_FROM_NAMElog/127.0.0.1/2525注册与重置密码邮件;默认 log 不发信
TYPESENSE_HOST/TYPESENSE_PORT/TYPESENSE_API_KEYlocalhost/8108/xyz内置搜索引擎连接参数
REVERB_APP_ID/REVERB_APP_KEY/REVERB_APP_SECRET镜像内置生成WebSocket 广播凭据
APP_DEBUGfalse排障时临时开(官方 FAQ 提醒用完关闭)

基座级(serversideup/php 镜像,实测 docker image inspect 采集):PHP_MEMORY_LIMIT=256M、PHP_POST_MAX_SIZE=500M、PHP_UPLOAD_MAX_FILE_SIZE=500M、NGINX_HTTP_PORT=8080、NGINX_CLIENT_MAX_BODY_SIZE=100M、HEALTHCHECK_PATH=/healthcheck、SSL_MODE=off、TRUSTED_PROXY=cloudflare、GLOG_minloglevel=3(Typesense 日志级别,设 2 开调试)。全表见《Many Notes 0.18.1 配置参数详解》第五章。

八、配置文件挂载与部署变体

8.1 无需挂载应用配置

Many Notes 全部配置走环境变量 + 管理界面(注册开关、自动更新检查存于 settings 表),官方 compose 不挂载任何 .env/配置文件——这是与多数 Laravel 自托管产品的显著差异,升级镜像不会覆盖你的定制(因为定制不在文件里)。

8.2 bind mount 变体(官方文档转述)

需要宿主直接访问 Vault 目录时,官方提供 docs/installation/docker-bind-mounts.md 方案:因镜像以 www-data(UID 33)运行,需自建 Dockerfile 把宿主用户 ID 烧入构建阶段:

FROM brufdev/many-notes:latest
USER root
ARG UID
ARG GID
RUN docker-php-serversideup-set-id www-data $UID:$GID && \
    docker-php-serversideup-set-file-permissions --owner $UID:$GID --service nginx
USER www-data

compose 中 build.context: . 传 UID/GID(如 1000/1000),四目录改挂 ./database、./logs、./private、./typesense。

8.3 外置数据库变体(官方文档转述)

DB_CONNECTION=mysql(或 mariadb/pgsql)+ DB_* 五参数,compose 增加数据库服务并用 depends_on: condition: service_healthy 等待就绪;此时 database 卷不再需要。官方示例以 mysql:9 演示。

8.4 等价 docker run

docker run -d --name many-notes-0.18.1-app \
  -e APP_URL=http://localhost:8144 \
  -v manydb:/var/www/html/database/sqlite \
  -v manylogs:/var/www/html/storage/logs \
  -v manypriv:/var/www/html/storage/app/private \
  -v manyts:/var/www/html/typesense \
  -p 8144:8080 \
  --restart unless-stopped \
  brufdev/many-notes:latest

九、常用运维命令

docker compose ps                        # 状态与健康
docker compose logs -f php               # Web 请求日志(nginx 访问日志在此)
docker compose exec php tail -f storage/logs/laravel.log   # 应用错误日志(官方 FAQ 指定路径)
docker compose exec php grep -a "\.ERROR" storage/logs/laravel.log | tail -5
docker compose exec php php artisan about                  # 运行环境总览
docker compose exec php php artisan migrate --force        # 升级后手动补迁移(正常自动)
docker compose pull && docker compose up -d                # 升级(先备份 private+database 卷)
docker compose down                                      # 停止(卷保留,数据不丢)

十、常见问题

10.1 端口占用导致启动失败

现象:Bind for 0.0.0.0:8144 failed: port is already allocated。定位:ss -ltnp | grep 8144 查到占用进程。解决:换宿主端口,同时改 APP_URL 后 docker compose up -d。多实例并行部署时务必先查端口再启动。

10.2 搜索无结果

官方 FAQ 两条根因:① 未挂载 typesense 卷(重启即丢索引);② 历史数据未导入。先确认卷在位,再执行 php artisan upgrade:reimport-data-into-typesense(实测输出 All data was successfully reimported into Typesense)。

10.3 首次启动日志报 Typesense 重导入错误

见 4.3 排障实录:初始化时序导致,转 healthy 后补跑重导入即可,无需重装。

10.4 通过接口创建节点返回 500

实测复现:以表单方式 POST is_file=0(字符串)触发 TypeError: GetAvailableVaultNodeName::handle(): Argument #3 ($isFile) must be of type bool, string given。原因:应用内部接口面向自家前端,布尔字段要求 JSON 体。解决:请求改 Content-Type: application/json 且 is_file 传 true/false。

10.5 Inertia 接口返回 419/409

会话接口带 CSRF 与版本校验:419 为 XSRF token 缺失/过期(先 GET 页面取 XSRF-TOKEN cookie,解码后放 X-XSRF-TOKEN 头);409 为 X-Inertia-Version 与页面版本不符(从页面 JSON 中读取 version 字段随请求携带)。连续快速 POST 还会触发登录限流 429,操作间隔 1-2 秒。

10.6 上传大附件失败

默认上限 500M(metadata.upload_max_filesize 实测返回 500 MB),且 nginx 侧 NGINX_CLIENT_MAX_BODY_SIZE 默认 100M——超过 100M 需同时调 PHP_POST_MAX_SIZE、PHP_UPLOAD_MAX_FILE_SIZE 与 NGINX_CLIENT_MAX_BODY_SIZE 三者。

十一、卸载与清理

cd /opt/many-notes
docker compose down -v      # 删除容器、网络与四个命名卷(数据销毁,先备份!)
docker rmi brufdev/many-notes:latest

down -v 前确认 private/database 卷已备份:docker run --rm -v many-notes-0181-stack_private:/from -v $(pwd):/to alpine cp -a /from/. /to/private-backup/(database 卷同理)。

十二、参考链接

Many Notes Docker Compose 部署指南 - Quzuo