在VPS上使用Docker Compose自托管Paperless-ngx

2 分钟阅读·Matthieu·self-hostingdocker-composeocrdocument-managementpaperless-ngx|

在VPS上使用Docker Compose、PostgreSQL和Redis部署Paperless-ngx。配置OCR语言和模式,设置自动标签规则、邮件消费以及带异地同步的生产级备份策略。

Paperless-ngx将你的VPS变成一个可搜索的文档档案库。你扫描或上传文档,Paperless-ngx对其进行OCR识别,使其支持全文搜索,并自动分配标签和通讯人。它以Docker Compose堆栈形式运行,包含PostgreSQL、Redis和用于文档转换的Gotenberg。

本指南在已安装Docker和反向代理的VPS上部署Paperless-ngx。如果你还没有这些基础,先参考我们的Docker基础安装指南。

Paperless-ngx在VPS上需要什么资源?

Paperless-ngx最低需要2 GB内存,推荐4 GB。堆栈运行PostgreSQL、Redis、Gotenberg、Tika和Web服务器。空闲时总内存占用约800 MB。OCR处理扫描PDF时,单核CPU使用率飙升至100%,内存升至1.5-2 GB。每个文档平均占用5-10 MB磁盘空间(原件+存档+缩略图)。

组件 空闲内存 峰值内存(OCR) 每1,000个文档磁盘占用
Paperless-ngx webserver ~300 MB ~900 MB 5-10 GB
PostgreSQL ~50 MB ~100 MB ~500 MB
Redis ~10 MB ~10 MB 可忽略
Gotenberg ~150 MB ~300 MB
Tika ~250 MB ~400 MB
合计 ~760 MB ~1,710 MB 5-10 GB

根据文档量规划磁盘空间。家庭用户每月扫描50个文档,每年约需6 GB。小型企业每月500个文档应预留50-60 GB/年。

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

创建堆栈目录,生成密钥,编写Compose文件。每个服务都配置了健康检查,Docker会自动重启不健康的容器。

创建项目目录

mkdir -p /opt/paperless-ngx && cd /opt/paperless-ngx

生成密钥

绝不使用默认密码。生成强数据库密码和Django密钥:

openssl rand -base64 32 > .db_password
openssl rand -base64 48 > .secret_key
chmod 600 .db_password .secret_key

这些文件以受限权限保存在磁盘上。Compose文件在容器启动时读取它们。

编写环境文件

cat > .env << 'EOF'
COMPOSE_PROJECT_NAME=paperless
EOF

编写Compose文件

# docker-compose.yml
services:
  broker:
    image: docker.io/library/redis:8
    restart: unless-stopped
    volumes:
      - redisdata:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 30s
      timeout: 5s
      retries: 3

  db:
    image: docker.io/library/postgres:18
    restart: unless-stopped
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: paperless
      POSTGRES_USER: paperless
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U paperless"]
      interval: 30s
      timeout: 5s
      retries: 3

  gotenberg:
    image: docker.io/gotenberg/gotenberg:8
    restart: unless-stopped
    command:
      - "gotenberg"
      - "--chromium-disable-javascript=true"
      - "--chromium-allow-list=file:///tmp/.*"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 5s
      retries: 3

  tika:
    image: docker.io/apache/tika:latest
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9998/"]
      interval: 30s
      timeout: 5s
      retries: 3

  webserver:
    image: ghcr.io/paperless-ngx/paperless-ngx:latest
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
      broker:
        condition: service_healthy
      gotenberg:
        condition: service_healthy
      tika:
        condition: service_healthy
    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
    environment:
      PAPERLESS_REDIS: redis://broker:6379
      PAPERLESS_DBHOST: db
      PAPERLESS_DBUSER: paperless
      PAPERLESS_DBPASS_FILE: /run/secrets/db_password
      PAPERLESS_TIKA_ENABLED: 1
      PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
      PAPERLESS_TIKA_ENDPOINT: http://tika:9998
      PAPERLESS_SECRET_KEY_FILE: /run/secrets/secret_key
      PAPERLESS_OCR_LANGUAGE: eng
      PAPERLESS_OCR_MODE: skip
      PAPERLESS_OCR_OUTPUT_TYPE: pdfa
      PAPERLESS_FILENAME_FORMAT: "{created_year}/{correspondent}/{title}"
      PAPERLESS_URL: https://paperless.example.com
      USERMAP_UID: 1000
      USERMAP_GID: 1000
    secrets:
      - db_password
      - secret_key
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 60s

