Paperless-ngx is a free, open-source document management system that turns your pile of paper into a searchable archive. Scan a letter or drop a PDF into a folder and Paperless OCRs it, works out the correspondent, document type, date and tags, and files it away, so that bill from three years ago is one search away. It is the community-maintained successor of Paperless and Paperless-ng.
In this guide we will run Paperless-ngx with the official Docker Compose files: the web server with PostgreSQL and a Valkey (Redis) broker, plus the optional Apache Tika and Gotenberg containers so it can also read Word, Excel and email files.
ghcr.io/paperless-ngx/paperless-ngx.✅ Before you start
- A Linux host with Docker and Docker Compose v2 installed (
docker compose versionshould work). Usedocker composewith a space: the old Pythondocker-composev1 can crash on modern Docker. - About 2 GB of RAM (Tika and Gotenberg add a bit more).
- One free port,
8000, for the web UI. - Optional: a scanner that can save to a network folder (SMB/FTP) pointing at the consume folder.
- Optional: a reverse proxy such as Traefik or Nginx Proxy Manager and a domain, if you want to reach it from outside your home network.
STEP 1: Find your user and group IDs
Paperless-ngx should run as a normal user, not as root. Log in to your Docker host over SSH and run:
idYou will see something like uid=1000(you) gid=1000(you). Make a note of the two numbers; they become PUID and PGID below.
Paperless calls them USERMAP_UID and USERMAP_GID. Setting them means files you drop in the consume folder, and the files Paperless exports, belong to you.
You also need two secrets: a database password and PAPERLESS_SECRET_KEY, which signs sessions and is required. The docs generate the key like this:
openssl rand -hex 24 # database password
python3 -c "import secrets; print(secrets.token_urlsafe(64))" # PAPERLESS_SECRET_KEYSTEP 2: Create the folders
I keep every app's files under one parent folder so a backup is a single job. Create the folders for Paperless-ngx:
mkdir -p /path/to/appdata/paperless-ngx/{data,media,pgdata,redisdata,export,consume}media is the precious one: it holds your documents. consume is the inbox: anything you put there is imported and then removed.
When you are done, the layout should look like this:
/path/to/appdata/paperless-ngx/
docker-compose.yml
docker-compose.env # Paperless settings
data/ # search index, classifier, logs
media/ # your documents (originals and archived PDFs)
pgdata/ # PostgreSQL
redisdata/ # Valkey (broker)
export/ # document exporter output
consume/ # drop files here to import themFolder layout
Finally, I give every web app that sits behind my reverse proxy one shared Docker network called proxy. Paperless-ngx joins it so Traefik or Nginx Proxy Manager can reach it by container name, while its database and helpers stay on the stack's own private network. Create it once (if it already exists, Docker just tells you so):
docker network create proxySTEP 3: The compose file
First create /path/to/appdata/paperless-ngx/docker-compose.env. It is the official env file with the common settings filled in:
USERMAP_UID=1000
USERMAP_GID=1000
PAPERLESS_URL=https://paperless.example.com
PAPERLESS_SECRET_KEY=change-me
PAPERLESS_TIME_ZONE=Europe/London
PAPERLESS_OCR_LANGUAGE=eng
# PAPERLESS_OCR_LANGUAGES=deu fra # extra Tesseract languages to install
PAPERLESS_DBPASS=CHANGE_ME_STRONG_PASSWORDdocker-compose.env
Create /path/to/appdata/paperless-ngx/docker-compose.yml and paste in the following. It is the official docker-compose.postgres-tika.yml, with bind mounts, a database password and the shared proxy network added. Use the copy button in the top right corner of the block.
services:
broker:
image: docker.io/valkey/valkey:9-alpine
container_name: paperless-broker
restart: unless-stopped
volumes:
- /path/to/appdata/paperless-ngx/redisdata:/data
db:
image: docker.io/library/postgres:18
container_name: paperless-db
restart: unless-stopped
volumes:
- /path/to/appdata/paperless-ngx/pgdata:/var/lib/postgresql
environment:
POSTGRES_DB: paperless
POSTGRES_USER: paperless
POSTGRES_PASSWORD: CHANGE_ME_STRONG_PASSWORD # same as PAPERLESS_DBPASS
webserver:
image: ghcr.io/paperless-ngx/paperless-ngx:latest
container_name: paperless
restart: unless-stopped
depends_on:
- db
- broker
- gotenberg
- tika
ports:
- "8000:8000"
volumes:
- /path/to/appdata/paperless-ngx/data:/usr/src/paperless/data
- /path/to/appdata/paperless-ngx/media:/usr/src/paperless/media
- /path/to/appdata/paperless-ngx/export:/usr/src/paperless/export
- /path/to/appdata/paperless-ngx/consume:/usr/src/paperless/consume
env_file: docker-compose.env
environment:
PAPERLESS_REDIS: redis://broker:6379
PAPERLESS_DBHOST: db
PAPERLESS_DBENGINE: postgresql
PAPERLESS_TIKA_ENABLED: 1
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
networks:
- default
- proxy
gotenberg:
image: docker.io/gotenberg/gotenberg:8.37
container_name: paperless-gotenberg
restart: unless-stopped
command:
- "gotenberg"
- "--chromium-disable-javascript=true"
- "--chromium-allow-list=file:///tmp/.*"
tika:
image: docker.io/apache/tika:3.3.1.0
container_name: paperless-tika
restart: unless-stopped
networks:
proxy:
external: truedocker-compose.yml
Tika and Gotenberg are optional. Delete the
gotenberg and tika services, remove them from depends_on, and drop the three PAPERLESS_TIKA_* lines. That is the official docker-compose.postgres.yml and saves a fair bit of RAM.STEP 4: The settings explained
| Setting | What it does |
|---|---|
broker |
Valkey (a Redis fork): the task queue that hands documents to the background workers. |
db |
PostgreSQL 18. Note the mount is /var/lib/postgresql, not .../data, as Postgres 18 expects. |
PAPERLESS_DBPASS / POSTGRES_PASSWORD |
The database password. The two must match. (The official example leaves both at the default paperless; I set a real one.) |
USERMAP_UID / USERMAP_GID |
Your IDs from STEP 1, so you can write to consume and read export. |
PAPERLESS_URL |
The public address. Required when you reach Paperless through a domain, for CSRF protection and correct links. |
PAPERLESS_SECRET_KEY |
Required. Signs sessions. Long, random and stable. |
PAPERLESS_TIME_ZONE |
Your time zone (defaults to UTC). List of zones. |
PAPERLESS_OCR_LANGUAGE |
The main language of your documents, as a Tesseract code (eng, deu, fra...). Combine with +, e.g. eng+deu. |
PAPERLESS_OCR_LANGUAGES |
Extra Tesseract languages to install. English, German, Italian, Spanish and French are included already. |
PAPERLESS_TIKA_* |
Turns on Office/email support: Tika extracts the text and Gotenberg converts the file to PDF. |
/usr/src/paperless/media |
Your documents. Back this up, together with data and the database (or use the exporter). |
STEP 5: Start Paperless-ngx
From the folder containing your compose file:
cd /path/to/appdata/paperless-ngx
docker compose up -d
docker compose logs -fWait until the logs settle down (press Ctrl+C to stop following them; the container keeps running), then check it is up:
docker ps --filter name=paperless-ngxYou should see paperless, paperless-db, paperless-broker, paperless-gotenberg and paperless-tika. The first start runs the database migrations, which takes a minute.
STEP 6: First-run setup
- Open
http://192.168.1.20:8000. On the first visit you are asked to create a superuser: this is your admin account. - Create a normal (non-superuser) account for daily use under Settings → Users & Groups, as the docs recommend: superusers can see every document.
- Set up the basics: a few Correspondents (your bank, energy supplier...), Document types (Invoice, Letter, Payslip) and Tags.
- Drop a PDF into
/path/to/appdata/paperless-ngx/consume(or upload it in the web UI) and watch it appear, OCR'd and searchable. - Once you have a few dozen documents, Paperless learns to assign correspondents, types and tags automatically. Use Workflows for rules of your own.
consume folder (share it over SMB) and paper goes from the scanner glass to a searchable archive without touching a computer.STEP 7: Put Paperless-ngx behind a reverse proxy
Use a dedicated hostname such as paperless.example.com, and make sure PAPERLESS_URL is set to exactly that HTTPS address, or logins fail with a CSRF error.
Option A: Traefik (Docker labels)
If Traefik runs on the same Docker host and is attached to the proxy network, add these labels to the paperless service. They assume your HTTPS entry point is called websecure and your certificate resolver is called cloudflare; change them to match your Traefik config.
labels:
- traefik.enable=true
- traefik.http.routers.paperless.rule=Host(`paperless.example.com`)
- traefik.http.routers.paperless.entrypoints=websecure
- traefik.http.routers.paperless.tls.certresolver=cloudflare
- traefik.http.services.paperless.loadbalancer.server.port=8000
- traefik.docker.network=proxydocker-compose.yml (extra lines)
Option B: Traefik (file provider)
If Traefik runs on a different machine, add a router and service to your dynamic config file instead:
http:
routers:
paperless:
rule: Host(`paperless.example.com`)
entryPoints:
- websecure
tls:
certResolver: cloudflare
service: paperless
services:
paperless:
loadBalancer:
servers:
- url: http://192.168.1.20:8000dynamic.yml
Option C: Nginx Proxy Manager
Add a Proxy Host for paperless.example.com with scheme http, forward hostname paperless (if NPM is on the proxy network) or 192.168.1.20, and forward port 8000. Request a certificate on the SSL tab and tick Force SSL. Turn Websockets Support on (the status updates use them), and add client_max_body_size 100m; under the Advanced tab for big uploads.
Put the labels on the webserver service. The extra traefik.docker.network=proxy label matters because it is on two networks.
Updating Paperless-ngx
The latest tag follows the newest release. Migrations run automatically on start. To update:
cd /path/to/appdata/paperless-ngx
docker compose pull
docker compose up -d
docker image prune -fdocker compose exec webserver document_exporter ../export writes all documents and metadata into the export folder. Upgrading PostgreSQL to a new major version needs a dump and restore; do not change postgres:18 at the same time.Troubleshooting
CSRF verification failed when logging in. PAPERLESS_URL does not match the address in your browser (scheme and hostname). Fix it in docker-compose.env and run docker compose up -d.
Files in consume are not picked up. Check docker logs paperless. Usually it is permissions (USERMAP_UID/GID do not match the folder's owner), or the folder is a network share that does not send file change events; set PAPERLESS_CONSUMER_POLLING=60 to poll instead.
Word or email files fail. Tika or Gotenberg is not running or the endpoints are wrong. Check docker logs paperless-tika and paperless-gotenberg.
OCR gets the language wrong. Set PAPERLESS_OCR_LANGUAGE (and install extra languages with PAPERLESS_OCR_LANGUAGES).
port is already allocated. 8000 is popular. Change the left-hand side, for example "8010:8000".
That's it! Go and feed it that shoebox full of letters. Future you will be very grateful. 🗂️