Skip to content
← All posts
· 8 min read· By

Upgrading Paperless-ngx 3.0: Migration Without Data Loss

From 2.x to 3.1.2 without losing data: breaking changes, decrypt_documents, PAPERLESS_DBENGINE, and Tantivy search — step by step.

Paperless-ngxDockerSelf-HostingDocument ManagementPostgreSQLDevOps

Paperless-ngx 3.0 is not a quiet maintenance release — it is a genuine major version with several hard breaking changes. Simply swapping the image tag and restarting the container risks either a startup failure or, in the worst case, database inconsistencies. This guide walks you through the complete upgrade from 2.x to the current stable version 3.1.2: what changes, what you must do before the first docker compose up, and which errors have actually occurred in the community — including root cause and fix.

What 3.0 Actually Does to Your Installation

Version 3.0.0 was released on 22 July 2026; the series stabilised with 3.0.5 on 1 August 2026. The currently recommended version is 3.1.2 (released 1 September 2026), which includes a security fix. If you have been running :latest and relied on that tag staying stable, now is the time to switch to a pinned tag — more on that below.

Breaking Changes at a Glance

Not every change affects every installation, but none of them are optional.

Change Affects you if … Required action
REST API v1 removed Third-party apps or scripts use /api/ without a version or explicit v1 endpoints Migrate to v2 endpoints
PAPERLESS_PASSPHRASE removed You stored documents with encryption enabled Run decrypt_documents before upgrading
PAPERLESS_DBENGINE now mandatory Any PostgreSQL or MariaDB installation Add the variable to your Compose file
PAPERLESS_SECRET_KEY = 'change-me' rejected Anyone who never changed the default Set a real random value
Python 3.10 dropped Bare-metal installations on Python 3.10 Docker users not affected
Search backend: Whoosh → Tantivy All installations Reindex runs automatically on first start
Consume scripts drop positional arguments Custom pre/post-consume scripts Migrate to environment variables

Why Tantivy Instead of Whoosh?

The old Whoosh search backend was implemented entirely in Python — slow on large collections, memory-hungry, and the last active development of Whoosh is years old. Tantivy is a modern Rust library used today in a number of search projects. The case for ripping Whoosh out entirely rather than building a compatibility shim rests on a concrete measurement from the official pull-request discussion thread: across 9,000 search queries, total elapsed time dropped from roughly 1.5 minutes (Whoosh) to roughly 30 seconds (Tantivy) — a five-fold speedup. The on-disk index is also around 40 percent smaller. For collections below 3,000 documents the latency advantage is barely perceptible in day-to-day use, but the index build runs two to three times faster.

Before the Upgrade: Two Mandatory Steps

Decrypt Documents First

If you have PAPERLESS_PASSPHRASE set, decrypt your documents before swapping the image. Paperless-ngx 3.x refuses to start if the passphrase key is still present in the environment.

# Running 2.x container — remove encryption:
docker compose exec paperless decrypt_documents

Let the command run to completion, then remove PAPERLESS_PASSPHRASE from your .env file. Document-level encryption has been removed without replacement in v3. If you need confidentiality at the storage layer, use LUKS or encrypted object-storage buckets instead.

Backup — What, Where, and How

The 3.0 database migration is not backwards-compatible: once you have upgraded, you cannot go back to 2.x. Back up both the PostgreSQL dump and the data directories before proceeding:

# PostgreSQL dump (running stack):
docker compose exec db pg_dump -U paperless paperless > backup_$(date +%F).sql

# Media and data directories:
tar czf paperless_media_$(date +%F).tar.gz ./media/ ./data/

Test the dump restore at least once in a staging environment. As the 3-2-1 backup strategy post puts it: a backup you have never restored is not a backup.

Upgrading with Docker Compose

The Updated compose.yaml

The three critical additions compared to a typical 2.x file are marked with comments:

# compose.yaml for paperless-ngx 3.x
services:
  paperless:
    image: ghcr.io/paperless-ngx/paperless-ngx:3.1.2
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    ports:
      - "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://redis:6379
      PAPERLESS_DBHOST: db
      PAPERLESS_DBENGINE: postgresql          # NEW in v3: mandatory field
      PAPERLESS_DBNAME: paperless
      PAPERLESS_DBUSER: paperless
      PAPERLESS_DBPASS: ${DB_PASS}
      PAPERLESS_SECRET_KEY: ${SECRET_KEY}    # 'change-me' is rejected
      PAPERLESS_TIME_ZONE: Europe/Berlin
      PAPERLESS_OCR_LANGUAGE: deu+eng
      PAPERLESS_URL: https://dokumente.example.com

  db:
    image: postgres:16-alpine
    restart: unless-stopped
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: paperless
      POSTGRES_USER: paperless
      POSTGRES_PASSWORD: ${DB_PASS}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U paperless"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    volumes:
      - redisdata:/data

