SSD Nodes Learn 8GB 内存 — 每年 $66
指南 Matt Connor作者: Matt Connor · 更新于 2026-08-01

VPS 部署 Paperless-ngx:Docker 自托管文档归档

在 VPS 上用 Docker Compose 部署 Paperless-ngx,配置官方 PostgreSQL 堆栈、PAPERLESS_URL、consume 文件夹、OCR 语言、HTTPS 与备份,避开主机名和内存配置错误。

你要构建的系统

VPS 上的 Paperless-ngx 可以将扫描纸质文件的目录转换为可搜索的存档。将 PDF 放入受监控的目录后,服务器会对其运行 OCR(光学字符识别),提取文本,推测日期和通信对象,然后归档文件。安装过程只需使用一个包含 4 个服务的 Docker Compose 文件。之后的工作都是配置。本指南的大部分内容也集中在配置上,因为安装通常会在这里出错。

Paperless-ngx 是原始 Paperless 项目的社区维护分支。它免费、支持自托管,并将文档以普通文件形式存储在磁盘上,因此您始终可以访问自己的存档。在 VPS 上运行它,而不是运行在家用设备上,意味着您可以从任何位置访问扫描文件,无需在家庭路由器上开放端口;它还适合与用于存储非纸质文件的私有 Nextcloud 实例配合使用。

该堆栈实际运行的组件

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

  • 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 工作进程同时常驻时,轻量使用场景下 2 GB 内存足够。如果您计划导入数百份扫描件的积压文件,请分配 4 GB 内存。因为大型多页 PDF 的 OCR 处理会产生内存峰值,可能导致内核的内存不足终止程序杀死工作进程。
  • 磁盘:您的存档会保存两份,一份是原始文件,另一份是经过 OCR 处理的存档 PDF。因此,磁盘空间应按扫描件大小的大约两倍进行规划。

获取官方 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,然后再尝试访问浏览器。首次启动还会运行数据库迁移,这需要一到两分钟。

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

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 绑定挂载到容器中。放入该目录的任何文件都会被导入,然后从该目录中删除,因为文件现在由 paperless 管理并存储在媒体卷中。

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

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

备份数据库和媒体文件

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

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

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

在全新的堆栈上,从同一目录执行 document_importer 即可恢复。这意味着只需确保导出目录安全。按计划将其发送到异地,并使用 来自 VPS 的加密、去重 restic 备份;先运行导出,再运行 restic,避免 restic 捕获尚未写入完成的归档。

通过检查 export/manifest.json 是否存在,并确认文件数量是否与界面中的文档数量一致,验证备份。未曾列出内容的备份不能视为有效备份。

FAQ

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

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

我将 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。若将导出目录保存在同一磁盘上,还要加上该目录的空间,这样同一份存档在磁盘上会占用约三倍空间。

我需要 Tika 和 Gotenberg 容器吗?

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

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