Paperless-ngx 部署教程:中文 OCR、文档检索与备份
部署 Paperless-ngx 3.2.1 文档库,识别中文扫描件,设置分类、共享权限和 HTTPS,再导出文档与部署配置。
先决定文档库放在哪台机器
为合同、发票和账单建一个能搜索的文档库,先比较现有 VPS、独立实例和内网机器是否合适。除了每月租金,还要留出批量 OCR 的资源、长期增加的文件空间和站外备份预算。已有机器能满足资源和资料保存条件,就不必为了安装 Paperless-ngx 再买一台。
Paperless-ngx 适合持续归档的个人或小团队。合同扫描成 PDF、报销凭证和说明书存成图片后,可以提取纸面文字,再按标签、往来单位、文档类型和日期整理。在浏览器里既能搜索正文、查看原件,也能修改分类,查订单号时不必只靠文件名逐个打开。
本文使用 Paperless-ngx 3.2.1 与 Docker Compose 的部署示例。先保留本机访问入口并初始化管理员,再通过 Caddy 接入 HTTPS,核对中文识别、共享权限和导出备份。全文搜索与 OCR 都可以在自己的服务器上完成,无需为这个用途接入大模型;选型时先看导入负载和数据保存方式。
资料能否放上 VPS,要单独判断
Paperless-ngx 的重点是文档归档。它保留原始文件,提取文字,并根据文件和设置生成可供浏览的归档版本。标签与文档类型让一张发票既能按月份查,也能按供应商查,不必把同一个文件复制到几个目录里。
它不会替你确认合同条款,也不能保证 OCR 读出的金额、身份证号或账户号码准确。搜索帮助找到文件,最后使用其中的信息时仍要对照原件。照片歪斜、印章覆盖文字、低分辨率和手写内容都可能影响识别。
选择部署位置之前,要看这些资料是否适合保存到那台机器。普通 OCR 使用本地 Tesseract;但自托管不等于文件已经加密。Paperless-ngx 的功能说明明确提到,文件以明文形式存放在磁盘上。拿到服务器磁盘、备份或足够权限的管理员,可能读到这些材料。HTTPS 保护传输,无法代替服务器存储和账户权限管理。
个人说明书、公开资料和普通票据可以先作为入门样本。包含商业秘密、客户身份资料或其他敏感内容时,先确认保存位置、授权和备份方式是否合适;不适合上公网的资料,可以放在内网服务器,通过自己的安全访问通道使用。不要为了“能在手机打开”而直接公开整套文档库。
项目还提供可选 AI 和远程 OCR 功能,但这些有各自的配置与数据流向。本文只使用本地 OCR,不开启它们。
把 OCR、文件空间和站外备份一起计入预算
一个只有几位使用者、按天少量导入文件的文档库,可以从 2 核、4GB 内存的 VPS 考虑起。这是本文对小规模用途的建议,不是官方最低配置,也不是吞吐量承诺。批量识别几百页 PDF、同时运行其他应用时,要另外估算内存和处理时间。
导入扫描件时,OCR 会集中占用 CPU 和内存;平时浏览文档不能代表这一阶段的负载。比较实例前,先看经常导入的页数、清晰度和图片尺寸。本例的后台任务数和每个任务的线程数都先设为 1,避免第一次导入就同时运行多个识别任务;之后按资源余量调整。
磁盘容量不能只按现有 PDF 的总大小来选。原件、归档文件、缩略图、数据库和临时导出都要占空间,还要给后续资料增长留余量。还要在另一台机器或独立存储上预留备份容量,并核对存储和传输是否另收费;只在当前 VPS 留一份导出,仍无法应对实例丢失。磁盘接近满额时,应先处理空间,再继续导入或备份。
比较现有 VPS 与新实例时,要确认完整 Docker 支持、磁盘扩展方式、流量和备份安排。例如雨云服务器可以作为选型入口,具体仍按当前机房、规格和价格核对。便宜的小实例如果运行其他应用后内存余量已经不足,就不适合直接叠加 OCR 任务;先看现有负载,再决定独立部署或升配。
准备 Linux 管理账号、可用的 Docker Engine 与 Compose 插件。没有安装时按Docker 官方安装文档操作;有面板或已有 Docker 的服务器,先确认版本和现有端口,不要重复安装一套。
docker --version
docker compose version本文使用的管理账号需要有权限调用 Docker。该权限可以控制宿主机,应只给可信的管理员。Caddy 使用宿主机服务形式;如果你已有容器形式的反向代理,需要改为容器网络互通,不能直接照搬后面的回环地址。
先建立目录和两份私有配置
这里选择 PostgreSQL 数据库与 Valkey 消息代理。Valkey 是兼容 Redis 协议的服务,所以 Paperless 的连接设置仍叫 `PAPERLESS_REDIS`。这与 3.2.1 发布标签中的官方 Compose 文件保持一致。
先在普通管理账号的家目录建立项目:
(
set -eu
umask 077
mkdir -p ~/paperless/consume ~/paperless/export
cd ~/paperless
for config_file in .env docker-compose.env; do
if [ -e "$config_file" ] || [ -L "$config_file" ]; then
printf '已存在 %s,停止首次初始化并保留原件。\n' "$config_file" >&2
exit 1
fi
done
paperless_db_password="$(openssl rand -hex 32)" || exit 1
paperless_secret_key="$(openssl rand -hex 64)" || exit 1
if [ "${#paperless_db_password}" -ne 64 ] || [ "${#paperless_secret_key}" -ne 128 ]; then
printf '随机密钥生成结果不完整,未创建配置文件。\n' >&2
exit 1
fi
case "$paperless_db_password$paperless_secret_key" in
*[!0-9a-fA-F]*)
printf '随机密钥不是预期的十六进制格式,未创建配置文件。\n' >&2
exit 1
;;
esac
paperless_uid="$(id -u)" || exit 1
paperless_gid="$(id -g)" || exit 1
set -C
printf 'PAPERLESS_DB_PASSWORD=%s\n' "$paperless_db_password" > .env || exit 1
printf 'PAPERLESS_SECRET_KEY=%s\nUSERMAP_UID=%s\nUSERMAP_GID=%s\n' \
"$paperless_secret_key" "$paperless_uid" "$paperless_gid" > docker-compose.env || exit 1
unset paperless_db_password paperless_secret_key
)这段 Bash 命令只用于首次安装,会先检查 `.env` 和 `docker-compose.env`。任一目标已存在,包括空文件、目录和符号链接,都会停止并保留原件;两份随机值生成成功且格式完整后才创建文件。命令在独立子 Shell 中执行,`noclobber` 只作用于这一段;写入失败会返回失败,不继续当作初始化成功。它分别保存数据库密码、应用签名密钥和管理账号的 UID、GID。后续更新不要再次运行。若第二份文件写入失败,第一份新文件可能已经留下,先保留现场检查原因,不要删除配置后反复重跑。更换数据库环境变量不会自动修改已初始化数据库里的密码,重生成签名密钥也可能影响已有会话。
在 `docker-compose.env` 后面补上以下设置,把域名换成自己的地址:
PAPERLESS_URL=https://docs.example.com
PAPERLESS_TIME_ZONE=Asia/Shanghai
PAPERLESS_OCR_LANGUAGES=chi-sim
PAPERLESS_OCR_LANGUAGE=chi_sim+eng
PAPERLESS_OCR_MODE=auto
PAPERLESS_TASK_WORKERS=1
PAPERLESS_THREADS_PER_WORKER=1中文配置的两处写法不同:`PAPERLESS_OCR_LANGUAGES` 是安装额外语言包,简体中文写 `chi-sim`;`PAPERLESS_OCR_LANGUAGE` 是 Tesseract 实际使用的语言代码,写 `chi_sim`。英文与中文一起识别时用 `chi_sim+eng`,多种语言也会增加处理开销。
本文使用容器默认的启动与用户映射方式,让启动脚本安装中文语言包。不要额外给这个容器加 `user: 1000:1000` 来强制无 root 启动;官方说明,启动时安装额外语言包与这种运行方式不兼容。需要严格无 root 的环境,应先准备带所需语言包的镜像,不能只改一个字段。
`PAPERLESS_URL` 要包含协议,不带结尾斜杠,也不要写成 `https://example.com/paperless`。它会帮助配置允许的主机与请求来源,适合独立子域名。
写 Compose,数据库端口不要向公网开放
创建 `docker-compose.yml`:
services:
broker:
image: docker.io/valkey/valkey:9-alpine
restart: unless-stopped
volumes:
- redisdata:/data
db:
image: docker.io/library/postgres:18
restart: unless-stopped
volumes:
- pgdata:/var/lib/postgresql
environment:
POSTGRES_DB: paperless
POSTGRES_USER: paperless
POSTGRES_PASSWORD: ${PAPERLESS_DB_PASSWORD:?set PAPERLESS_DB_PASSWORD}
webserver:
image: ghcr.io/paperless-ngx/paperless-ngx:3.2.1
restart: unless-stopped
depends_on:
- db
- broker
ports:
- "127.0.0.1:8000:8000"
volumes:
- data:/usr/src/paperless/data
- media:/usr/src/paperless/media
- ./export:/usr/src/paperless/export
- ./consume:/usr/src/paperless/consume
env_file: docker-compose.env
environment:
PAPERLESS_REDIS: redis://broker:6379
PAPERLESS_DBHOST: db
PAPERLESS_DBENGINE: postgresql
PAPERLESS_DBNAME: paperless
PAPERLESS_DBUSER: paperless
PAPERLESS_DBPASS: ${PAPERLESS_DB_PASSWORD:?set PAPERLESS_DB_PASSWORD}
volumes:
data:
media:
pgdata:
redisdata:数据库与 Web 服务从同一个 Compose 变量取得密码,避免两边写成不同值。`db` 和 `broker` 只在项目网络里提供服务,没有给宿主机发布 5432 或 6379 端口。
这里用的是 PostgreSQL 18,数据卷挂到 `/var/lib/postgresql`。这是该版本官方 Compose 文件采用的路径,不要把旧教程中的 `/var/lib/postgresql/data` 不加核对地搬过来。已经有 PostgreSQL 旧版本数据卷时,也不能只把镜像改成 18;数据库大版本迁移需要单独安排。
`data` 保存应用数据,`media` 保存文档等媒体,`pgdata` 保存数据库。`consume` 是投递文件的入口,`export` 是导出目录。`consume` 用于接收待处理文件,不能当成归档原件目录。
Web 端口只绑定 `127.0.0.1`。所以启动后,即使在云防火墙开放 8000,外部也不能直接访问这个端口。先通过本机入口初始化,随后由反向代理提供正式访问。
检查配置并拉取镜像:
docker compose config --quiet
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail=100 webserver`config --quiet` 会检查 Compose 配置,避免把包含展开密码的完整配置输出到终端记录里。拉取镜像后,首次启动还会准备数据库、运行迁移和安装额外语言包,需要根据日志判断完成状态;只有容器列表出现名字,不代表应用已经能处理文档。
首次启动卡住时,保留日志里的错误。镜像拉取、容器启动后下载中文语言包、数据库连接是不同步骤,不应一律归为“Docker 出问题”。
初始化管理员,再接入正式域名
在自己的电脑上建立 SSH 转发,将示例账号和服务器地址换成实际值:
ssh -L 18000:127.0.0.1:8000 your-user@your-server-ip保持连接,浏览器访问 `http://127.0.0.1:18000`。Paperless-ngx 3.2.1 的首次访问会提示创建超级用户,按页面完成初始化。因为配置里提前设置了正式域名,回环地址访问如果被主机或来源检查拦截,可以临时在 `docker-compose.env` 增加:
PAPERLESS_ALLOWED_HOSTS=127.0.0.1,localhost
PAPERLESS_CSRF_TRUSTED_ORIGINS=http://127.0.0.1:18000修改环境文件后,需要重新创建应用容器才会读到新值:
docker compose up -d webserver这些设置与 `PAPERLESS_URL` 产生的值合并,因此不需要把正式域名改成回环地址。完成初始化并确认正式域名可访问后,移除临时加入的本地访问设置,再重新创建应用容器。这样首次设置账户的页面不会先暴露在公网。
准备正式域名,添加指向 VPS 的 A 记录,并确认已有 AAAA 记录有效。国内地域的公开网站要满足相应备案与接入要求。宿主机已有 Caddy 时,在现有配置中增加站点块:
docs.example.com {
reverse_proxy 127.0.0.1:8000
}按照自己现有的 Caddy 管理方式校验并重载配置。使用常规 systemd 安装、配置路径为 `/etc/caddy/Caddyfile` 时:
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy证书签发需要域名与网络满足验证条件。防火墙开放正式 Web 入口,保留 SSH 管理入口,不必公开应用、数据库和消息代理端口。此例的 Caddy 在宿主机上运行;容器里的 `127.0.0.1` 指向容器自己,采用容器反向代理时要改连接方式。
打开 `https://docs.example.com`,用刚创建的账号登录。若页面能显示但提交时报 CSRF 错误,先检查域名与 `PAPERLESS_URL` 是否一致、环境文件是否已被重新读取,不要直接关闭 CSRF 保护。
导入第一批文件时,先验证中文识别
先挑少量有代表性的文件:一份带原生文字的 PDF、一份中文扫描 PDF、一张手机拍摄的清晰图片。上传后等任务完成,打开详情页,查看预览、提取出的文字和识别出的日期。再选一个原件里明确存在的词或编号搜索,确认它能指向正确文档。中文识别成功与搜索结果正确是两项检查,应分别核对,不只看任务状态。
`auto` 模式会判断 PDF 是否已有足够的文字层,存在时可能跳过 OCR。这适合原生 PDF 与扫描件混合的资料库。如果扫描软件已有错误文字层,不能只因“搜索有结果”就认定识别无误;要对照正文内容。`redo` 和 `force` 有各自代价,尤其 `force` 会把页面栅格化,可能让文件变大、文字放大后不如原件清晰,不宜作为全部文件的默认处理。
文档详情把文件预览和分类信息放在一起。核对编号或金额时,应查看原件,不只依靠搜索结果摘要。
简体中文文件识别为空或日志提示缺语言数据时,回到语言包安装和语言代码设置排查。照片中的字很小、背景复杂或有明显倾斜,则要先改善扫描质量。OCR 参数无法补回原件里已经缺失的信息。
标签先从用途明确的少量分类开始,例如“待核对”“已报销”;文档类型用“发票”“合同”“说明书”;往来单位记录开票方或合同对方。不要把每个日期和文件名都做成标签。归档以后还能调整分类,没有必要第一天就建几十个空标签。
投递目录会清空文件,权限也要另设
文件可以在网页上传,也可以放到宿主机的 `consume` 目录。目录中的文件被成功接收后,会从该目录移走并存入 Paperless 管理的文档存储。因此,向这个目录投递副本;不要把电脑里唯一一份材料所在的目录直接挂载成消费目录。
初期先使用网页上传,熟悉处理情况后再连接扫描仪或同步任务。若投递目录位于 NFS 等不支持文件变动通知的文件系统上,可能需要设置轮询;具体见安装文档的文件系统说明。不要把文件迟迟未导入直接理解为 OCR 慢。
多用户使用时,每个人建立自己的账号。超级用户能看全部文档,适合维护,不适合用来验证普通成员的隔离效果。日常使用可以建立普通账号,根据需要给文档查看、上传或编辑权限。
尤其注意消费目录导入:官方说明,这种文档默认没有所有者和额外对象权限;无所有者对象会对有相应应用访问权限的用户可见。团队使用前,应通过工作流安排所有者或文档权限,并用两个普通账号分别检查可见范围。给标签设置权限,也不会自动限制带该标签的文档。
文档有自己的所有者、查看与编辑权限。它与用户能否进入应用、能否操作标签等全局权限是两层设置,详见用户与权限说明。
分享链接会提供另一条访问路径。发送之前确认文件内容和有效期,不要因为日常登录有密码就默认所有分享都受相同限制。
导出文档之后,还要保存部署配置
从项目目录执行:
cd ~/paperless
docker compose exec -T webserver document_exporter ../export导出器会写出文档、缩略图和记录元数据的 `manifest.json` 等内容,容器内的 `../export` 对应宿主机项目下的 `export`。成功导出后,把整个导出结果复制到独立存储,另外私密保存 Compose 文件、环境文件和恢复所需的版本信息。
仅在这台 VPS 上留一个导出目录,无法应对实例丢失。导出本身也包含敏感文档和元数据,不能放进公开网页目录。默认导出不会清理以前导出中已经删除的旧文件;需要清理时先了解删除选项的范围,避免指向混有其他资料的目录。
正式资料库安排导出时,可以暂停导入、编辑和删除,让这一批备份有清楚的时间范围。数据库备份与媒体卷备份要配套保存,不能只复制数据库,就认为原件也跟着保存了。
恢复文档导出时,把完整结果放进新环境的 `export` 目录,在版本匹配、数据库和文档目录为空的安装中使用:
docker compose exec -T webserver document_importer ../export这是新环境恢复命令,不要对正在使用的资料库直接执行。官方建议导出和导入使用相同版本;导入后检查文档数量、原件下载、中文检索和普通账号权限。确认恢复路径之后,再把新环境投入使用。参数和注意事项见导出与导入文档。
先分清升配、应用更新与数据库迁移
本例固定应用镜像为 3.2.1。这一版于 2026 年 9 月 20 日发布,修复了邮件抓取锁、OCR 相关依赖和缺少搜索索引文件时的重建问题。更新计划可以从官方发布记录开始,不需要因为名字叫“最新”就自动更新。
有新应用版本时,先看升级要求,完成站外备份,再修改应用镜像标签,执行拉取与启动。升级可能运行数据库迁移,出问题时不能保证只把镜像换回旧标签就能恢复。数据库和文档要有同一个恢复计划。
PostgreSQL 的大版本升级是另一项工作,本例的 `postgres:18` 不应随手改成未来的大版本并复用原数据卷。同理,更新 `.env` 中的密码只改变传入容器的值,不会改动已有数据库账户。需要变更密码时,应同步修改数据库账户与应用连接设置。
导入变慢时,先检查队列、失败日志和磁盘空间;OCR 期间网页也变慢,可以先减少同时处理的文件,再看资源占用。确认瓶颈和余量后,再决定是否调整任务并发或实例配置。批量扫描和邮件收集接入前,仍先用少量文件核对导入、中文检索、普通账号权限与恢复,不用“首页能打开”代替这些检查。
