GuidesFiles, photos and homeDocuments with Paperless

Go paperless with Paperless-ngx and rootless Podman

Paperless-ngx on Debian 13 scans your receipts, invoices and letters into a searchable archive with Swedish text recognition, in rootless Podman under a user of its own behind Caddy, with nightly exports you can restore.

Tested on Paperless-ngx 3.2.1 on Debian 13 (trixie) on a Melonslab server Updated October 2, 2026

Recommended server for this guide

VC-S Micro · 2 vCPU · 8 GB Memory · 250 GB Storage

Month to month, no lock-in 7-day money-back guarantee

€7.99/mo

Deploy now
On this page

What you will set up

Paperless-ngx turns paper into a searchable archive. You scan or photograph a receipt, an invoice or a letter, and Paperless reads the text (OCR), finds the date, and keeps both your original file and a searchable PDF copy. A search for "elnät" or "förfallodatum" then finds every document that contains the word. It also takes Word documents and emails.

Here it runs as a pod under a user of its own called paperless, behind Caddy from the Podman guide. The pod holds Paperless itself, PostgreSQL for its database, Valkey for its task queue, and Apache Tika and Gotenberg, which read Office files and emails. These are the parts that Paperless's own setup files use.

Paperless-ngx is a community project, developed by volunteers in the paperless-ngx organisation on GitHub. It is the successor to the earlier Paperless and Paperless-ng projects. There is no company behind it, and the project names no country. It is open source under the GNU GPL v3: you may use, change and share it, and changed versions you share must use the same licence. In our test it contacted no online service on its own. Its update check is off by default; if you switch it on, the server asks GitHub's API (api.github.com, run by GitHub in the US) for the latest version number. The Swedish language data in this guide is installed from Debian's package servers (deb.debian.org) each time the container starts.

Every step below was run on a fresh Melonslab VC-P Alloy (2 vCPU, 8 GB) with Debian 13:

  • The admin account was created before the site went online, and sign-ups were closed from the outside.
  • Swedish test documents (a scanned receipt, a two-page invoice, a letter photographed as a JPEG, a Word file and an email) were read correctly, å, ä and ö included, and a search for words such as "förråd", "tvättstugan" and "Älvsbacka" found them.
  • OCR took about 3.5 seconds per page using both vCPUs, and a two-page invoice was searchable about 11 seconds after upload.
  • Documents went in through the web page on a phone-sized screen, and through SFTP and rsync into the consume folder.
  • Failed logins were logged with the visitor's real IP address, and the login limit applied per visitor.
  • A full export was imported into a new, empty installation: all documents, the search, and the account and its password came back, and the originals were byte for byte the same.
  • In six minutes of capturing the server's traffic, idle and while in use, Paperless made no outgoing connections.
  • Everything came back by itself after a reboot.

The pod used about 870 MB of memory when idle. Paperless itself peaked at about 850 MB during OCR, and after the first Word file and email, Tika and Gotenberg stayed at about 220 and 240 MB.

Before you start

You need:

  • a server set up as in the Podman guide, with Caddy running;
  • an A record and an AAAA record for paperless.example.com pointing at your server;
  • about 6 GB of free disk for the images, plus about twice the size of the documents you will store.

The examples use paperless.example.com for the site, and 198.51.100.7 for your own IP address. Replace them throughout.

1. Create the user

As root:

useradd -m -s /bin/bash paperless
loginctl enable-linger paperless
machinectl shell paperless@

Everything up to step 5 runs as paperless.

2. Create the secrets

openssl rand -hex 24 | tr -d '\n' | podman secret create paperless-db-password -
openssl rand -base64 48 | tr -d '\n' | podman secret create paperless-secret-key -

The first is the database password. The second, PAPERLESS_SECRET_KEY, signs logins and tokens, and Paperless requires it.

3. Define the pod

mkdir -p ~/.config/containers/systemd ~/consume ~/export
cd ~/.config/containers/systemd

~/consume is the folder Paperless takes new documents from, and ~/export is where its backups go.

Create paperless.pod:

[Pod]
PodName=paperless
# Only Caddy, on this server, can reach Paperless: the port is not open to the internet.
PublishPort=127.0.0.1:8114:8000

[Install]
WantedBy=default.target

Create paperless-db.container:

[Container]
ContainerName=paperless-db
Image=docker.io/library/postgres:18
Pod=paperless.pod
Volume=paperless-db:/var/lib/postgresql
Environment=POSTGRES_DB=paperless POSTGRES_USER=paperless
Secret=paperless-db-password,type=env,target=POSTGRES_PASSWORD
AutoUpdate=registry

