SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor · 更新于 2026-08-28

如何在 VPS 上用 Docker Compose 部署 Paperless-ngx

在 VPS 上部署 Paperless-ngx:使用官方 PostgreSQL Compose 栈,正确设置 PAPERLESS_URL,配置 consume 目录与 OCR 语言,并完成 HTTPS 和备份。

您要构建的内容

在 VPS 上运行 Paperless-ngx,可以将一批扫描纸质文件转换为可搜索的归档。将 PDF 放入受监控的目录后,服务器会对其运行 OCR(光学字符识别),提取文本,推测日期和通信对象,然后完成归档。安装过程只需使用一个包含 4 个服务的 Docker Compose 文件。之后的工作都属于配置,本指南的大部分内容也都集中在这里,因为安装通常会在配置阶段出错。Paperless-ngx 并不是照片库:OCR 和通信对象识别无法处理一批旅行照片 JPEG,因此应将这些文件放入专为照片设计的照片服务器,而让 Paperless-ngx 专门处理纸质文件。视频也是如此:一组翻录的电影应放在媒体服务器上。此类场景中,类似伪装成 90 年代录像租赁店的 Jellyfin 前端的界面可以将浏览作为核心功能,而不是依赖搜索。

Paperless-ngx 是原始 Paperless 项目的社区维护分支。它免费、支持自托管,并将文档以普通文件形式存储在磁盘上,因此您始终可以访问自己的文档归档,不会被锁定在系统之外。在 VPS 上运行它,而不是运行在家用设备上,可以让您从任何位置访问扫描件,无需在家用路由器上开放端口;它还适合与用于存放非纸质文件的私有 Nextcloud 实例配合使用。相同的思路也适用于连接扫描仪的桌面电脑,因为在该 VPS 上自行运行 RustDesk 中继服务器后,您可以从其他位置操作这台电脑,同样无需在路由器上打孔。

实际运行的组件

官方 compose 文件会启动 4 个容器。了解每个容器的作用后,日志更容易阅读。

  • webserver:paperless-ngx 镜像本身。它运行 Web 界面、API、监控输入目录的 consumer,以及执行 OCR 的 Celery 任务 worker。
  • db:PostgreSQL。它保存元数据、标签、对应方和全文搜索索引表,但不保存 PDF 文件。
  • broker:Valkey,一种兼容 Redis 的键值存储。它在 Web 进程和 worker 之间提供任务队列。
  • gotenbergtika:可选组件,仅用于 -tika compose 变体。它们将 Office 文档(.docx.xlsx.odt)转换为 PDF,供 paperless 建立索引。

截至 2026 年 7 月,postgres compose 文件固定使用 docker.io/library/postgres:18docker.io/valkey/valkey:9-alpine,并从 ghcr.io/paperless-ngx/paperless-ngx:latest 拉取应用。

前置条件

  • 一台具有 sudo 访问权限的 Ubuntu 24.04 KVM VPS,并且已安装 Docker 和 Compose 插件。如果这部分对您来说较新,请先阅读VPS 的 Docker Compose 基础知识,然后再回来。
  • 一个 A 记录指向该 VPS 的域名。Paperless 不会在未配置的主机名上提供服务,因此这一点需要尽早处理。
  • 内存是真正的限制因素。PostgreSQL、Valkey、gunicorn 和一个 Tesseract OCR worker 同时常驻时,轻量使用场景下 2 GB 足够。如果计划导入数百份扫描件的积压任务,请分配 4 GB,因为大型多页 PDF 的 OCR 处理会产生内存峰值,可能导致 worker 被内核的 out-of-memory killer 终止。
  • 磁盘:归档会同时保存原始文件和经过 OCR 处理的归档 PDF,因此应按扫描件总大小的大约 2 倍规划磁盘空间。

获取官方 compose 文件

提供了一个交互式安装程序:

bash -c "$(curl --location --silent --show-error https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/install-paperless-ngx.sh)"

它会询问相关问题并替您写入文件。手动执行只需 4 条命令,而且您可以明确知道所有内容的位置。这正适合需要长期维护的服务器。

mkdir -p ~/paperless && cd ~/paperless
curl -fsSL -o docker-compose.yml https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docker/compose/docker-compose.postgres.yml
curl -fsSL -o docker-compose.env https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docker/compose/docker-compose.env
curl -fsSL -o .env https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docker/compose/.env

这些变体位于同一目录中:docker-compose.sqlite.ymldocker-compose.mariadb.yml,以及各自的 -tika 版本。新安装请选择 postgres。SQLite 可满足几百份文档的需求,但全文搜索索引的速度会在 PostgreSQL 之前明显下降。

