Self-HostingDockerAI

Paperless-ngx with Docker: A Paperless Office with OCR and Local AI Tagging (Linux, Windows, Mac)

Before you start

  • 19 min read
  • 14 to run in terminal
  • 4 file contents

Boxes marked Run in terminal are what you type; plain text explains why. Check what you need first

What you get in one minute#

Paperless-ngx is a free, self-hosted document archive: you drop in scans and files, it reads the text with OCR, and you find any page later by searching.

People use it to get rid of the paper drawer. Invoices, contracts, letters and receipts go in, and tags, correspondents and document types keep them tidy. Your documents stay on your own machine. This guide builds the official stack (PostgreSQL, a queue, Gotenberg and Tika for Office files) with pinned versions, then adds an optional helper that suggests titles and tags with a local AI model.

WhatValue
Paperless-ngx version3.3.4, released 2026-10-22 (GitHub releases)
Imageghcr.io/paperless-ngx/paperless-ngx:3.3.4
AI helper (optional)paperless-gpt v0.31.0, released 2026-10-14
Time neededAbout 15-20 minutes, plus the first image pulls
Ports8000 Paperless, 8080 paperless-gpt (both on 127.0.0.1 only)
Official docshttps://docs.paperless-ngx.com/

Which version? The newest release, 3.3.4, is a security-maintenance release. We checked the issue tracker: the one startup-loop report was caused by a custom command: override in the reporter's own Compose file, and another report came from a third-party non-Docker script that the maintainers closed as not a bug. We found no repeated reports of a broken Docker install, so this guide uses the latest release. It is only four days old at the time of writing, so read the release notes before you update later.

If you already run Paperless-ngx 2.x, read the official v3 migration notes before you change the image tag.

How it fits together#

Files arrive by folder, mail or upload, Paperless-ngx reads them, and the optional AI helper only suggests metadata that you approve.

How a document travels through Paperless-ngx: a scanner or phone drops a PDF in the consume folder, a mail rule fetches an attachment, or a Word, Excel or email file is converted by Gotenberg and Tika; Paperless-ngx on port 8000 reads the text with OCR; the result is saved and searchable in PostgreSQL with a Valkey queue; optionally paperless-gpt on port 8080 asks a local Ollama model for a title, tags and correspondent, and you review and apply them

Two things worth knowing. First, the current official Compose file uses Valkey (a Redis-compatible queue) instead of a Redis-branded image. Paperless still calls the setting PAPERLESS_REDIS and the address redis://broker:6379. Second, paperless-gpt asks the model for suggestions based on the text Paperless already extracted. It does not replace the normal OCR here.

Which AI helper? paperless-gpt is the active one. The paperless-ai README now says plainly that the repository is not maintained, so we skip it.

Before you start#

You need Docker with the Compose plugin, a terminal, and a few GB of free disk space.

SystemWhat you needTest status
LinuxDocker Engine with the docker compose plugin, bashChecked against official docs, not run
WindowsDocker Desktop (Linux containers, WSL2 backend), PowerShell 7Checked against official docs, not run
MacDocker Desktop for Mac, TerminalChecked against official docs, not run

Check Docker first:

Run in terminalbash
docker --version
docker compose version

Paperless-ngx has no official minimum for this whole stack. As a starting point, plan for 2-4 CPU cores and about 4 GB of free RAM for the document stack (VERIFY on your own machine). A local AI model needs extra memory on top, and 16 GB in total is a sensible start for an 8B-class model (VERIFY, it depends on the model and context size). The one official tuning rule: workers times threads must not be more than your CPU cores.

  • [ ] Ports 8000 and 8080 are free on the machine
  • [ ] You have a place for backups on another disk or machine
  • [ ] Windows only: Docker Desktop is running in Linux-container mode

Tested on: not run. This guide was checked against the official docs and compose files on 2026-10-26; the commands were not executed on Linux, Windows or Mac.

Step 1: Create the folders and secrets#

Make one folder under ~/docker_volumes/paperless-ngx and generate a database password and a secret key once.

Paperless needs PAPERLESS_SECRET_KEY (the official env file marks it required). The database password and the key go in a .env file that only you can read. Run this only for a new install. If you generate new values later, the database password no longer matches the existing database.

Linux#

Run in terminalbash
set -eu
umask 077
mkdir -p "$HOME/docker_volumes/paperless-ngx"
cd "$HOME/docker_volumes/paperless-ngx"
mkdir -p data media consume export postgres broker gpt/prompts gpt/config gpt/db