[Service]
Restart=always

Create paperless-broker.container:

[Container]
ContainerName=paperless-broker
Image=docker.io/valkey/valkey:9-alpine
Pod=paperless.pod
Volume=paperless-broker:/data
AutoUpdate=registry

[Service]
Restart=always

Create paperless-tika.container:

[Container]
ContainerName=paperless-tika
Image=docker.io/apache/tika:3.3.1.0
Pod=paperless.pod
AutoUpdate=registry

[Service]
Restart=always

Create paperless-gotenberg.container:

[Container]
ContainerName=paperless-gotenberg
Image=docker.io/gotenberg/gotenberg:8.37
Pod=paperless.pod
# Emails are turned into PDFs in a browser: no JavaScript, and no loading of images or trackers from the internet.
Exec=gotenberg --chromium-disable-javascript=true --chromium-allow-list=file:///tmp/.*
AutoUpdate=registry

[Service]
Restart=always

And paperless-app.container:

[Unit]
After=paperless-db.service paperless-broker.service paperless-tika.service paperless-gotenberg.service

[Container]
ContainerName=paperless-app
Image=ghcr.io/paperless-ngx/paperless-ngx:latest
Pod=paperless.pod
Volume=paperless-data:/usr/src/paperless/data
Volume=paperless-media:/usr/src/paperless/media
Volume=%h/consume:/usr/src/paperless/consume
Volume=%h/export:/usr/src/paperless/export
Environment=USERMAP_UID=0 USERMAP_GID=0
Environment=PAPERLESS_URL=https://paperless.example.com
Environment=PAPERLESS_ALLOWED_HOSTS=paperless.example.com
Environment='PAPERLESS_PROXY_SSL_HEADER=["HTTP_X_FORWARDED_PROTO", "https"]'
Environment=PAPERLESS_ALLAUTH_TRUSTED_PROXY_COUNT=1
Environment=PAPERLESS_TIME_ZONE=Europe/Stockholm
Environment=PAPERLESS_DATE_ORDER=YMD
Environment=PAPERLESS_OCR_LANGUAGE=swe+eng PAPERLESS_OCR_LANGUAGES=swe
Environment=PAPERLESS_REDIS=redis://127.0.0.1:6379
Environment=PAPERLESS_DBENGINE=postgresql PAPERLESS_DBHOST=127.0.0.1
Environment=PAPERLESS_TIKA_ENABLED=1 PAPERLESS_TIKA_ENDPOINT=http://127.0.0.1:9998 PAPERLESS_TIKA_GOTENBERG_ENDPOINT=http://127.0.0.1:3000
Secret=paperless-db-password,type=env,target=PAPERLESS_DBPASS
Secret=paperless-secret-key,type=env,target=PAPERLESS_SECRET_KEY
AutoUpdate=registry

[Service]
Restart=always
TimeoutStartSec=900

What the settings do:

  • USERMAP_UID=0 runs Paperless as root inside its container, which in rootless Podman is the paperless user on the server. The files in ~/consume, ~/export and the volumes then belong to paperless, so you can copy documents in over SFTP and read the exports without extra steps.
  • PAPERLESS_URL and PAPERLESS_ALLOWED_HOSTS give Paperless its address, so that logins work through Caddy and requests for any other name get 400. PAPERLESS_PROXY_SSL_HEADER tells it that Caddy serves it over HTTPS, so the links it makes start with https://.
  • PAPERLESS_ALLAUTH_TRUSTED_PROXY_COUNT=1 makes Paperless take the visitor's address from the X-Forwarded-For header that Caddy sets, for its login limit. Only Caddy can reach the port, so the header cannot be faked. Step 5 shows how to check it.
  • PAPERLESS_OCR_LANGUAGES=swe installs Swedish for Tesseract, the OCR program, and PAPERLESS_OCR_LANGUAGE=swe+eng reads every document as Swedish and English. Without the Swedish data, our test invoice came out as "Bjorkvagen" and "férbrukning", and a search for "förbrukning" found nothing.
  • PAPERLESS_DATE_ORDER=YMD matches Swedish dates such as 2026-09-30. With the default, DMY, Paperless found no date in our documents and used the day of upload instead.

