From paperless-gpt to Paperless-NGX v3: dropping a container, cutting complexity Paperless-NGX v3.0.0 upgrade required replacing the paperless-gpt side container with built-in AI configured for Gemini via the OpenAI-compatible endpoint, and the embedding model name must be 'gemini-embedding-001' instead of 'text-embedding-004'. The upgrade of a 1,805-document library took 11 minutes, with four config changes including pinning the image to 3.0.0, setting PAPERLESS_DBENGINE to postgresql, changing OCR_MODE to skip_noarchive, and deleting PAPERLESS_OCR_SKIP_ARCHIVE_FILE. The author built a rollback plan with backups and hardlink snapshots, but never needed it. From paperless-gpt to Paperless-NGX v3: dropping a container, cutting complexity Upgrading to Paperless-NGX v3, replacing the paperless-gpt side container with the new built-in AI configured for Gemini via the OpenAI-compatible endpoint. Includes the one embedding model gotcha that took me ten minutes to find. The 404 that took me ten minutes to understand OpenAIError "Error code: 404 - {'error': {'code': 404, 'message': 'models/text-embedding-004 is not found for API version v1main, or is not supported for embedContent...'}}" That was Paperless-NGX v3, after I'd upgraded my 1,805-document library, ripped out the side container that used to handle AI, and pointed the new built-in AI at Gemini. text-embedding-004 is the name Google's own docs use for their embedding model. It's the name every tutorial uses. It is not the name Gemini's OpenAI-compatible endpoint accepts. The correct name is gemini-embedding-001 . Nothing on the internet told me that. This post exists so you don't spend ten minutes staring at a stack trace to find it. Everything else about the v3 upgrade - the DB migration, the rollback plan, dropping the paperless-gpt container, the config-as-code work - was routine. That one line was the whole trick. Before I upgraded, I built a rollback The v3 migration doc has one line worth taking seriously: "the document contains no rollback procedures or downgrade guidance." Once v3's Postgres migrations run, you're on the new schema. There is no downgrade command. So the plan started with backups - not the daily one that's been running for months, but a fresh one, five minutes before the pull: 1. Fresh Paperless export the app's own backup ./paperless.sh backup 2. Direct pg dump into a file under my control docker exec paperless-db pg dump -U paperless -Fc paperless \ pre-v3-dump.pgcustom 3. Hardlink snapshots of media/ and data/ zero extra disk on the same FS cp -al media media.pre-v3 cp -al data data.pre-v3 4. Pin the current 2.20.15 image by digest docker inspect paperless-ngx --format '{{.Image}}' pre-v3-image-digest.txt 5. Snapshot .env and docker-compose.yml cp .env .env.pre-v3 cp docker-compose.yml docker-compose.yml.pre-v3 The cp -al trick is the one worth knowing: on the same filesystem it makes a hardlink tree that costs zero bytes until v3 actually rewrites a file. If anything had gone sideways, I could stop the container, pg restore the dump, rename my snapshots back into place, revert the compose file, and be running 2.20.15 again in a couple of minutes. I never had to. But knowing I could is what let me hit "go" at all. The four config changes v3 forced Reading the migration doc, I made a list of what would break in my existing docker-compose.yml : | Before v2.20.15 | After v3.0.0 | Why | |---|---|---| image: paperless-ngx:latest | image: paperless-ngx:3.0.0 | Never let a docker compose pull do a major-version upgrade unattended. Pin. | no PAPERLESS DBENGINE | PAPERLESS DBENGINE: postgresql | v3 no longer infers the engine from DBHOST . Must be explicit. | PAPERLESS OCR MODE: skip | PAPERLESS OCR MODE: skip noarchive | The skip variant was removed. skip noarchive is the closest match. | PAPERLESS OCR SKIP ARCHIVE FILE: with text | deleted | The setting is gone; behaviour is folded into OCR MODE . | That's it. Everything else - 40-ish other env vars for barcodes, ASN, storage paths, mail rules, workflows - carried through untouched. The upgrade itself: 11 minutes I made the four edits, changed the image tag, ran docker compose pull paperless && docker compose up -d paperless , and tailed the logs. Running migrations: Applying documents.0003 remove document storage type... OK Applying documents.0004 workflowtrigger filter has any correspondents and more... OK ... through 0015 ... paperless.migrations Recomputing SHA-256 checksums for 1809 document s ... paperless.migrations SHA-256 checksum progress: 500/1809 27% The checksum recompute is a v3 thing - every document gets re-hashed under the new integrity system. It ran for about six minutes. Then the search index switched from Whoosh v2's default to Tantivy v3's default in the background. docker compose ps said healthy after eleven minutes total. Nothing broke. All documents present, 20 workflows intact, 522 correspondents there, mail rules still firing. Ripping out paperless-gpt My old setup had a companion container called paperless-gpt - a Go service that watched Paperless via API, called Gemini for each new document, and wrote back title/tag/correspondent/type suggestions. It worked well. It also meant: - Two containers to keep updated - A Google Cloud service-account JSON file baked into a bind mount - API tokens and prompt templates to remember to back up - A layer of indirection every time I wanted to change an AI setting v3 folds all of that into the main app. What v3's built-in AI actually does: - Every document details page has a Suggestions drawer with LLM-generated proposals for title, correspondent, document type, tags, storage path, and dates. One click accepts each. - The LLM index gets consulted internally as RAG context similar prior documents feed the suggestion prompt, so your existing correspondent and tag conventions carry over to new docs . - There's also a chat interface for asking questions across the library /api/chat/ streams answers; the UI exposes it as an "Ask AI" panel . - A weekly cron rebuilds the index for consistency; incremental updates happen automatically on ingest. - One PAPERLESS AI ENABLED flag turns the whole thing on or off. Same behaviour paperless-gpt gave me, minus a container. Wiring Gemini into v3 the gotcha, in context v3's dropdown for LLM Backend offers two options: openai-like and ollama . That's it. No "Gemini" option. Gemini exposes an OpenAI-compatible endpoint, so openai-like is the right choice - you just fill in Gemini's URL. Here's what I have in my docker-compose.yml : environment: PAPERLESS AI ENABLED: "true" PAPERLESS AI LLM BACKEND: "openai-like" PAPERLESS AI LLM ENDPOINT: "https://generativelanguage.googleapis.com/v1beta/openai/" PAPERLESS AI LLM MODEL: "gemini-2.5-flash" PAPERLESS AI LLM API KEY: "