cat > .env <<EOF
POSTGRES_PASSWORD=$(od -An -N32 -tx1 /dev/urandom | tr -d ' \n')
PAPERLESS_SECRET_KEY=$(od -An -N48 -tx1 /dev/urandom | tr -d ' \n')
USERMAP_UID=$(id -u)
USERMAP_GID=$(id -g)
EOF
chmod 600 .env

Windows (PowerShell)#

Codepowershell
$Root = Join-Path $HOME "docker_volumes\paperless-ngx"
New-Item -ItemType Directory -Force -Path $Root | Out-Null
Set-Location $Root
"data","media","consume","export","postgres","broker","gpt\prompts","gpt\config","gpt\db" |
    ForEach-Object { New-Item -ItemType Directory -Force -Path $_ | Out-Null }

function New-RandomHex([int]$Count) {
    $Bytes = New-Object byte[] $Count
    $Rng = [System.Security.Cryptography.RandomNumberGenerator]::Create()
    try { $Rng.GetBytes($Bytes) } finally { $Rng.Dispose() }
    ($Bytes | ForEach-Object { $_.ToString("x2") }) -join ""
}

$EnvText = @"
POSTGRES_PASSWORD=$(New-RandomHex 32)
PAPERLESS_SECRET_KEY=$(New-RandomHex 48)
USERMAP_UID=1000
USERMAP_GID=1000
"@
[System.IO.File]::WriteAllText((Join-Path $Root ".env"), $EnvText, (New-Object System.Text.UTF8Encoding($false)))

On Windows the USERMAP values only describe the user inside the container. Windows file sharing in Docker Desktop still decides who can read the folder. Keep the folder inside your own profile.

Mac (Terminal)#

Same commands as Linux:

Run in terminalbash
set -eu
umask 077
mkdir -p "$HOME/docker_volumes/paperless-ngx"
cd "$HOME/docker_volumes/paperless-ngx"
mkdir -p data media consume export postgres broker gpt/prompts gpt/config gpt/db

cat > .env <<EOF
POSTGRES_PASSWORD=$(od -An -N32 -tx1 /dev/urandom | tr -d ' \n')
PAPERLESS_SECRET_KEY=$(od -An -N48 -tx1 /dev/urandom | tr -d ' \n')
USERMAP_UID=$(id -u)
USERMAP_GID=$(id -g)
EOF
chmod 600 .env

Check it worked: ls -a (Windows: Get-ChildItem -Force) shows .env and the folders. Do not print the file in a shared screen.

Potential issue: Docker Desktop must be allowed to share your home folder (Mac: Settings, Resources, File sharing).

The Compose file (same on all systems)#

This is the official PostgreSQL + Tika file, adapted: pinned image, bind mounts under your home folder, ports on localhost only, and no version: line.

Save it as docker-compose.yml in the same folder. The tags for Valkey, PostgreSQL, Gotenberg and Tika match the official file for v3.3.4 and each tag exists on its registry. PostgreSQL 18 mounts /var/lib/postgresql (not the older /var/lib/postgresql/data). Never point a new PostgreSQL major version at a data folder made by an older one.

File contentyaml
name: paperless

services:
  broker:
    image: docker.io/valkey/valkey:9-alpine
    restart: unless-stopped
    volumes:
      - ./broker:/data

  db:
    image: docker.io/library/postgres:18
    restart: unless-stopped
    volumes:
      - ./postgres:/var/lib/postgresql
    environment:
      POSTGRES_DB: paperless
      POSTGRES_USER: paperless
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD}

  webserver:
    image: ghcr.io/paperless-ngx/paperless-ngx:3.3.4
    restart: unless-stopped
    depends_on:
      - db
      - broker
      - gotenberg
      - tika
    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_DBENGINE: postgresql
      PAPERLESS_DBNAME: paperless
      PAPERLESS_DBUSER: paperless
      PAPERLESS_DBPASS: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD}
      PAPERLESS_SECRET_KEY: ${PAPERLESS_SECRET_KEY:?Set PAPERLESS_SECRET_KEY}
      PAPERLESS_URL: http://localhost:8000
      USERMAP_UID: ${USERMAP_UID:-1000}
      USERMAP_GID: ${USERMAP_GID:-1000}
      PAPERLESS_TIME_ZONE: UTC
      PAPERLESS_OCR_LANGUAGE: eng
      PAPERLESS_OCR_MODE: auto
      PAPERLESS_TASK_WORKERS: "1"
      PAPERLESS_THREADS_PER_WORKER: "2"
      PAPERLESS_CONSUMER_POLLING_INTERVAL: "10"
      PAPERLESS_TIKA_ENABLED: "1"
      PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
      PAPERLESS_TIKA_ENDPOINT: http://tika:9998

  gotenberg:
    image: docker.io/gotenberg/gotenberg:8.37
    restart: unless-stopped
    command:
      - gotenberg
      - "--chromium-disable-javascript=true"
      - "--chromium-allow-list=^file:///tmp/.*"

  tika:
    image: docker.io/apache/tika:3.3.1.0
    restart: unless-stopped