secrets:
  db_password:
    file: .db_password
  secret_key:
    file: .secret_key

volumes:
  data:
  media:
  pgdata:
  redisdata:

关于这个配置:

  • **端口只绑定到127.0.0.1。**反向代理通过HTTPS处理公共访问。将8000端口暴露到0.0.0.0会绕过TLS和防火墙。
  • 密钥使用Docker secrets(带_FILE后缀的变量)。密码不会出现在docker inspect输出或进程列表中。
  • **USERMAP_UID/USERMAP_GID**将容器用户映射到宿主机的UID 1000。consume和export目录中创建的文件归此用户所有。
  • **PAPERLESS_URL**必须与你的公共域名一致。Paperless-ngx用它生成分享链接和邮件URL。将paperless.example.com替换为你的实际域名。

启动堆栈

docker compose up -d

等待约60秒让所有服务初始化。检查每个容器是否健康:

docker compose ps
NAME                   SERVICE      STATUS                  PORTS
paperless-broker-1     broker       running (healthy)
paperless-db-1         db           running (healthy)
paperless-gotenberg-1  gotenberg    running (healthy)
paperless-tika-1       tika         running (healthy)
paperless-webserver-1  webserver    running (healthy)       127.0.0.1:8000->8000/tcp

五个容器都应显示(healthy)。如果有容器显示(health: starting),再等30秒。如果某个容器持续显示(unhealthy),查看其日志:

docker compose logs gotenberg --tail 20

创建超级用户

docker compose exec webserver createsuperuser

按提示输入用户名、邮箱和密码。这是你的Web界面管理员账户。

访问Web界面

如果反向代理已配置,在浏览器中打开https://paperless.example.com。Paperless-ngx登录页面加载完成。如果你还在配置反向代理,可以本地测试:

curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8000
200

应该配置什么OCR语言和模式?

Paperless-ngx使用Tesseract进行OCR。通过环境变量配置语言和处理模式。默认值适用于英文文档,但大多数用户需要调整。

OCR语言

PAPERLESS_OCR_LANGUAGE设置为文档对应的Tesseract三字母语言代码。多种语言用+连接:

# 单一语言
PAPERLESS_OCR_LANGUAGE: deu

# 多种语言
PAPERLESS_OCR_LANGUAGE: deu+eng+fra

常用语言代码:eng(英语)、deu(德语)、fra(法语)、spa(西班牙语)、ita(意大利语)、nld(荷兰语)、por(葡萄牙语)、chi_sim(简体中文)。完整列表见Tesseract文档。

容器镜像包含大多数语言包。向PAPERLESS_OCR_LANGUAGE添加更多语言会增加每页OCR处理时间。

OCR模式

PAPERLESS_OCR_MODE设置控制Paperless-ngx如何处理已包含文本层的文档(数字创建的PDF中很常见)。

模式 行为 适用场景
skip 只对没有文本的页面进行OCR。始终创建存档副本。 **默认值。**适合混合输入:扫描+数字PDF。
skip_noarchive skip相同,但不为已有文本的文档创建存档。 想节省数字PDF的磁盘空间。
redo 重新OCR所有页面,替换现有文本层。 收到OCR质量差的文档。
force 光栅化文档并从头OCR。会破坏原始文本。 最后手段。生成更大的文件,文本清晰度更低。

