在VPS上使用Docker Compose自托管Paperless-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以获得更好结果。注意redo与PAPERLESS_OCR_CLEAN和PAPERLESS_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会自动重新训练。训练步骤:
- 在各分类中手动标记至少50个文档
- 确保这些文档不在收件箱中(分类器会忽略收件箱文档)
- 分类器按计划重新训练(默认:每小时通过
document_create_classifier) - 新文档开始接收自动分配
你可以手动触发重新训练:
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中配置。你可以添加邮件账户和邮件规则。
添加邮件账户:
- 进入Manage > Mail > Mail Accounts
- 设置IMAP服务器、端口(TLS用993)、用户名和密码
- 设置要监控的文件夹(如
INBOX)
添加邮件规则:
- 进入Manage > Mail > Mail Rules
- 选择邮件账户
- 选择消费内容:仅附件,或整封邮件转为PDF
- 设置已处理邮件的操作:标记为已读、移动到文件夹或删除
- 可选地为消费的文档分配标签、通讯人或文档类型
典型配置:将文档转发到专用邮箱(如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 团队原创作品。 未经书面许可,禁止复制、转载或再分发。