Papra 26.7.1 Docker Compose 部署指南
Papra 26.7.1 Docker Compose 部署指南:官方镜像形态选型、数据库依赖链、compose 逐项解说与首次注册登录、数据持久化、升级回滚的完整实测记录
Papra 26.7.1 Docker Compose 部署指南
目标:用官方打包发行 + Compose 编排在单机部署一套可用的 Papra 26.7.1 文档归档服务,完成首次注册登录、数据持久化验证与升级/回滚路径说明。本文所有命令与回显均来自 2026-10-31 在 Ubuntu 22.04(Docker 29.9.2 + Compose v5.6.1)上的实测。组件背景见 《Papra 26.7.1 介绍与选型指南》。
一、环境信息
| 项 | 要求(实测环境) |
|---|---|
| 操作系统 | Ubuntu 22.04 |
| Docker / Compose | Docker 29.9.2,Compose v5.6.1 |
| Papra | 26.7.1(ghcr.io/papra-hq/papra:26.7.1-root) |
| 端口 | 服务内监听 1221,宿主映射 8134 |
| 数据目录 | /opt/papra/data(绑定挂载到服务内 /app/app-data) |
| 硬件 | 1C2G 起步(SQLite 单机形态实测空闲内存占用约 100MB 量级) |
| 外部依赖 | 无(默认内嵌 SQLite,无需外挂数据库/消息队列) |
二、镜像选型说明
官方发行源为 ghcr.io/papra-hq/papra(备用同步源 corentinth/papra),标签分两族(2026-10-31 查询官方标签列表):
| 标签 | 说明 | 建议 |
|---|---|---|
26.7.1-root / 26.7.1-rootless | CalVer 版本化固定标签 | 生产推荐,升级时机完全自主可控 |
latest-root / latest-rootless / latest | 浮动最新标签 | 仅测试评估用 |
0.0.1 ~ 0.9.6(-root/-rootless) | 0.x 早期系列 | 勿用 |
- root 形态:服务内以 root 运行,绑定挂载数据目录无需处理 UID 映射,上手最简单。
- rootless 形态:服务内以非特权用户运行,更安全,但要求宿主数据目录对映射 UID 可写(配合
--user $(id -u):$(id -g)或user:字段)。 - 26.7.1 构建信息(服务内环境变量实测):
PAPRA_VERSION=26.7.1、GIT_COMMIT=3078ac59e953ee9a96e8f5ad7a967ae39e594037、BUILD_DATE=2026-10-22T21:42:32+02:00。
三、前置条件
- 已安装 Docker 与 Compose v2 插件:
docker --version
# Docker version 29.9.2, build a82e528
docker compose version
# Docker Compose version v5.6.1
- 生成部署密钥的工具(系统自带 openssl 即可)。
- 确认宿主 8134 端口空闲:
ss -ltn | grep 8134
# 无输出即空闲
- 规划数据目录
/opt/papra/data(Papra 全部状态——数据库与文档原文件——都在这一个目录内)。
四、部署步骤
4.1 生成部署密钥并建目录
mkdir -p /opt/papra/data && cd /opt/papra
openssl rand -hex 48
# ee7eddeee820b3742fa4a583979c8e4d1f0aebd22faf5a580b0382c8ab10547332ffd4f228ce48bced95b0e7f772272d
⚠️ 注意:AUTH_SECRET 是会话签名与加密的基础密钥,务必用上面的命令生成自己的随机值并妥善保管;更换它会使全部已有登录会话与 API Key 失效(实测 26.7.1 行为)。上面的示例值仅用于演示环境。
4.2 编写 docker-compose.yml
在 /opt/papra/docker-compose.yml 写入(以下为实测通过的完整文件):
name: papra-2671-stack
services:
papra:
image: ghcr.io/papra-hq/papra:26.7.1-root
container_name: papra-2671-app
restart: unless-stopped
ports:
- "8134:1221"
environment:
- APP_BASE_URL=http://192.168.1.200:8134
- AUTH_SECRET=${AUTH_SECRET}
volumes:
- /opt/papra/data:/app/app-data
networks:
- papra-net
networks:
papra-net:
ipam:
config:
- subnet: 10.89.57.0/24
并准备同目录 .env 承载密钥(避免明文进编排文件):
echo 'AUTH_SECRET=ee7eddeee820b3742fa4a583979c8e4d1f0aebd22faf5a580b0382c8ab10547332ffd4f228ce48bced95b0e7f772272d' > /opt/papra/.env
chmod 600 /opt/papra/.env
4.3 启动并观察首次迁移
cd /opt/papra && docker compose up -d
docker compose ps
实测回显:
NAME IMAGE STATUS PORTS
papra-2671-app ghcr.io/papra-hq/papra:26.7.1-root Up About an hour 0.0.0.0:8134->1221/tcp, [::]:8134->1221/tcp
首次启动会自动执行全部数据库迁移(服务日志节选,实测 28 个迁移逐个执行):
$ pnpm migrate:up:prod && pnpm start
$ tsx src/scripts/migrate-up.script.ts
{"level":"info","message":"Database directory missing, created it","timestampMs":1793411729783,"namespace":"database-services","data":{"scriptName":"migrate-up","isDryRun":false,"dbUrl":"file:./app-data/db/db.sqlite","dbDir":"./app-data/db","dbPath":"./app-data/db/db.sqlite"}}
{"level":"debug","message":"Running migrations","timestampMs":1793411729789,"namespace":"migrations","data":{... "migrationsToRunCount":28,"existingMigrationsCount":0}}
{"level":"info","message":"Migration run successfully","timestampMs":1793411729794,"namespace":"migrations","data":{"scriptName":"migrate-up","isDryRun":false,"migrationName":"initial-schema-setup"}}
...(其余 26 个迁移逐条 "Migration run successfully",节选)...
{"level":"info","message":"All migrations run successfully","timestampMs":1793411729901,"namespace":"migrations","data":{"scriptName":"migrate-up","isDryRun":false}}
$ node dist/index.js
{"level":"info","message":"Starting application","timestampMs":1793411731049,"namespace":"app-server","data":{"processMode":"all","isWebMode":true,"isWorkerMode":true}}
{"level":"info","message":"Creating emails services","timestampMs":1793411731052,"namespace":"emails.services","data":{"processMode":"all","driverName":"logger"}}
{"level":"debug","message":"Task persistence driver initialized","timestampMs":1793411731058,"namespace":"tasks:services","data":{"processMode":"all","driverName":"memory"}}
...(注册 5 个定时清理任务:文档硬删除/邀请过期/组织清理/KV 过期清理/权益复核,节选)...
{"level":"info","message":"Server started","timestampMs":1793411731086,"namespace":"app-server","data":{"processMode":"all","port":1221}}
日志出现 Server started ... port 1221 即部署完成。
4.4 首次注册(实测)
浏览器打开 http://192.168.1.200:8134 进入引导页注册第一个账号;等价的 API 实测如下:
curl -s -X POST http://192.168.1.200:8134/api/auth/sign-up/email \
-H 'Content-Type: application/json' \
-d '{"name":"admin","email":"admin@papra.local","password":"q1w2e3r4"}'
实测回显(HTTP 200):
{"token":"agfwmVM59tWupGYD79fqzcvwQD76oAJm","user":{"name":"admin","email":"admin@papra.local","emailVerified":false,"image":null,"createdAt":"2026-10-31T01:57:37.091Z","updatedAt":"2026-10-31T01:57:37.091Z","twoFactorEnabled":false,"id":"usr_v63ltj99aqbnbjq8jitdemkl"}}
4.5 登录验证(实测)
curl -s -X POST http://192.168.1.200:8134/api/auth/sign-in/email \
-H 'Content-Type: application/json' \
-d '{"email":"admin@papra.local","password":"q1w2e3r4"}'
实测回显(HTTP 200):
{"redirect":false,"token":"gh4CcLULlNzjUtNJ5ZK5tb6Iacb5eTRa","user":{"name":"admin","email":"admin@papra.local","emailVerified":false,"image":null,"createdAt":"2026-10-31T01:57:37.091Z","updatedAt":"2026-10-31T01:57:37.091Z","twoFactorEnabled":false,"id":"usr_v63ltj99aqbnbjq8jitdemkl"}}
首个账号注册后系统自动创建默认组织;上传第一份文档、打标签、创建 API Key 等操作的逐条实测见《Papra 26.7.1 REST API 使用详解》。
⚠️ 注意:生产环境请关闭公开注册(
AUTH_IS_REGISTRATION_ENABLED=false),并在部署后尽快改掉演示密码;关闭后新用户注册请求会收到EMAIL_PASSWORD_SIGN_UP_DISABLED错误(实测回显见常见问题 11.2)。
五、数据持久化说明
Papra 的全部状态收敛在一个数据目录(对应服务内 /app/app-data,由 PAPRA_CONFIG_DIR 指定):
/opt/papra/data/
├── db/
│ └── db.sqlite # 全部业务数据(文档索引/标签/用户/组织/分享链接…)
└── documents/
└── org_tqq4cqy4g6ui3iwvitpgucgh/ # 按组织 ID 分目录
└── originals/
├── doc_fi8no66puuek7em2chpe37ep.pdf # 文档原文件
├── doc_htp3s4dxqs1jnnoepbw3xk8u.txt
└── doc_v8bhjdd7fty5o3xf6zeuxlt2.png
- 文件名即文档 ID(
doc_前缀 + 24 位小写字母数字),目录结构默认为{组织ID}/originals/{文档ID}.{原始后缀}(可通过DOCUMENT_STORAGE_KEY_PATTERN自定义,见配置篇)。 - 实测该实例在 5 份文档(含 1 份 PDF)时数据目录总大小 860K,可估算磁盘规划。
- 备份:小规模直接停写备份(
docker compose stop后打包/opt/papra/data)最稳妥;不停机备份需用 SQLite 在线备份工具处理db/db.sqlite,并连同documents/一起打包。 - 恢复:还原数据目录后
docker compose up -d,服务启动时迁移框架检测到 schema 已存在会跳过全部迁移(日志中existingMigrationsCount为全部迁移数)。
六、环境变量表
Compose 最小可用集(全部经实测;完整参数体系见《Papra 26.7.1 配置参数详解》):
| 变量 | 必填 | 实测取值 | 说明 |
|---|---|---|---|
| AUTH_SECRET | 是 | openssl rand -hex 48 生成 | 会话签名/加密密钥;更换即全员登出 |
| APP_BASE_URL | 是 | http://192.168.1.200:8134 | 对外访问地址,决定分享链接 url、邮件中的链接等 |
| DATABASE_URL | 否 | file:./app-data/db/db.sqlite(默认) | 换成 libsql:///postgres:// 连接串即切换外部数据库 |
| DOCUMENT_STORAGE_FILESYSTEM_ROOT | 否 | ./app-data/documents(默认) | 本地文件存储根目录,必须在挂载目录内 |
| PAPRA_CONFIG_DIR | 否 | ./app-data(默认) | 数据根目录(数据库+文档) |
| EMAILS_DRY_RUN | 否 | true | 只把邮件打到日志不真发,默认可保持 |
| DOCUMENTS_OCR_LANGUAGES | 否 | eng(默认) | 中文扫描件建议 chi_sim,eng |
| DOCUMENT_STORAGE_MAX_UPLOAD_SIZE | 否 | 26214400(默认 25MB) | 超限上传实测返回 413,见常见问题 |
七、配置参数说明
逐项解读 4.2 编排文件:
name: papra-2671-stack:显式指定 Compose 项目名,避免默认目录名变化导致项目漂移。container_name: papra-2671-app:固定实例名,便于日志与监控检索。restart: unless-stopped:开机自启与异常自动拉起(手动 stop 的除外)。ports: "8134:1221":服务内固定监听 1221(PORT变量可改),宿主侧 8134 按需更换;APP_BASE_URL必须与宿主侧端口一致,否则分享链接/邮件链接会指向错误地址。environment:密钥经${AUTH_SECRET}从.env注入,编排文件可入库、密钥不入库。volumes: /opt/papra/data:/app/app-data:唯一持久化路径,见第五节。networks.papra-net:显式固定网段 10.89.57.0/24,防止与内网其它网段冲突;单机部署下也可省略 networks 块使用默认网络。
八、配置文件挂载
Papra 26.7.1 无配置文件,全部配置走环境变量(官方 Configuration 文档只提供 .env 变量清单,2026-10-31 查询)。两种注入方式:
- Compose
environment:直接列出(推荐少量变量时)。 - 编排文件引用
.env(AUTH_SECRET=${AUTH_SECRET}),变量本体放同目录.env文件——本文采用此方式;.env 同样会被 Compose 自动加载为变量替换源。
无 YAML/JSON 配置挂载需求,也不需要挂载 Tesseract 语言包(OCR 语言随发行包内置,切换语言仅需 DOCUMENTS_OCR_LANGUAGES)。
九、等价 docker run
与 4.2 编排等价的最小单命令(官方文档形态,适合临时验证):
docker run -d \
--name papra-2671-app \
--restart unless-stopped \
--env APP_BASE_URL=http://192.168.1.200:8134 \
--env AUTH_SECRET=<你的密钥> \
-p 8134:1221 \
-v /opt/papra/data:/app/app-data \
ghcr.io/papra-hq/papra:26.7.1-root
rootless 变体追加 --user $(id -u):$(id -g) 并保证数据目录属主一致。
十、常用运维命令
cd /opt/papra
docker compose ps # 实例状态(回显见 4.3)
docker compose logs -f --tail 100 # 跟踪日志(迁移/任务/HTTP 报错都在这里)
docker compose restart # 重启实例
docker compose down # 停止并移除实例(数据目录保留)
升级(CalVer 固定标签策略下):
# 1. 先备份数据目录
tar -czf /backup/papra-data-$(date +%F).tgz -C /opt/papra data
# 2. 改 docker-compose.yml 中的 image 标签为新版本号
# 3. 拉取并滚动
docker compose pull && docker compose up -d
# 4. 看日志确认迁移执行完成(新版本可能新增迁移,逐条 Migration run successfully)
docker compose logs -f | grep -E 'Migration|Server started'
回滚:把 image 标签改回旧版本号后 docker compose up -d。⚠️ 若新版本已执行过数据库迁移,降级可能因 schema 不兼容而失败——回滚前用第五节的备份还原数据目录是唯一稳妥路径。
十一、常见问题
Q1:上传大文件报错? 默认单文件上限 25MB(26214400 字节)。实测上传 27MB 文件:
HTTP=413
{"error":{"message":"Document size too large.","code":"document.size_too_large"}}
调大 DOCUMENT_STORAGE_MAX_UPLOAD_SIZE(单位字节,设 0 为不限制)后重启生效。
Q2:注册接口返回 EMAIL_PASSWORD_SIGN_UP_DISABLED?
AUTH_IS_REGISTRATION_ENABLED=false 时的预期行为,实测回显(HTTP 400):
{"message":"Email and password sign up is not enabled","code":"EMAIL_PASSWORD_SIGN_UP_DISABLED"}
注意该错误没有外层 error 对象,与业务接口错误形态不同(better-auth 端点风格)。开放注册期间建议配合 AUTH_ALLOWED_EMAIL_DOMAINS 限定域名。
Q3:首次启动迁移失败/数据库文件损坏?
确认 DATABASE_URL 指向的目录存在且可写(日志中 Database directory missing, created it 表示框架自动建目录);db/db.sqlite 损坏时从备份还原整个数据目录。
Q4:上传的扫描件搜不到内容?
检查 DOCUMENTS_OCR_LANGUAGES 是否包含对应语言(中文 chi_sim);对已入库文档可在界面触发"重新处理",或按 REST API 篇调用 reprocess 接口(默认限流 100 次/小时/组织)。
Q5:APP_BASE_URL 配错会怎样?
分享链接 url 字段、邮件内链接都会拼错(分享链接实测直接返回 APP_BASE_URL 拼接的完整地址)。改正确后已有分享链接需重建或用 PATCH 更新。
十二、参考链接
- 官方自托管文档(Using Docker / Using Docker Compose):https://docs.papra.app/self-hosting/using-docker 、https://docs.papra.app/self-hosting/using-docker-compose (2026-10-31 查询)
- 官方配置参考:https://docs.papra.app/self-hosting/configuration (2026-10-31 查询)
- 官方软件包源:https://ghcr.io/papra-hq/papra (2026-10-31 查询)
- 官方仓库:https://github.com/papra-hq/papra (2026-10-31 查询)
本专栏姊妹篇:《Papra 26.7.1 介绍与选型指南》、《Papra 26.7.1 配置参数详解》、《Papra 26.7.1 REST API 使用详解》。