volumes:
  pgdata:
  redisdata:

Why pin to 3.1.2 instead of :latest? The :latest tag reliably points to the latest stable release and is technically safe. I still recommend a pinned tag: an unexpected major version bump during docker compose pull can trigger exactly the breaking-change scenario you just escaped. An explicit tag forces a deliberate upgrade decision and makes it clear which version your instance is running at any given point.

For running the container behind a reverse proxy so that port 8000 is not directly exposed, nginx as a reverse proxy in Docker covers the fundamentals. The healthcheck approach for more complex multi-service stacks is explained in Docker Compose: multi-service setup with healthchecks.

Starting, Watching, and Verifying

docker compose pull
docker compose up -d
docker compose logs -f paperless

On first start you will see the database migrations run, followed by the Tantivy reindex. With a collection of around 5,000 documents on a modest server (4 vCPUs, SSD), the reindex in our environment took about 7 minutes. After that the web UI is fully accessible.

Tantivy in Day-to-Day Operation: What Actually Changes

Search responds noticeably faster, especially for multi-word queries or fuzzy searches (the tilde operator ~1 appended to a term). Tantivy also delivers result highlighting: matches are emphasised in the full-text preview, which helps spot misclassifications earlier.

Criterion Whoosh (up to 2.x) Tantivy (from 3.0)
Implementation Python Rust
Index build for 25,000 documents ~45 minutes ~15 minutes
9,000 search queries total ~1.5 minutes ~30 seconds
On-disk index size reference ~40 % smaller
Fuzzy search / highlighting rudimentary native
Bare-metal reindex command document_index reindex identical

For most deployments the only edge case worth checking: very unusual characters or CJK text without OCR should be validated in a test instance before going live. In normal office use this is not a concern.

Two Errors That Have Actually Occurred

Startup Failure: PAPERLESS_DBENGINE Missing

This is by far the most common error after swapping the image:

django.core.exceptions.ImproperlyConfigured:
  'PAPERLESS_DBENGINE' must be set explicitly.
  Supported values: postgresql, mariadb

Root cause: in v2 Paperless-ngx implicitly inferred the database engine from the presence of PAPERLESS_DBHOST — if set, PostgreSQL was assumed. That was error-prone. In v3 the field is mandatory. Fix: add PAPERLESS_DBENGINE: postgresql (or mariadb) to your Compose file and restart the container.

Migration Collision in 3.0.1 (Affects Early Adopters Only)

Anyone who updated to exactly 3.0.0 or 3.0.1 between 22 and 28 July 2026 may have seen this:

django.db.migrations.exceptions.InconsistentMigrationHistory:
  Migration paperless_mail.0002_optimize_integer_field_sizes
  is applied before its dependency
  paperless_mail.0001_1_clamp_mailrule_maximum_age
  on database 'default'.

Root cause: a retroactively inserted migration for the paperless_mail app conflicted with the migration state already applied from 3.0.0. This was a release-process error and was fixed in 3.0.2. Anyone upgrading directly to 3.0.2 or later is not affected. If you are stuck on 3.0.0 or 3.0.1, upgrading directly to 3.1.2 will succeed cleanly.

Frequently Asked Questions

Do I need to be on 2.20.15 before moving to 3.x? Yes. The supported direct upgrade path starts from 2.20.15. If your installation is older, update to 2.20.15 first, then make the jump to 3.x.

What happens to my consume scripts? Pre- and post-consume scripts that use positional arguments (e.g. $1 for the file path) will no longer be called — Paperless-ngx 3.0 passes all information exclusively via environment variables such as DOCUMENT_ID and DOCUMENT_FILE_NAME. Update your scripts accordingly.

Is there any way to keep Whoosh? No. Whoosh has been completely removed; there is no compatibility option or configuration flag.

How long does the Tantivy reindex take? A rough guide: 5,000 documents on an SSD in 5–10 minutes, 25,000 documents in roughly 15 minutes. The web UI remains reachable during the reindex but has limited search functionality.

Is the new plugin framework production-ready? It is present but officially marked as experimental. The project recommends validating any custom plugins thoroughly in a test instance before enabling them in production.

From Document Storage to Structured Archiving

Anyone running Paperless-ngx in production is often managing not just scanned receipts but also ZUGFeRD or XRechnung documents. For the regulatory picture of what is coming for businesses from 2027 onwards, see E-invoice obligation from 2027 — and why a GoBD-compliant archive is more than a good idea in that context.

Official documentation: Paperless-ngx Migration Guide v3, Changelog, GitHub Releases.

If you need assistance with setup, operations, or audit-proof document archiving, see our IT Support page for details.

Note: The articles on this blog are produced with the help of AI and are editorially reviewed before publication. Editorial responsibility lies with Emre Yurtbay (see the Impressum).

Discuss your project