.env 文件包含一行内容:COMPOSE_PROJECT_NAME=paperless。该名称会成为每个容器和卷名称的前缀。因此不要删除它,然后再疑惑为什么 docker compose down -v 找不到您的数据。

首次启动前配置 docker-compose.env

有两项设置不可省略。使用项目文档指定的命令生成密钥:

python3 -c "import secrets; print(secrets.token_urlsafe(64))"

然后编辑 docker-compose.env

PAPERLESS_SECRET_KEY=<the long string you just generated>
PAPERLESS_URL=https://paperless.example.com
PAPERLESS_TIME_ZONE=Europe/Berlin
PAPERLESS_OCR_LANGUAGE=deu+eng
USERMAP_UID=1000
USERMAP_GID=1000

PAPERLESS_SECRET_KEY 的初始值是字面值 change-me。它用于签名会话 Cookie,因此保留默认值后,任何知道该默认值的人都可以伪造会话。必须在首次启动前设置,因为之后修改会使所有用户退出登录。

PAPERLESS_URL 是最能节省时间的设置。Paperless 是 Django 应用,Django 会验证每个请求的 Host 请求头。设置 PAPERLESS_URL 后,系统会自动为您填充 ALLOWED_HOSTSCORS_ALLOWED_HOSTSCSRF_TRUSTED_ORIGINS。如果将其留空,再将域名指向此服务器,每个页面都会返回 Bad Request (400),容器日志中则会出现 DisallowedHost。填写时不要添加末尾斜杠或路径。

USERMAP_UIDUSERMAP_GID 设置容器运行时使用的用户。将它们设置为您自己的账户,并使用 id -uid -g 检查账户信息。如果两者不匹配,您复制到 consume 文件夹中的文件将无法被 consumer 读取,日志会显示权限错误,而不是执行导入。

启动服务栈并创建第一个用户

docker compose pull
docker compose up -d
docker compose run --rm webserver createsuperuser
docker compose logs -f webserver

createsuperuser 会提示您输入用户名、电子邮件地址和密码。系统没有默认登录凭据,因此跳过此步骤后,您会停留在无法接受任何凭据的登录页面。等待日志显示服务器正在监听 8000 端口后,再尝试通过浏览器访问。首次启动还会执行数据库迁移,这通常需要 1 到 2 分钟。

在配置域名之前,先在本地检查:

curl -I http://127.0.0.1:8000

302 重定向到 /accounts/login/ 表示服务栈运行正常。

在前端配置 HTTPS

默认的 compose 文件会发布 8000:8000,并绑定到所有网络接口。在公网 VPS 上,这会让任何找到该地址的人都能通过明文 HTTP 访问整个文档归档。将端口行改为仅绑定到 loopback:

    ports:
      - "127.0.0.1:8000:8000"

然后在反向代理中终止 TLS(传输层安全),并将请求转发到 127.0.0.1:8000。如果这台主机上只有此应用,使用带有 ACME(自动证书管理环境)客户端的任意代理即可。如果您在同一套证书配置后运行多个容器,请遵循多个 Docker Compose 应用的 Traefik 反向代理模式,并将 webserver 服务连接到代理网络,完全不要发布端口。

无论使用哪种代理,都必须发送 X-Forwarded-Proto: https。如果缺少该标头,Django 会认为请求通过 HTTP 到达,登录表单的来源检查会失败,页面看起来正常,但会显示 CSRF verification failed. Request aborted.。此修复的另一部分是将 PAPERLESS_URL 设置为您在浏览器中输入的确切 https:// 地址。

此外,还应提高代理的上传大小限制。通过将请求体限制为 1 MB 的代理上传 40 MB 的扫描文件时,请求会在 paperless 收到之前被拒绝,浏览器只会报告通用的上传失败。

consume 目录的工作方式

compose 文件会将 compose 目录中的 ./consume bind mount 到容器中。您放入该目录的文件会被导入,然后从该目录删除,因为文件现在由 paperless 管理,并存放在 media 卷中。

cp ~/scan-2026-07-14.pdf ~/paperless/consume/
docker compose logs -f webserver

您应能看到 consumer 获取文件名、执行 OCR,并以一行报告文档已添加的日志结束。单页扫描的整个处理过程只需几秒,长文档可能需要 1 分钟或更长时间。

有两个设置会改变文件的查找方式。PAPERLESS_CONSUMER_RECURSIVE=true 会让 paperless 查找子目录,PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS=true 会将每个子目录名称转换为标签,因此将文件放入 consume/invoices/2026/ 后,会为其添加 invoices2026 标签。这是您能构建的最简单的归档系统。