对于大多数场景,skip是正确选择。它既处理扫描文档也处理数字文档,不会浪费CPU重新处理已可搜索的文档。

如果你收到的文档来自自带(低质量)OCR的扫描仪,将模式设为redo以获得更好结果。注意redoPAPERLESS_OCR_CLEANPAPERLESS_OCR_DESKEW不兼容。

OCR输出类型

PAPERLESS_OCR_OUTPUT_TYPE: pdfa(默认值)生成PDF/A文件,即档案标准格式。它们是自包含的,嵌入了字体。除非有特殊原因,否则保持此设置。

修改任何OCR设置后,重启webserver容器:

docker compose restart webserver

现有文档不会重新处理。要重新OCR某个文档,在Web界面中使用「Redo OCR」操作。

Paperless-ngx的自动标签是如何工作的?

Paperless-ngx支持六种匹配算法,用于标签、通讯人和文档类型。当文档到达时,每个标签将文档内容与其匹配模式进行比对。如果模式匹配,标签自动应用。

算法 工作原理 适用场景 示例
Any 匹配字段中任一词出现即匹配。 宽泛分类。 invoice receipt bill
All 所有词都出现才匹配(顺序无关)。 更精确的匹配,不依赖位置。 electricity quarterly
Exact 按顺序精确匹配短语。 公司名称、账号。 Acme Corp
Regular Expression 完整正则表达式匹配。 结构化数据:日期、参考编号、金额。 Invoice\s+#?\d{4,}
Fuzzy 可配置阈值的近似匹配。 OCR输出不一致、轻微拼写错误。 Stadtwerke(匹配Stadtwenke
Auto 从你的手动分配中学习的ML分类器。 手动标记约50+文档后。 (从你的纠正中学习)

创建带匹配规则的标签

在Web界面中,进入Manage > Tags创建标签。设置匹配算法Match字段。一些实际示例:

  • 标签:Invoice,算法:Any,Match:invoice rechnung facture(捕获多语言发票)
  • 标签:Bank,算法:Regular Expression,Match:IBAN\s*[A-Z]{2}\d{2}(匹配任何IBAN号码)
  • 标签:Medical,算法:All,Match:patient diagnosis(要求两个词都出现)
  • 通讯人:Electric Company,算法:Exact,Match:Springfield Energy Inc

训练Auto分类器

Auto算法使用机器学习分类器,Paperless-ngx会自动重新训练。训练步骤:

  1. 在各分类中手动标记至少50个文档
  2. 确保这些文档不在收件箱中(分类器会忽略收件箱文档)
  3. 分类器按计划重新训练(默认:每小时通过document_create_classifier
  4. 新文档开始接收自动分配

你可以手动触发重新训练:

docker compose exec webserver document_create_classifier

分类器随着你的纠正不断改进。每次纠正都会反馈到下一个训练周期。

如何在Paperless-ngx中设置文档消费?

Paperless-ngx通过三个通道接收文档:Web界面手动上传、磁盘上的监视目录和通过IMAP获取邮件。

Web界面上传

在Web界面中拖放文件。安装后立即可用。支持PDF、PNG、JPEG、TIFF以及(启用Tika后)DOCX、XLSX、ODT和其他办公格式。

监视目录(consume目录)

Compose文件中映射的./consume目录由inotify监视器监控。放入一个文件,Paperless-ngx在几秒内就会处理它。

cp /tmp/scan-001.pdf /opt/paperless-ngx/consume/

处理完成后文件从consume目录消失。要按标签组织消费,启用子目录标签功能:

PAPERLESS_CONSUMER_RECURSIVE: true
PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS: true

使用此设置,放在consume/invoices/scan.pdf的文件会自动获得invoices标签。

邮件消费(IMAP)

邮件消费完全在Web界面的Manage > Mail中配置。你可以添加邮件账户和邮件规则。

添加邮件账户:

  1. 进入Manage > Mail > Mail Accounts
  2. 设置IMAP服务器、端口(TLS用993)、用户名和密码
  3. 设置要监控的文件夹(如INBOX

添加邮件规则:

  1. 进入Manage > Mail > Mail Rules
  2. 选择邮件账户
  3. 选择消费内容:仅附件,或整封邮件转为PDF
  4. 设置已处理邮件的操作:标记为已读、移动到文件夹或删除
  5. 可选地为消费的文档分配标签、通讯人或文档类型

典型配置:将文档转发到专用邮箱(如scan@yourdomain.com),配置Paperless-ngx监视该收件箱并消费附件。处理后原件移动到IMAP的「Archived」文件夹。

如何组织存储路径和文件名?

Paperless-ngx在两个位置存储文档:media卷包含磁盘上的文件,PostgreSQL管理元数据。PAPERLESS_FILENAME_FORMAT变量控制media目录中归档文件的命名方式。

上面的Compose文件使用:

PAPERLESS_FILENAME_FORMAT: "{created_year}/{correspondent}/{title}"

这会创建如下结构:

media/documents/archive/
  2026/
    Springfield Energy Inc/
      Electricity Bill January 2026.pdf
    Dr. Smith/
      Lab Results March 2026.pdf

可用的模板变量包括{created_year}{created_month}{created_day}{correspondent}{document_type}{title}{tag_list}{owner_username}。如果变量为空(未分配通讯人),Paperless-ngx将其替换为none

更改文件名格式后,将其应用到现有文档:

docker compose exec webserver document_renamer

Paperless-ngx的最佳备份策略是什么?

有效的备份应覆盖PostgreSQL数据库、文档文件(原件+存档+缩略图)和Paperless-ngx元数据(标签、通讯人、匹配规则)。文档导出器以可移植格式捕获所有内容。将其与数据库转储配合使用,以便更快地进行纯数据库恢复。

数据库转储

docker compose exec db pg_dump -U paperless paperless | gzip > /opt/paperless-ngx/backups/db-$(date +%Y%m%d).sql.gz

文档导出器

导出器创建完整快照:文档、元数据和清单。这是标准的备份方法。

docker compose exec webserver document_exporter ../export -c -d

-c标志比较校验和(只导出已更改的文件)。-d标志从导出中删除Paperless-ngx中已不存在的文件。两者配合使导出目录成为当前状态的镜像。

用cron自动化

创建备份脚本:

cat > /opt/paperless-ngx/backup.sh << 'SCRIPT'
#!/bin/bash
set -euo pipefail

BACKUP_DIR="/opt/paperless-ngx/backups"
EXPORT_DIR="/opt/paperless-ngx/export"

mkdir -p "$BACKUP_DIR"

# Database dump
docker compose -f /opt/paperless-ngx/docker-compose.yml exec -T db \
  pg_dump -U paperless paperless | gzip > "$BACKUP_DIR/db-$(date +%Y%m%d).sql.gz"

# Document exporter
docker compose -f /opt/paperless-ngx/docker-compose.yml exec -T webserver \
  document_exporter ../export -c -d

# Remove database dumps older than 30 days
find "$BACKUP_DIR" -name "db-*.sql.gz" -mtime +30 -delete

echo "[$(date -Is)] Backup completed" >> "$BACKUP_DIR/backup.log"
SCRIPT

chmod 700 /opt/paperless-ngx/backup.sh

用cron调度(保留现有crontab条目):

(crontab -l 2>/dev/null; echo "0 3 * * * /opt/paperless-ngx/backup.sh") | crontab -

这会添加一个每晚03:00执行的任务。

异地同步

导出目录和数据库转储需要离开服务器。使用rsync或rclone推送到第二个位置。对于S3兼容目标:

rclone sync /opt/paperless-ngx/export remote:paperless-backup/export
rclone sync /opt/paperless-ngx/backups remote:paperless-backup/db-dumps

对于通过SSH连接的另一台服务器:

rsync -az --delete /opt/paperless-ngx/export/ backup-server:/backups/paperless/export/
rsync -az /opt/paperless-ngx/backups/ backup-server:/backups/paperless/db-dumps/

关于Docker卷备份策略的更多信息,参见我们的Docker卷备份指南。

如何恢复Paperless-ngx备份?

恢复需要全新安装Paperless-ngx。启动堆栈,然后导入。

仅恢复数据库(更快,适用于数据库损坏或迁移):

gunzip < /opt/paperless-ngx/backups/db-20260320.sql.gz | \
  docker compose exec -T db psql -U paperless paperless

从导出器完整恢复(文档、标签、通讯人,全部):

docker compose exec webserver document_importer ../export

对空数据库运行导入器。它会重建所有元数据并从导出目录链接文件。

定期测试你的恢复流程。从未恢复过的备份不是备份。

如何加固Paperless-ngx容器?

上面的Compose文件已经处理了基础部分:密钥文件而非明文密码、端口绑定到localhost、用户映射。以下是额外的加固措施。

移除不必要的能力

为webserver服务添加安全选项:

webserver:
  # ... 现有配置 ...
  security_opt:
    - no-new-privileges:true

这防止容器进程通过setuid二进制文件获取额外权限。

设置资源限制

防止失控的OCR进程耗尽所有服务器资源:

webserver:
  # ... 现有配置 ...
  deploy:
    resources:
      limits:
        memory: 2g
        cpus: "2.0"
      reservations:
        memory: 512m

关于资源限制的更多信息,参见我们的Docker Compose资源限制指南。

隐藏版本信息

配置反向代理移除Server头。使用Nginx:

server_tokens off;

Paperless-ngx本身不会在HTTP头中暴露版本,但你的反向代理可能会。

如何更新Paperless-ngx?

拉取最新镜像并重建容器。Paperless-ngx镜像在启动时自动运行数据库迁移。

cd /opt/paperless-ngx
docker compose pull
docker compose up -d

更新后检查日志中的迁移输出:

docker compose logs webserver --tail 30

查找类似Applying documents.XXXX_migration_name... OK的行。如果迁移失败,容器会停止。在大版本升级前查看发布说明。

更新前务必运行备份脚本。

遇到问题了?

**容器持续unhealthy:**用docker compose logs <service> --tail 50查看日志。常见原因:PostgreSQL密码不匹配(重新生成.db_password并重建数据库卷),Redis连接被拒绝(broker尚未启动)。

**OCR输出乱码:**语言设置错误。检查PAPERLESS_OCR_LANGUAGE是否与你的文档匹配。对于多语言文档,用+分隔添加所有相关语言代码。

**上传后文档不显示:**查看consumer日志:

docker compose logs webserver --tail 50 | grep -i consumer

常见原因:文件权限不匹配。consume目录必须对UID 1000(或USERMAP_UID的值)可写:

chown -R 1000:1000 /opt/paperless-ngx/consume

**自动标签不工作:**分类器至少需要约50个手动标记的文档才能产生结果。检查分类器是否已训练:

docker compose exec webserver document_create_classifier

**磁盘空间增长过快:**检查哪些文档最大:

docker compose exec webserver document_sanity_checker

同时检查你的OCR模式。force模式创建的存档文件比skip大得多。

**邮件消费不获取邮件:**在Web界面中检查IMAP凭据。触发手动获取以查看错误:

docker compose exec webserver mail_fetcher

更多自托管指南,参见我们的Immich指南了解在同一堆栈上管理照片。


版权所有 2026 Virtua.Cloud。保留所有权利。 本内容为 Virtua.Cloud 团队原创作品。 未经书面许可,禁止复制、转载或再分发。

准备好亲自尝试了吗?

几秒内部署您自己的服务器。支持 Linux、Windows 或 FreeBSD。

查看 VPS 方案