Productivity & Home
Paperless-ngx logo

How to install Paperless-ngx with Docker

Paperless-ngx cover: How to install Paperless-ngx with Docker
Difficulty: IntermediateTime: 30 minutesTested with: Paperless-ngx v3.2 (official compose file)

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.

💡
Note: This guide works with the current Paperless-ngx v3.2 release (v3.2.1 at the time of writing) from ghcr.io/paperless-ngx/paperless-ngx.

✅ Before you start

  • A Linux host with Docker and Docker Compose v2 installed (docker compose version should work). Use docker compose with a space: the old Python docker-compose v1 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:

id

You 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_KEY

STEP 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 them

Folder 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 proxy

STEP 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_PASSWORD

docker-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: true

docker-compose.yml

👍
Tip: Only scanning PDFs and images?
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 -f

Wait 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-ngx

You 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

  1. Open http://192.168.1.20:8000. On the first visit you are asked to create a superuser: this is your admin account.
  2. Create a normal (non-superuser) account for daily use under Settings → Users & Groups, as the docs recommend: superusers can see every document.
  3. Set up the basics: a few Correspondents (your bank, energy supplier...), Document types (Invoice, Letter, Payslip) and Tags.
  4. 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.
  5. Once you have a few dozen documents, Paperless learns to assign correspondents, types and tags automatically. Use Workflows for rules of your own.
👍
Tip: Point your scanner's "scan to network folder" at the 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=proxy

docker-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:8000

dynamic.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 -f
⚠️
Warning: Read the release notes first, and make a backup: docker 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. 🗂️