What the settings do:

  • PAPERLESS_OCR_LANGUAGE picks the language Tesseract reads. The container already ships English, German, Italian, Spanish and French.
  • PAPERLESS_OCR_MODE: auto is the v3 default. A document that already has enough embedded text skips OCR, and one without text is OCRed. (The old skip value from v2 is now treated as auto.)
  • PAPERLESS_CONSUMER_POLLING_INTERVAL: "10" checks the consume folder every 10 seconds instead of waiting for file-change events (the default 0). It helps on network shares. The v2 name PAPERLESS_CONSUMER_POLLING no longer works in v3.
  • Change the time zone to yours, for example Asia/Tokyo.
  • The two Gotenberg flags come from the official file: they stop email conversion from loading outside content or running JavaScript.

Windows note: a PostgreSQL bind mount on an NTFS folder can fail with ownership errors (VERIFY on your setup). If that happens, either replace ./postgres:/var/lib/postgresql with a named volume (add pgdata: under volumes: at the bottom) or run the Linux steps inside a WSL distribution and keep everything on its Linux disk. Do not switch the same database between the two.

Step 2: Start the stack and create your admin#

Check the file, pull the images, start everything, then create the first user.

Linux#

Run in terminalbash
cd "$HOME/docker_volumes/paperless-ngx"
docker compose config --quiet
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail=100 webserver
docker compose exec webserver createsuperuser

Windows (PowerShell)#

Codepowershell
Set-Location "$HOME\docker_volumes\paperless-ngx"
docker compose config --quiet
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail=100 webserver
docker compose exec webserver createsuperuser

Mac (Terminal)#

Same commands as Linux:

Run in terminalbash
cd "$HOME/docker_volumes/paperless-ngx"
docker compose config --quiet
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail=100 webserver
docker compose exec webserver createsuperuser

Check it worked: docker compose ps lists webserver, db, broker, gotenberg and tika as running, and http://localhost:8000 shows the login page. The first start runs the database migrations, so give it a minute. createsuperuser asks for a name and password interactively, so no admin password sits in a config file.

Potential issue: if the web page does not load, read docker compose logs --tail=100 webserver. Keep the container command: as it is. A custom command: that replaces the long-running one caused the one restart-loop report we found for 3.3.4.

First-run setup#

Test with three real files before you import a whole archive.

  1. Sign in at http://localhost:8000 with your admin.
  2. Upload one PDF scan, one Word or Excel file and one normal text PDF through the web page.
  3. Open each document. Is the text searchable? Is the page the right way up? Can you download the original and the archived copy?
  4. Create a few tags, correspondents and document types. Paperless can also learn rules from your reviewed documents (automatic matching), with no AI at all.
  5. Create normal users for everyday work (Settings, Users and groups) and use the admin only for setup. Set owners and sharing on purpose, then sign in as a normal user and check what that user sees.

The consume folder#

Copy files into ~/docker_volumes/paperless-ngx/consume and Paperless picks them up. Inside the container this folder is /usr/src/paperless/consume. To move it, change the host side of the volume line, not the setting PAPERLESS_CONSUMPTION_DIR.

Treat consume as a queue. Paperless needs permission to remove files it has consumed, so keep your own copy until the document is safe in a backup. If your scanner writes slowly, let it write to another folder and move finished files in.

More OCR languages#

Set PAPERLESS_OCR_LANGUAGE to the language codes you read (for example eng+deu). If a language is not one of the five built in, also set PAPERLESS_OCR_LANGUAGES to the extra pack names, for example PAPERLESS_OCR_LANGUAGES: jpn, and recreate the container. Every extra language costs CPU, so list only the ones you really have. Settings you change in the web page can override these environment values.

Mail fetching#

Add your mailbox under Mail accounts, then create a mail rule. Start narrow: one folder, one sender, a non-destructive action. Paperless checks mail every 10 minutes. To test now:

Run in terminalbash
docker compose exec webserver mail_fetcher

That command is the same in all three shells. Gmail and Microsoft accounts need token-based sign-in, and the exact steps are provider-specific (VERIFY in your provider's docs). The PAPERLESS_EMAIL_HOST settings are for outgoing mail only.

Add local AI tagging with paperless-gpt and Ollama (optional)#

paperless-gpt reads the text Paperless already has, asks a local model for a title, tags and a correspondent, and waits for your approval.

Start small. Keep the normal OCR, keep PDF_REPLACE off, and review every suggestion by hand at first. If you do not want any AI, skip this step. Paperless works fine without it.

You need Ollama running on your machine with a model pulled. Install it from the official Ollama site, then:

Run in terminalbash
ollama pull qwen3:8b
ollama list

The model name comes from the paperless-gpt README example. Smaller or larger models work too. Quality, especially for non-English text, depends on the model, so test your own documents.

  1. In Paperless, create a user just for the helper and give it access only to the documents it should process. Open that user's profile and create an API token.
  2. Add this line to .env with the real token:
File contenttext
PAPERLESS_GPT_TOKEN=<YOUR_API_TOKEN>
  1. Add this service under services: in docker-compose.yml:
File contentyaml
  paperless-gpt:
    image: icereed/paperless-gpt:v0.31.0
    restart: unless-stopped
    depends_on:
      - webserver
    ports:
      - "127.0.0.1:8080:8080"
    extra_hosts:
      - "host.docker.internal:host-gateway"
    environment:
      PAPERLESS_BASE_URL: http://webserver:8000
      PAPERLESS_PUBLIC_URL: http://localhost:8000
      PAPERLESS_API_TOKEN: ${PAPERLESS_GPT_TOKEN:?Set PAPERLESS_GPT_TOKEN}
      LLM_PROVIDER: ollama
      LLM_MODEL: qwen3:8b
      OLLAMA_HOST: http://host.docker.internal:11434
      OLLAMA_CONTEXT_LENGTH: "8192"
      OLLAMA_KEEP_ALIVE: 10m
      OLLAMA_THINK: "false"
      LLM_LANGUAGE: English
      MANUAL_TAG: paperless-gpt
      AUTO_TAG: paperless-gpt-auto
      PDF_UPLOAD: "false"
      PDF_REPLACE: "false"
    volumes:
      - ./gpt/prompts:/app/prompts
      - ./gpt/config:/app/config
      - ./gpt/db:/app/db

Keep all three mounted folders. The project warns that without them you lose prompts, settings and history when the container is replaced.

  1. Start it on every system with the same commands:
Run in terminalbash
docker compose pull paperless-gpt
docker compose up -d paperless-gpt
docker compose logs --tail=100 paperless-gpt
  1. Open http://localhost:8080. Add the tag paperless-gpt to one test document in Paperless, generate suggestions in paperless-gpt, read them, then apply. Check that nothing else on the document changed. Only after a few good rounds try the paperless-gpt-auto tag on a small batch.

Check it worked: the log shows the helper connected to Paperless, and the suggestions page lists your tagged test document.

Things to know:

  • No login on port 8080. paperless-gpt has no authentication, and by default it listens on all interfaces. Anyone who reaches the port can change documents in Paperless. That is why the Compose line binds it to 127.0.0.1.
  • Ollama must be reachable from the container. On Docker Desktop (Windows and Mac) host.docker.internal works. On Linux the extra_hosts line above provides it, but Ollama listens only on the host loopback by default, so the container may not reach it until you change how Ollama binds (VERIFY with the current Ollama docs). Do not open that port to your network just to make this work.
  • The README says local does not mean offline by itself: the helper can also talk to cloud providers if you configure them. Check every endpoint you set.
  • Run only one AI tagger against your documents at a time while you test.

Put it behind HTTPS with Traefik#

On a server with a domain, let Traefik handle HTTPS and drop the published ports. Local-only users can skip this.

This is an illustration, not an official Paperless example. It assumes Traefik v3 already runs with the Docker provider, an external network called proxy, an entrypoint called https and a certificate for your host name. Create the network once if needed: docker network create proxy. Then change the webserver service like this (merge these keys, do not add a second service):

File contentyaml
  webserver:
    # keep image, depends_on, volumes and the other settings as above
    environment:
      PAPERLESS_URL: https://paperless.<your-domain>
      PAPERLESS_ALLOWED_HOSTS: paperless.<your-domain>,localhost,webserver
      PAPERLESS_PROXY_SSL_HEADER: '["HTTP_X_FORWARDED_PROTO", "https"]'
    networks:
      - default
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"
      - "traefik.http.routers.paperless.rule=Host(`paperless.<your-domain>`)"
      - "traefik.http.routers.paperless.entrypoints=https"
      - "traefik.http.routers.paperless.tls=true"
      - "traefik.http.services.paperless.loadbalancer.server.port=8000"

networks:
  proxy:
    external: true

Remove the ports: lines from webserver when Traefik is in front. Add traefik.http.routers.paperless.tls.certresolver=<your-resolver> if you use an ACME resolver. PAPERLESS_URL is required when you use a public domain, and the proxy header setting is only safe when the header comes from your own proxy.

Do not give paperless-gpt a Traefik route unless the proxy also asks for a login. HTTPS alone does not protect it.

Security and testing#

Paperless-ngx needs a strong secret key, no direct internet exposure and a locked .env; the AI helper needs a network that only you can reach.

ControlHow
Secrets.env with chmod 600, never in Git; keep a safe copy of PAPERLESS_SECRET_KEY
NetworkPorts on 127.0.0.1 only; database, queue, Tika and Gotenberg publish nothing
Remote accessA VPN or a private network first; a public domain only with a proxy and good passwords
UsersDaily work with normal users, admin only for setup
AI helperIts own limited Paperless user and token; never exposed without a login in front
DiskPaperless does not encrypt stored documents; use an encrypted disk against theft
Auto loginLeave the automatic-login and remote-user header options off

Quick tests after every change:

Run in terminalbash
docker compose ps
docker compose logs --tail=100 webserver
docker compose exec webserver document_sanity_checker

The sanity checker looks for missing files and wrong checksums. For a Traefik setup also run curl -I https://paperless.<your-domain>/.

Troubleshooting#

ProblemSolution
Web container restarts again and again after a Granian shutdown messageRemove any custom command: from webserver; it replaces the real start command
chown: changing ownership of '../export': Operation not permittedThe storage (often a network share) forbids ownership changes; use a local disk or fix the share, not chmod 777
[Errno 13] Permission denied on a consume fileSet USERMAP_UID and USERMAP_GID to your user, and make sure the container can read, write and delete in consume
504 Server Error: Gateway Timeout for url: http://gotenberg:3000/forms/libreoffice/convertAdd "--api-timeout=60s" to the Gotenberg command: list and keep the other flags
A file is picked up half-writtenLet the scanner write to another folder and move finished files into consume; keep polling on
Classifier error: No training data availableHarmless with no automatic matching; otherwise review some documents so the classifier can learn
Ghostscript PDF/A rendering failedCheck the source PDF; the opt-in continue_on_soft_render_error setting helps, but check the archived copy
Search results look wrongdocker compose exec webserver document_index reindex
paperless-gpt cannot reach OllamaCheck OLLAMA_HOST, that the model is pulled, and the Linux loopback note in the AI section

Diagnostics: docker compose ps, then docker compose logs --tail=200 webserver (also db, gotenberg, paperless-gpt).

Granian shutdown completed, see ya!#

In an October 2026 report for 3.3.4 the web container printed Granian shutdown completed, see ya! and started again in a loop. The cause was a Compose file that replaced the container command to install jq. Remove the override. If you need extra tools, use a custom init script as the official docs describe.

Import into a half-filled target#

A report from the same week showed FileNotFoundError: [Errno 2] No such file or directory: '/opt/paperless_data/data/index', then a FileExistsError on retry. It came from a third-party non-Docker script, and the maintainers said not to import into partly filled storage. Always import into a fresh, empty install.

Known bugs#

None confirmed for 3.3.4 beyond the two reports above. paperless-gpt v0.31.0 fixed a case where fewer pages were processed than the document had (image OCR mode), so stay on v0.31.0 or newer.

Updates and backups#

Export with document_exporter, copy it off the machine, prove it with a restore test, then change the pinned tag.

The export holds documents, thumbnails, metadata and the database contents. It does not hold API tokens, so recreate those after an import. Keep .env, docker-compose.yml and the gpt folder as a separate backup.

Pause scanning, mail and the AI helper first so nothing changes during the export.

Linux#

Run in terminalbash
cd "$HOME/docker_volumes/paperless-ngx"
BACKUP="backup-$(date +%Y%m%d-%H%M%S)"
docker compose exec -T webserver document_exporter "/usr/src/paperless/export/$BACKUP"
docker compose exec -T webserver document_sanity_checker

Windows (PowerShell)#

Codepowershell
Set-Location "$HOME\docker_volumes\paperless-ngx"
$Backup = "backup-" + (Get-Date -Format "yyyyMMdd-HHmmss")
docker compose exec -T webserver document_exporter "/usr/src/paperless/export/$Backup"
docker compose exec -T webserver document_sanity_checker

Mac (Terminal)#

Same commands as Linux:

Run in terminalbash
cd "$HOME/docker_volumes/paperless-ngx"
BACKUP="backup-$(date +%Y%m%d-%H%M%S)"
docker compose exec -T webserver document_exporter "/usr/src/paperless/export/$BACKUP"
docker compose exec -T webserver document_sanity_checker

The export lands in export/ on your disk. Copy it to another disk or machine. Schedule it with cron at 03:00 on Linux and Mac (0 3 * * *) or with Task Scheduler on Windows. An export on the same disk does not protect you from that disk dying.

Restore test#

The importer needs a completely empty Paperless, and the official docs advise importing with the same version that made the export. Test it in a second folder so production stays untouched:

  1. Make ~/docker_volumes/paperless-restore-test, copy docker-compose.yml there, and change name: paperless to name: paperless-restore-test.
  2. Make new empty folders and a new .env with new secrets (Step 1). Change the host port to 127.0.0.1:8001:8000 and PAPERLESS_URL to http://localhost:8001.
  3. Remove the paperless-gpt service, and add PAPERLESS_EMAIL_TASK_CRON: "disable" to webserver so the test cannot fetch your mail.
  4. Copy one export folder to export/restore-input.
  5. Start the helpers and import. Do not create a superuser first, because the export contains the users.
Run in terminalbash
docker compose up -d db broker gotenberg tika
docker compose run --rm --no-deps webserver document_importer /usr/src/paperless/export/restore-input
docker compose up -d webserver
docker compose exec -T webserver document_sanity_checker

These commands are the same in all three shells. VERIFY the one-off importer on your own setup. If an import fails halfway, keep the logs, delete the test folders and start again from empty. Then open http://localhost:8001 and check the document count, a few downloads, search, tags and users. A command that exits without error is not proof; the documents must open.

Update#

  1. Read the release notes and the newest issues.
  2. Pause scanning, mail and AI. Export and check a backup.
  3. Change only the image tag on webserver, then:
Run in terminalbash
docker compose down
docker compose pull webserver
docker compose up -d
docker compose logs --tail=200 webserver
docker compose exec -T webserver document_sanity_checker

Start-up applies the database migrations. Never run docker compose down -v as part of an update, and do not change the PostgreSQL major version in the same step. Rolling back means restoring the backup, because migrations are not reversed by choosing an older tag.

Performance and resources#

Start with one worker and two threads, measure, then raise them, and never go past your CPU core count.

We did not run this stack, so we give no measured memory number (see docker stats on your machine). What the docs say:

  • Workers times threads must stay within your cores. On one core use one thread.
  • More OCR languages mean more CPU. List only the ones you have.
  • auto mode avoids OCR on documents that already have enough embedded text.
  • Keep the data on a local disk; some network shares cause ownership errors.
  • For the AI helper, a longer context and a bigger model need more memory. OLLAMA_KEEP_ALIVE keeps the model loaded between requests, and OLLAMA_TIMEOUT_SECONDS (default 300) covers slow answers. No source gives a pages-per-minute figure, so time your own documents.

Watch it with:

Run in terminalbash
docker stats
docker compose logs --tail=100 webserver

FAQ#

Does Paperless-ngx need AI to be useful? No. OCR, full-text search, matching rules and the built-in classifier work without paperless-gpt or Ollama. Add the AI helper only if you want better titles and tags.

Can it read more than one language? Yes. Use codes like eng+deu in PAPERLESS_OCR_LANGUAGE and install any extra packs with PAPERLESS_OCR_LANGUAGES. Test a mixed-language page before you import a lot.

Does a local model mean nothing leaves my network? Not automatically. paperless-gpt can also call cloud providers if you set them. Check every endpoint in your Compose file.

Can I throw away the paper after scanning? This guide cannot tell you that. Rules for tax, insurance and contract papers depend on your country, so check them first. Keep the originals until you know.

Is the export a full backup? It is the main one, but it leaves out API tokens, your .env and the gpt folder. Back those up too and run a restore test.

Sources#