Paperless-ngx
Paperless-ngx turns scanned paper into a searchable archive: a document dropped into a folder is OCR’d, classified and filed, and its text becomes searchable. Five containers — the application with its workers and consumer, a database, a cache, and two converters it hands other formats to.
Last run on a host 8 Nov 2026 · data restored from a backup
Pinned at 3.4.2 (released 3 Nov 2026) · the newest upstream release
In the upstream images: 2 CRITICAL · 39 HIGH with a published fix — per image
To try it: Docker, and the OCR language packs you intend to use. See Try it locally.
To deploy it behind the proxy: Traefik running, a domain, and the OCR languages you intend to use. SMTP is optional.
Try it locally
Section titled “Try it locally”On your own machine, with no proxy, domain or certificate. From apps/paperless-ngx/:
cp .env.local.example .env.local
# The secrets ship empty; generate the ones this stack needs{ echo "DB_PASSWORD=$(openssl rand -hex 32)" echo "SECRET_KEY=$(openssl rand -base64 48 | tr -d '\n')" echo "ADMIN_PASSWORD=$(openssl rand -base64 24 | tr -d '\n')"} >> .env.local
mkdir -p volumes/local/redissudo chown 999:1000 volumes/local/redis
docker compose -f docker-compose.local.yml --env-file .env.local up -dThen open http://localhost:8000. Stop it again with the same command and down.
It archives what you already have; it does not open or edit a document. For that, see document editing in the browser.
Installation
Section titled “Installation”From apps/paperless-ngx/:
-
Copy the environment file and set the values for this host:
Terminal window cp .env.example .envVariable What it is APP_TRAEFIK_HOSTthe hostname Traefik routes to this stack PAPERLESS_OCR_LANGUAGEthe language used when a document carries no language metadata PAPERLESS_OCR_LANGUAGESevery language pack to install, space-separated ( deu eng) — a comma makes one unknown package nameUSERMAP_UID/USERMAP_GIDthe host user that owns volumes/—1000:1000on a default Debian installTZthe timezone timestamps are written in Language codes are ISO 639-2, three letters:
deu,eng,fra,spa,ita,ron. The Tesseract language data lists the rest.Set them now, before the first document is consumed. A language pack that is not installed is not an error: Tesseract falls back to English, and the document is archived with the wrong text.
-
Generate the two secrets and create the mail password file.
tr -d '\n'is not optional — a trailing newline is part of the value, and authentication fails with a password that looks right:Terminal window mkdir -p .secretsopenssl rand -base64 32 | tr -d '\n' > .secrets/db_pwd.txtopenssl rand -base64 48 | tr -d '\n' > .secrets/secret_key.txt: > .secrets/email_pwd.txt # compose mounts it; empty while mail is off (see Mail) -
Create the volumes and hand them to the user the application drops to:
Terminal window mkdir -p volumes/{data,media,consume,export,postgres,redis}sudo chown -R 1000:1000 volumes/data volumes/media volumes/consume volumes/exportsudo chown 999:1000 volumes/redis # Redis cannot fix this ownership itselfUse the same UID and GID you set in step 1. A mismatch surfaces as permission errors on the consume folder rather than as a startup failure.
-
Start the stack:
Terminal window docker compose up -d -
Watch the migrations finish:
Terminal window docker compose logs paperless-app --followWait for
Listening at: http://:::8000. -
Create the first account:
Terminal window docker compose exec paperless-app createsuperuser -
Open
https://<APP_TRAEFIK_HOST>and log in.
Verify
Section titled “Verify”docker compose ps # five services; four healthy, tika "running" (no healthcheck)docker compose exec paperless-app /command/with-contenv python3 manage.py check # no issuesThen put a PDF into volumes/consume/ and watch it arrive:
docker compose logs paperless-app --followThe document is consumed, OCR’d and appears in the interface. Open it and read
the extracted text — a document that appears with empty or wrong text means the
language pack in PAPERLESS_OCR_LANGUAGES does not cover it.
Going further
Section titled “Going further”Who can reach it
Section titled “Who can reach it”The stack ships access-vpn, with limit-standard and headers-standard:
reachable over your VPN, refused from everywhere else. Paperless holds whatever
you scan, which is usually the most personal data on the server. Change
APP_TRAEFIK_ACCESS in .env only deliberately, and consider single sign-on at
the same time.
Single sign-on through Authentik
Section titled “Single sign-on through Authentik”sso.yml overlays OpenID Connect. With it active, username and password login is
disabled and every request redirects to Authentik.
In Authentik: an application with the slug paperless, an OAuth2/OIDC provider
bound to it, and the redirect URI
https://<APP_TRAEFIK_HOST>/accounts/oidc/<SSO_PROVIDER_ID>/login/callback/.
Then in .env:
COMPOSE_FILE=docker-compose.yml,sso.ymlSSO_PROVIDER_ID=authentikSSO_CLIENT_ID=<from authentik>SSO_CLIENT_SECRET=<from authentik>SSO_SERVER_URL=https://auth.example.com/application/o/paperless/.well-known/openid-configurationSSO_CLIENT_SECRET lives in .env rather than in .secrets/. Paperless builds
its provider configuration as a single JSON environment variable and has no
_FILE form for a value inside it.
Used for password resets, workflow notifications and document share links. Mail
ships off: Paperless treats it as off only while PAPERLESS_EMAIL_HOST=localhost
and PAPERLESS_EMAIL_HOST_USER is empty. An empty host does not turn it off — the
password reset then answers 500.
To switch it on, write the password to a file and set the relay in .env:
echo -n 'your-smtp-password' > .secrets/email_pwd.txtPAPERLESS_EMAIL_HOST=smtp.example.comPAPERLESS_EMAIL_PORT=587PAPERLESS_EMAIL_USE_TLS=truePAPERLESS_EMAIL_FROM=Paperless <[email protected]>Port 465 instead: PAPERLESS_EMAIL_PORT=465 with PAPERLESS_EMAIL_USE_TLS=false
and PAPERLESS_EMAIL_USE_SSL=true — never both; with USE_TLS=false alone the
connection to 465 is closed.
Backup
Section titled “Backup”| Database | PostgreSQL, container paperless-db, database paperless, user paperless_user |
| Password | .secrets/db_pwd.txt |
| The archive | volumes/media — the documents themselves |
| Also needed | .secrets/secret_key.txt, or existing sessions and tokens stop working |
| Rebuildable | volumes/data (search index) · volumes/redis · volumes/consume · volumes/export |
For borgmatic:
postgresql_databases: - name: paperless username: paperless_user # The server's own client, inside its container pg_dump_command: docker exec paperless-db pg_dump pg_restore_command: docker exec -i paperless-db pg_restore psql_command: docker exec -i paperless-db psqlNo quiescing — the dump is consistent on its own.
volumes/media is the archive. The database holds tags, correspondents and
the extracted text; the documents are files on disk. A database restored without
media leaves every record pointing at a document that is gone.
Restore order: database, then media, then the application. The search index rebuilds on first start; until it finishes, search returns nothing while every document is present.
Paperless also ships document_exporter, which writes documents and metadata
into one self-describing tree. That is the path to use when moving between major
versions rather than restoring in place.
Updates
Section titled “Updates”Paperless runs schema migrations on every minor bump, and the classifier model may need rebuilding.
-
Read the release notes for breaking changes.
-
Export, then dump — the exporter knows what belongs together:
Terminal window docker compose exec -u paperless paperless-app \document_exporter /usr/src/paperless/export --compare-checksums --use-filename-formattar czf paperless-export-$(date +%Y%m%d).tgz volumes/export volumes/mediadocker compose exec db sh -c 'pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"' \> paperless-db-$(date +%Y%m%d).sql -
Raise
APP_TAGin.env, then:Terminal window docker compose pull && docker compose up -ddocker compose logs paperless-app --follow
Troubleshooting
Section titled “Troubleshooting”Permission errors on the consume or media folder. USERMAP_UID and
USERMAP_GID do not match the host user that owns volumes/. Correct the values
in .env or the ownership on disk, then recreate the app container.
Login is refused with a password that is definitely right. A secret file has
a trailing newline. Paperless compares "password\n" against "password".
Regenerate with tr -d '\n'.
A document is archived with wrong or garbled text. Its language is not in
PAPERLESS_OCR_LANGUAGES, so Tesseract read it as English. Add the language,
recreate the app container, and re-run OCR on the affected documents.
The first OCR of a large document takes minutes. Tesseract loads language data on first use; subsequent documents in the same language are fast.
Adding user: to the app service breaks startup. Paperless uses s6-overlay,
which needs root to set up /run before it drops privileges. The blueprint
leaves the directive off for that reason.
Repository files
Section titled “Repository files”apps/paperless-ngx/README.md— setup, security model, known issuesapps/paperless-ngx/CONFIG.md— full configuration referenceapps/paperless-ngx/UPSTREAM.md— upgrade checklist and tuningapps/paperless-ngx/sso.yml— the Authentik overlay
Official sources
Section titled “Official sources”© 2026 SecDockBlueCode Apache-2.0Site content CC BY-NC 4.0