检测是另一个环节。默认情况下,PAPERLESS_CONSUMER_POLLING_INTERVAL0,这表示 paperless 使用内核文件系统通知,文件变化会立即触发通知。此类通知无法跨越网络文件系统。如果 consume 目录位于 NFS 或 SMB 共享中,供网络扫描仪写入,那么文件将永远不会被检测到。解决方法是将间隔设置为正数秒,使 paperless 改为扫描该目录。

OCR 语言及其成本

PAPERLESS_OCR_LANGUAGE 接受三字母 Tesseract 语言代码,默认值为 eng。使用加号组合多种语言,例如 deu+eng。Tesseract 随后会逐一尝试这些语言,并保留最佳结果,因此每增加一种语言,处理每页所需的 CPU 时间都会增加。在共享 vCPU 的 VPS 上,这可能导致扫描从 10 秒完成变为 1 分钟完成。只列出文档实际使用的语言。

该镜像自带英语、德语、意大利语、西班牙语和法语。若需要其他语言,请将其作为空格分隔的列表添加到 PAPERLESS_OCR_LANGUAGES,例如 PAPERLESS_OCR_LANGUAGES=tur ces,然后重启。容器会在启动时下载 Tesseract 数据包,因此修改后的首次启动会更慢。

备份数据库和媒体文件

PostgreSQL 运行时复制 Docker 卷,得到的备份可能无法恢复。Paperless 提供了自己的导出工具。该工具会将文档以及包含全部元数据的 JSON 清单写入 ./export bind mount:

docker compose exec webserver document_exporter ../export --delete --no-progress-bar

--delete 会删除与当前文档不再匹配的导出文件,使该目录保持镜像状态,而不会无限增长。--no-progress-bar 可在通过 cron 运行时保持输出整洁。

在全新的 stack 上,恢复操作是针对同一目录执行 document_importer。因此,您只需妥善保管导出目录。按计划将其发送到异地,并使用 来自 VPS 的加密、去重 restic 备份;先执行导出,确保 restic 不会捕获未写入完成的归档。

通过确认存在 export/manifest.json,并检查文件数量是否与界面中的文档数量一致,验证备份是否有效。您从未列出过的备份不能算作备份。每晚导出任务悄无声息地失败则更糟,因此让 cron 任务将退出状态推送到 您自己的 ntfy 服务器。这样,您会在任务失败的那一周发现问题,而不是等到需要恢复的当天才发现。

FAQ

将域名指向该服务后,为什么每个页面都返回 “Bad Request (400)”?

Django 拒绝了 Host 请求头,因为您的域名不在 ALLOWED_HOSTS 中。在 docker-compose.env 中设置 PAPERLESS_URL=https://paperless.example.com,不要添加末尾斜杠,然后运行 docker compose up -d 重新创建容器。仅编辑 env 文件不会生效,因为正在运行的容器会继续使用启动时获取的环境变量。

我将 PDF 放进 consume 文件夹后没有任何反应。哪里出了问题?

先检查 docker compose logs webserver。权限错误表示 USERMAP_UIDUSERMAP_GID 与文件所属账号不匹配,因此请修复它们并重新创建容器。完全没有日志行表示文件事件根本没有到达;网络共享上会出现这种情况,因为内核通知不会跨越网络共享传递。将 PAPERLESS_CONSUMER_POLLING_INTERVAL 设置为类似 30 的值后,paperless 会改为每 30 秒扫描一次文件夹。

我可以使用 SQLite 而不是 PostgreSQL 运行 paperless-ngx 吗?

可以,支持 docker-compose.sqlite.yml,并且占用更少内存,适合小型 VPS。随着归档增长,差异会逐渐显现:当文档达到数千份后,全文搜索和批量编辑标签会明显变慢。之后迁移需要先导出再导入,因此如果预计归档会持续增长,现在就应选择 PostgreSQL。

扫描归档实际需要多少磁盘空间?

大约是源文件大小的两倍。Paperless 会保留未修改的原始文件,并存储一份带可搜索文本层的 OCR PDF,以及一些较小的缩略图。仅包含文本的 200 KB 扫描文件占用空间很小。对一份较长合同进行 30 MB 的彩色扫描后,存储空间约需 60 MB。如果将导出目录保存在同一磁盘上,还应加上该目录的空间;这样同一归档在磁盘上会占用约 3 倍空间。

我需要 Tika 和 Gotenberg 容器吗?

只有在您希望将 Word、Excel 或 OpenDocument 文件与 PDF 一起建立索引时才需要。它们会将这些格式转换为 PDF,供 paperless 进行 OCR 和搜索。它们还会增加两个运行中的容器,并额外占用几百 MB 内存;如果您归档的内容已经全部是 PDF 或图像文件,那么在小型服务器上可以跳过它们。

#paperless-ngx#documents#自托管#Docker#ocr