Tika and Gotenberg let Paperless read Word, Excel and LibreOffice files and emails (.eml). They take 2.2 GB of disk for their images, and about 170 MB of memory at rest, rising to about 470 MB once they have converted a file. If you only scan paper, leave out their two files, the PAPERLESS_TIKA_ line and their names on the After= line: according to Paperless's documentation, it then takes PDFs, images and plain text only.

Start the pod:

systemctl --user daemon-reload
systemctl --user start paperless-pod
systemctl --user enable --now podman-auto-update.timer

The first start downloads about 5.4 GB of images, and took about three minutes. Paperless is ready when its log shows celery@paperless ready:

podman logs -f paperless-app

Press Ctrl+C to stop following the log.

4. Create your admin account first

Until an account exists, Paperless's sign-up page says "This is the first user account for this installation and will be granted superuser privileges.", and anyone who opens it gets that account. Create yours now, before Caddy puts the site online:

podman exec -it paperless-app python3 manage.py createsuperuser

It asks for a Username, an Email address and a password twice, and answers Superuser created successfully. After that, the sign-up page only says Sign Up Closed, and it stays closed: new accounts are made by you under Users & Groups.

5. Put Caddy in front

Go back to root with exit, switch to machinectl shell caddy@, and add this block at the end of ~/Caddyfile:

paperless.example.com {
    reverse_proxy 127.0.0.1:8114
}

Restart Caddy with systemctl --user restart caddy.

From your own computer, check that sign-ups are closed:

curl -s https://paperless.example.com/accounts/signup/ | grep -o 'Sign Up Closed'

Then log in at https://paperless.example.com with a wrong password once, and look at the log as paperless:

podman exec paperless-app tail -n 2 /usr/src/paperless/data/log/paperless.log
[2026-10-02 18:53:39,224] [INFO] [paperless.auth] Login failed for user `anna` from IP `198.51.100.7`.

Your own address there means Paperless sees each visitor's real IP. In our test, a dozen failed logins from one address gave Too many failed login attempts. Try again later. to that address only, while a login from another address still worked.

6. Get documents in

In the browser. On the Dashboard, choose Upload documents or drop files anywhere on the page. The web interface works on a phone: we logged in and uploaded a JPEG on a phone-sized screen, and Upload documents opens the phone's own file picker. Paperless makes no phone app of its own; apps by others are listed in the project's wiki, and we did not test them.

Through the consume folder. Anything copied into ~/consume is taken in within seconds and then removed from the folder. That suits a scanner or a script. To copy files there from your computer, let your SSH key log in as paperless. As root, this gives paperless the same keys as root:

install -d -m 700 -o paperless -g paperless /home/paperless/.ssh
install -m 600 -o paperless -g paperless /root/.ssh/authorized_keys /home/paperless/.ssh/

Then, from your computer:

rsync kvitto.pdf faktura.pdf paperless@paperless.example.com:consume/

Or with SFTP, which most file managers and scanner apps can use:

sftp paperless@paperless.example.com
sftp> put brev.pdf consume/

By email. Paperless can also fetch documents from a mailbox over IMAP, under Mail in the menu. We did not test it.

When a document is done, it shows up under Documents. Open it to see the text Paperless read on the Content tab, and the date it found under Date created.

7. Search in Swedish

The search box at the top searches the text of every document. In our test, these all found the right document:

  • "förråd", "tvättstugan", "Skruvdragare", "säkring" and "trädgårdsskötsel";
  • "forrad" and "oberg", typed without å, ä and ö, also found "förråd" and "Öberg";
  • "764,78", an amount from an invoice.

OCR is not perfect. On the second page of our invoice, small text gave "pa" for "på" and "vader" for "väder", while the larger text on the first page was read without errors. A clear scan at 300 dpi helps.

8. Where your documents are

Paperless keeps up to three files for each document, in the paperless-media volume, ~/.local/share/containers/storage/volumes/paperless-media/_data/documents/:

  • originals: the file exactly as you uploaded it, never changed;
  • archive: a PDF/A copy with the recognised text, which is what you search and download by default;
  • thumbnails: the small previews.

For scans, the archive copy is about the size of the original, so plan for about twice the size of your scans. Our two-page invoice was 467 KB, its archive copy 490 KB and its thumbnail 12 KB. The database and search index are in the paperless-db and paperless-data volumes.

9. Back up

Paperless has its own export, document_exporter, which writes every original, archive copy and thumbnail together with a manifest.json holding the database: documents, tags, users with their password hashes, and settings. It can be imported into a new installation. Keep it as safe as the documents themselves.

As paperless, create ~/backup.sh:

#!/bin/sh
set -e
# Every document with its metadata, users and settings, in a form Paperless can import.
podman exec paperless-app document_exporter ../export --delete --no-progress-bar
# The database as well, as a second copy.
podman exec paperless-db pg_dump -U paperless paperless > ~/backup/paperless-db.sql

--delete removes files from the export when you delete the document in Paperless, so the export matches what you have. Then:

chmod 700 ~/backup.sh
mkdir -p ~/backup ~/.config/systemd/user

Create ~/.config/systemd/user/paperless-backup.service:

[Unit]
Description=Export Paperless-ngx for backup

[Service]
Type=oneshot
ExecStart=%h/backup.sh

And ~/.config/systemd/user/paperless-backup.timer:

[Unit]
Description=Export Paperless-ngx every night

[Timer]
OnCalendar=*-*-* 23:30
Persistent=true

[Install]
WantedBy=timers.target
systemctl --user daemon-reload
systemctl --user enable --now paperless-backup.timer
systemctl --user start paperless-backup.service
ls ~/export ~/backup

The export runs at 23:30, before Podman's update check at midnight. The files are still on the same server, so copy /home/paperless/export and /home/paperless/backup to another machine every night. The restic guide does that, and its backup of /home includes both.

10. Restore

We restored a full export into a new installation like this:

  1. Set up the user, secrets and pod as in steps 1 to 3, but do not create an account and do not add the Caddy block yet.
  2. Copy your export into /home/paperless/export.
  3. As paperless, import it:
podman exec paperless-app document_importer ../export

It copies the files, then rebuilds the search index. Then add the Caddy block from step 5 and log in with your old account and password. In our test, all documents, tags and dates came back, the search worked, and the originals were identical to the ones we uploaded.

The importer only works on an empty Paperless. The paperless-db.sql dump is a second copy of the database for PostgreSQL itself, should you ever need it.

11. Updates

Every container has AutoUpdate=registry, so the timer from step 3 checks every night for new images and restarts the pod if one changed. When Paperless starts on a new version, it updates its database by itself. To see what it would update now:

podman auto-update --dry-run

paperless-ngx:latest follows every new release, including a new major version. PostgreSQL stays on version 18: a move to 19 needs its data converted, which the export and import above can do. Tika and Gotenberg are pinned to the versions in Paperless's own Docker Compose file; when Paperless moves them, change the tags in your two files.

After a reboot, the pod started by itself and Paperless was ready about a minute and a half later. Each start installs the Swedish language data again, so Debian's package servers must be reachable; Paperless's start-up steps took 44 to 50 seconds in our test.

Troubleshooting

A document gets today's date instead of its own. Paperless uses the first date in the text that it can read and that is not in the future. Check that PAPERLESS_DATE_ORDER=YMD is set: with DMY, dates like 2026-09-30 are not read at all. In our letter, the date came right after a word, "Västerås 2026-09-30", and Paperless still missed it. Correct the date under Date created and choose Save.

The log says "Regular expression finditer timed out" during consumption. Paperless gives its date search 0.1 seconds, and on our 2 vCPU server the first document after a start sometimes ran over. The document is still saved, with the upload date. Correct the date by hand as above.

The log says "Login failed for user ... Unable to determine IP address." PAPERLESS_TRUSTED_PROXIES is set. With it, Paperless looks for the proxy's address inside X-Forwarded-For, but Caddy's connection comes from the server's own address and Caddy does not add it there. Remove the line, keep PAPERLESS_ALLAUTH_TRUSTED_PROXY_COUNT=1, then run systemctl --user daemon-reload and systemctl --user restart paperless-app.

Caddy answers 502 for a minute after a restart. Paperless is still starting, including the Swedish install. Wait for celery@paperless ready in podman logs paperless-app.

Paperless cannot install Swedish, and the log says "Unable to install language tesseract-ocr-swe as non-root". The container runs as a user other than root, for example because of a UserNS=keep-id line in the pod file. Use USERMAP_UID=0 and USERMAP_GID=0 as in step 3 instead. We tried keep-id first: besides this, it made Podman keep a second copy of every image, about 5 GB more disk.

Run it on your own server

VC-S Micro

€7.99/mo

vCPU
2
Memory
8 GB
Storage
250 GB
Transfer
10 TB
Standard
HDD · RAID 10
  • Full root access
  • Native /64 IPv6
  • RAID-protected storage
  • Malmö, Sweden
  • Month to month, no lock-in
  • 7-day money-back guarantee
All guides