How to Run paperless-ngx for VPS with Docker Compose
Run paperless-ngx for VPS with Docker Compose: set PAPERLESS_URL, consume folder, OCR languages, HTTPS, official Postgres stack, and backups correctly.
Wetin you dey build
Paperless-ngx for VPS dey turn folder wey get scanned paper into archive wey you fit search. You go drop PDF for directory wey server dey watch. The server go run OCR (optical character recognition) for am, extract the text, guess date and correspondent, then file am. The installation na one Docker Compose file with four services. Everything after that na configuration. This guide spend most of the length for there, because na there installations dey fail.
Paperless-ngx na the community fork wey dem still dey maintain from the original Paperless project. E free, you host am yourself, and e dey store your documents as plain files for disk. So you no go ever lock out from your own archive. If you run am for VPS instead of home box, your scans go dey reachable from anywhere without opening port for your home router. E also work well with private Nextcloud instance for files wey no be paper.
Wetin the stack really dey run
The official compose file dey start four containers, and if you know wetin each one dey do, the logs go easier to understand.
webserver: Na the paperless-ngx image itself. E dey run the web interface, the API, the consumer wey dey monitor your input folder, and the Celery task workers wey dey do OCR.db: PostgreSQL. E dey store metadata, tags, correspondents, and the full-text search index tables. E no dey store your PDFs.broker: Valkey, na Redis-compatible key-value store. E dey serve as the task queue between the web process and the workers.gotenbergandtika: Dem optional, and na only for the-tikacompose variants. Dem dey convert Office documents (.docx,.xlsx,.odt) to PDF so paperless fit index dem.
As of July 2026, the postgres compose file dey pin docker.io/library/postgres:18 and docker.io/valkey/valkey:9-alpine, and e dey pull the app from ghcr.io/paperless-ngx/paperless-ngx:latest.
Wetin You Need Before You Start
- An Ubuntu 24.04 KVM VPS wey get sudo access, plus Docker with the Compose plugin already installed. If this part never clear, start with Docker Compose fundamentals for VPS and come back.
- A domain name with an A record wey dey point to the VPS. Paperless no go serve for hostname wey you never configure, so this matter important pass wetin you fit expect.
- Memory na the real limitation. PostgreSQL, Valkey, gunicorn and one Tesseract OCR worker fit dey run together inside 2 GB for light use. Give am 4 GB if you plan import backlog of hundreds of scans, because OCR for one large multi-page PDF na the memory spike wey fit make kernel out-of-memory killer kill the worker.
- Disk: your archive dey stored two times: the original file and an OCR'd archive PDF. So, plan for roughly double the size of your scans.
Get the official compose files
Interactive installer dey available:
bash -c "$(curl --location --silent --show-error https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/install-paperless-ngx.sh)"E dey ask questions and write the files for you. If you do am by hand, na four commands, and you go know where everything dey. Na this one you want for server wey you go maintain.
mkdir -p ~/paperless && cd ~/paperless
curl -fsSL -o docker-compose.yml https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docker/compose/docker-compose.postgres.yml
curl -fsSL -o docker-compose.env https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docker/compose/docker-compose.env
curl -fsSL -o .env https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docker/compose/.envThe variants dey inside the same directory: docker-compose.sqlite.yml, docker-compose.mariadb.yml, and one -tika version for each. Pick postgres for new install. SQLite dey okay for few hundred documents, but the full-text search index go slow well before PostgreSQL.
The .env file get one line, COMPOSE_PROJECT_NAME=paperless. That name go become the prefix for every container and volume, so no delete am come dey wonder why docker compose down -v no fit find your data.
Configure docker-compose.env before the first start
Settings two no be optional. Generate the secret key with the command wey project document:
python3 -c "import secrets; print(secrets.token_urlsafe(64))"Then edit docker-compose.env:
PAPERLESS_SECRET_KEY=<the long string you just generated>
PAPERLESS_URL=https://paperless.example.com
PAPERLESS_TIME_ZONE=Europe/Berlin
PAPERLESS_OCR_LANGUAGE=deu+eng
USERMAP_UID=1000
USERMAP_GID=1000PAPERLESS_SECRET_KEY ships as the literal value change-me. E dey sign session cookies, so if you leave am like that, anybody wey know the default fit forge session. Set am before the first start, because if you change am later, e go log every user out.
PAPERLESS_URL na the one wey fit save you one hour. Paperless na Django application, and Django dey validate the Host header for every request. Set PAPERLESS_URL and e go fill ALLOWED_HOSTS, CORS_ALLOWED_HOSTS and CSRF_TRUSTED_ORIGINS for you. If you leave am empty and point domain to the box, every page go return Bad Request (400), while DisallowedHost go show for the container log. Write am without trailing slash and without path.
USERMAP_UID and USERMAP_GID set the user wey container go run as. Make dem match your own account, after you don check am with id -u and id -g. If dem no match, files wey you copy enter the consume folder no go readable by the consumer, and the log go show permission error instead of import.
Start the stack and create the first user
docker compose pull
docker compose up -d
docker compose run --rm webserver createsuperuser
docker compose logs -f webservercreatesuperuser go ask for username, email and password. No default login dey, so if you skip this step, you go reach sign-in page wey no go ever accept anything. Wait for log line wey report say server dey listen on port 8000 before you try browser. The very first start dey also run database migrations, and e fit take one or two minutes.
Check am locally before you involve domain:
curl -I http://127.0.0.1:8000A 302 redirect go /accounts/login/ mean say the stack dey healthy.
Put HTTPS in front of am
The stock compose file dey publish 8000:8000, wey bind to every interface. For public VPS, this one dey serve your complete document archive over plain HTTP to anybody wey find the address. Change the port line make e bind to loopback only:
ports:
- "127.0.0.1:8000:8000"Then terminate TLS (transport layer security) for reverse proxy and forward am to 127.0.0.1:8000. If na only app wey dey run for the box, any proxy wey get ACME (automatic certificate management environment) client go work. If you dey run many containers behind one certificate setup, follow the Traefik reverse proxy pattern for multiple Docker Compose apps and attach the webserver service to the proxy network without any published port.
Any proxy wey you use must send X-Forwarded-Proto: https. Without am, Django go believe say the request enter through HTTP, the origin check for the login form go fail, and you go see CSRF verification failed. Request aborted. for page wey look correct. The other part of this fix na to set PAPERLESS_URL to the exact https:// address wey you type for browser.
Also increase the proxy upload size limit. If you send 40 MB scan through proxy wey dey cap bodies at 1 MB, e go reject am before paperless even see am, and browser go report general upload failure.
How consume directory dey work
The compose file dey bind-mount ./consume from the compose directory enter the container. Anything wey you put there go import, then e go delete am from the folder, because the file don dey inside the media volume under paperless management.
cp ~/scan-2026-07-14.pdf ~/paperless/consume/
docker compose logs -f webserverYou suppose see the consumer pick the filename, run OCR, and finish with one line wey report say dem add the document. The whole cycle na seconds for one-page scan, but e fit take one minute or more for long document.
Two settings dey change how paperless dey find files. PAPERLESS_CONSUMER_RECURSIVE=true make paperless check subfolders, while PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS=true turn each subfolder name to tag. So if you drop file inside consume/invoices/2026/, e go tag am invoices and 2026. Na the cheapest filing system wey you go ever build.
Detection na the other part. By default, PAPERLESS_CONSUMER_POLLING_INTERVAL na 0. This mean say paperless dey use kernel filesystem notifications, and dem dey fire immediately. Those notifications no dey cross network filesystem. If your consume folder na NFS or SMB share wey network scanner fit write to, nothing go ever detect. To fix am, set the interval to positive number of seconds so paperless go scan the folder instead.
OCR languages, and wetin dem cost
PAPERLESS_OCR_LANGUAGE dey take three-letter Tesseract code, eng by default. Join languages with plus sign, like deu+eng. Tesseract go try each one and keep the best result, so every extra language dey multiply CPU time wey e spend for every page. For VPS wey dey share vCPU, na the difference between scan wey go finish for ten seconds and one wey go finish for one minute. List only the languages wey your documents really use.
The image get English, German, Italian, Spanish and French. For any other language, add am to PAPERLESS_OCR_LANGUAGES as space-separated list, for example PAPERLESS_OCR_LANGUAGES=tur ces, then restart. The container downloads the Tesseract data packs for startup, so the first boot after you make that change go slow pass.
Database and media back up
PostgreSQL dey run when you copy Docker volumes, the backup fit no restore properly. Paperless get im own exporter, wey dey write documents plus JSON manifest of all metadata inside the ./export bind mount:
docker compose exec webserver document_exporter ../export --delete --no-progress-bar--delete dey remove exported files wey no match any current document again, so the folder go remain mirror instead of growing forever. --no-progress-bar dey keep the output clean when cron dey run am.
To restore, na document_importer against that same folder for fresh stack. This mean say na only the export directory you need keep safe. Send am offsite according to schedule with encrypted, deduplicated restic backups from your VPS, and run the export first so restic no capture archive wey never finish writing.
Verify backup by checking say export/manifest.json dey exist and say the file count match your document count for the interface. Backup wey you never list no be backup.
FAQ
Why every page dey return "Bad Request (400)" after I point my domain to am?
Django reject the Host header because your domain no dey inside ALLOWED_HOSTS. Set PAPERLESS_URL=https://paperless.example.com inside docker-compose.env, without trailing slash, then run docker compose up -d to recreate the container. Editing the env file alone no go do anything, because the running container keep the environment wey e start with.
I put PDF for the consume folder but nothing happen. Wetin be wrong?
Check docker compose logs webserver first. Permission error mean USERMAP_UID and USERMAP_GID no match the account wey own the file, so fix dem and recreate the container. If no log line show at all, the file event no arrive. This one dey happen for network shares because kernel notifications no cross them. Set PAPERLESS_CONSUMER_POLLING_INTERVAL to something like 30 and paperless go scan the folder every 30 seconds instead.
I fit run paperless-ngx with SQLite instead of PostgreSQL?
Yes, docker-compose.sqlite.yml dey supported and e use less memory, so e fit small VPS. The tradeoff go show as your archive dey grow: full-text search and bulk tag edits go slow noticeably when documents reach thousands. If you migrate later, you need export and import. So choose PostgreSQL now if you expect the archive to keep growing.
How much disk archive of scans really need?
Use roughly twice the size of your source files. Paperless keep the original unchanged and store another OCR'd PDF with searchable text layer, plus small thumbnails. A 200 KB text-only scan remain small. A 30 MB colour scan of a long contract go store about 60 MB. Add the export directory if you keep am for the same disk, and the same archive go occupy three times the space on disk.
I need the Tika and Gotenberg containers?
Only if you want Word, Excel or OpenDocument files indexed together with your PDFs. Dem convert those formats to PDF so paperless fit do OCR and search them. Dem also add two more running containers and some hundred megabytes of memory, so skip dem for small box if everything wey you file already be PDF or image.