Paperless-ngx на VPS: встановлення через Docker Compose
Розгорніть paperless-ngx на VPS через Docker Compose: офіційний стек Postgres, PAPERLESS_URL, папка consume, OCR-мови, HTTPS і резервні копії.
Що ви створюєте
Paperless-ngx на VPS перетворює папку зі сканованими документами на архів із можливістю пошуку. Ви додаєте PDF до каталогу, за яким ведеться спостереження, сервер запускає OCR (оптичне розпізнавання символів), витягує текст, визначає ймовірні дату та кореспондента і зберігає документ. Інсталяція складається з одного файлу Docker Compose із чотирма сервісами. Після цього потрібно виконати налаштування. Саме їм присвячено більшу частину цього посібника, оскільки через помилки в налаштуваннях інсталяції найчастіше не працюють.
Paperless-ngx — це версія оригінального проєкту Paperless, яку підтримує спільнота. Це безкоштовне рішення для самостійного розгортання. Документи зберігаються на диску як звичайні файли, тому ви завжди маєте доступ до власного архіву. Запуск на VPS, а не на домашньому комп’ютері, дає змогу отримувати доступ до сканів з будь-якого місця без відкриття порту на домашньому маршрутизаторі. Крім того, Paperless-ngx добре поєднується з приватним екземпляром Nextcloud для файлів, які не є паперовими документами.
Що насправді запускає стек
Офіційний файл compose запускає чотири контейнери. Розуміння призначення кожного з них спрощує аналіз журналів.
webserver: безпосередньо образ paperless-ngx. Він запускає вебінтерфейс, API, consumer, який стежить за вхідною папкою, і робочі процеси Celery, що виконують OCR.db: PostgreSQL. Він зберігає метадані, теги, кореспондентів і таблиці індексу повнотекстового пошуку. PDF-файли в ньому не зберігаються.broker: Valkey, сховище пар ключ-значення, сумісне з Redis. Воно працює як черга завдань між вебпроцесом і робочими процесами.gotenbergіtika: необов’язкові компоненти, доступні лише у варіантах compose-tika. Вони перетворюють документи Office (.docx,.xlsx,.odt) на PDF, щоб paperless міг їх індексувати.
Станом на липень 2026 року файл compose для postgres фіксує версії docker.io/library/postgres:18 і docker.io/valkey/valkey:9-alpine та завантажує застосунок із ghcr.io/paperless-ngx/paperless-ngx:latest.
Передумови
- VPS Ubuntu 24.04 на KVM із доступом через sudo, а також уже встановлені Docker і плагін Compose. Якщо ця частина для вас нова, спочатку ознайомтеся з матеріалом основи Docker Compose для VPS, а потім поверніться до цього посібника.
- Доменне ім’я із записом A, що вказує на VPS. Paperless відмовляється обслуговувати запити на імені хоста, яке не було для нього налаштоване, тому це важливо врахувати заздалегідь.
- Основним обмеженням є пам’ять. PostgreSQL, Valkey, gunicorn і один робочий процес Tesseract OCR можуть одночасно працювати в межах 2 GB за невеликого навантаження. Використовуйте 4 GB, якщо плануєте імпортувати сотні сканів, оскільки обробка OCR великого багатосторінкового PDF спричиняє стрибок споживання пам’яті, через який ядро може завершити робочий процес через нестачу пам’яті.
- Диск: архів зберігається двічі — як оригінальний файл і як архівний PDF із OCR, тому передбачте приблизно подвійну місткість відносно обсягу сканів.
Отримання офіційних compose-файлів
Існує інтерактивний інсталятор:
bash -c "$(curl --location --silent --show-error https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/install-paperless-ngx.sh)"Він ставить запитання та записує файли за вас. Ручне виконання складається з чотирьох команд і дає змогу знати, де саме розташовані всі файли. Це важливо для сервера, який ви обслуговуватимете.
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/.envВаріанти розташовані в одному каталозі: docker-compose.sqlite.yml, docker-compose.mariadb.yml і версія -tika для кожного з них. Для нової інсталяції виберіть postgres. SQLite підходить для кількох сотень документів, але індекс повнотекстового пошуку починає працювати повільно значно раніше, ніж PostgreSQL.
Файл .env містить один рядок: COMPOSE_PROJECT_NAME=paperless. Це ім’я стає префіксом для кожного контейнера та тому, тому не видаляйте його, а потім не з’ясовуйте, чому docker compose down -v не може знайти ваші дані.
Налаштуйте docker-compose.env перед першим запуском
Два параметри є обов’язковими. Згенеруйте секретний ключ командою, наведеною в документації проєкту:
python3 -c "import secrets; print(secrets.token_urlsafe(64))"Потім відредагуйте 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 постачається з буквальним значенням change-me. Цей параметр підписує cookie сеансів, тому його незмінене значення дає змогу будь-кому, хто знає значення за замовчуванням, підробити сеанс. Задайте його до першого запуску, оскільки подальша зміна призведе до виходу всіх користувачів із системи.
PAPERLESS_URL — це параметр, який заощаджує годину роботи. Paperless — це застосунок Django, а Django перевіряє заголовок Host кожного запиту. Задайте PAPERLESS_URL, і він автоматично заповнить для вас ALLOWED_HOSTS, CORS_ALLOWED_HOSTS та CSRF_TRUSTED_ORIGINS. Якщо залишити його порожнім, спрямувати домен на цей сервер, кожна сторінка повертатиме Bad Request (400), а в журналі контейнера з’являтиметься DisallowedHost. Укажіть значення без кінцевої косої риски та без шляху.
USERMAP_UID і USERMAP_GID задають користувача, від імені якого працює контейнер. Узгодьте їх зі своїм обліковим записом, перевіривши його за допомогою id -u і id -g. Якщо значення не збігаються, споживач не зможе читати файли, які ви копіюєте до папки consume, а в журналі замість імпорту з’явиться помилка доступу.
Запустіть стек і створіть першого користувача
docker compose pull
docker compose up -d
docker compose run --rm webserver createsuperuser
docker compose logs -f webservercreatesuperuser запитує ім’я користувача, адресу електронної пошти та пароль. Облікових даних для входу за замовчуванням немає, тому пропуск цього кроку призведе до сторінки входу, яка не прийме жодних даних. Перш ніж відкривати сторінку в браузері, дочекайтеся запису в журналі про те, що сервер прослуховує порт 8000. Під час першого запуску також виконуються міграції бази даних. Це займає одну-дві хвилини.
Перевірте роботу локально, перш ніж підключати домен:
curl -I http://127.0.0.1:8000Перенаправлення 302 на /accounts/login/ означає, що стек працює належним чином.
Увімкнення HTTPS перед застосунком
Стандартний файл compose публікує 8000:8000 і прив’язує його до всіх інтерфейсів. На загальнодоступному VPS це надає будь-кому, хто знайде адресу, доступ до всього архіву документів через звичайний HTTP. Змініть рядок порту, щоб прив’язати його лише до loopback:
ports:
- "127.0.0.1:8000:8000"Завершуйте TLS (безпеку транспортного рівня) на reverse proxy і перенаправляйте запити на 127.0.0.1:8000. Якщо це єдиний застосунок на сервері, підійде будь-який proxy з клієнтом ACME (середовищем автоматичного керування сертифікатами). Якщо за однією конфігурацією сертифіката працює кілька контейнерів, скористайтеся схемою reverse proxy Traefik для кількох застосунків Docker Compose і підключіть сервіс webserver до мережі proxy, не публікуючи порт взагалі.
Незалежно від вибраного proxy, він має передавати X-Forwarded-Proto: https. Без цього Django вважає, що запит надійшов через HTTP, перевірка джерела у формі входу завершується помилкою, і ви отримуєте CSRF verification failed. Request aborted. на сторінці, яка виглядає правильно. Друга частина виправлення — встановити PAPERLESS_URL точно на адресу https://, яку ви вводите в браузері.
Також збільште обмеження proxy на розмір завантаження. Скан розміром 40 MB через proxy, який обмежує тіло запиту 1 MB, буде відхилено ще до того, як його побачить paperless, а браузер повідомить про загальну помилку завантаження.
Як працює каталог імпорту
Файл compose монтує ./consume з каталогу compose у контейнер. Усе, що ви туди поміщаєте, імпортується, а потім видаляється з каталогу, оскільки файл тепер зберігається в томі медіафайлів під керуванням paperless.
cp ~/scan-2026-07-14.pdf ~/paperless/consume/
docker compose logs -f webserverВи маєте побачити, як consumer знаходить ім’я файлу, запускає OCR і завершує обробку рядком про додавання документа. Для односторінкового сканування весь цикл триває кілька секунд, а для великого документа може тривати хвилину або більше.
Два параметри змінюють спосіб пошуку файлів. PAPERLESS_CONSUMER_RECURSIVE=true змушує paperless шукати файли у вкладених каталогах, а PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS=true перетворює назву кожного вкладеного каталогу на тег. Тому файл, поміщений у consume/invoices/2026/, отримує теги invoices і 2026. Це найпростіша система класифікації, яку ви коли-небудь налаштуєте.
Виявлення файлів — інша частина механізму. За замовчуванням PAPERLESS_CONSUMER_POLLING_INTERVAL має значення 0. Це означає, що paperless використовує сповіщення файлової системи ядра, які надходять негайно. Такі сповіщення не передаються через мережеву файлову систему. Якщо каталог імпорту — це спільний ресурс NFS або SMB, у який може записувати мережевий сканер, файли не виявлятимуться. Виправлення полягає у встановленні для інтервалу додатного значення в секундах, щоб paperless натомість періодично сканував каталог.
Мови OCR і їхня вартість
PAPERLESS_OCR_LANGUAGE приймає трилітерний код Tesseract, за замовчуванням — eng. Поєднуйте мови за допомогою знака плюса, наприклад deu+eng. Після цього Tesseract перевіряє кожну мову та залишає найкращий результат, тому кожна додаткова мова збільшує процесорний час для кожної сторінки. На VPS зі спільним vCPU через це обробка сканованого документа може тривати не десять секунд, а хвилину. Вказуйте лише мови, якими фактично написані ваші документи.
Образ містить англійську, німецьку, італійську, іспанську та французьку мови. Для інших мов додайте їх до PAPERLESS_OCR_LANGUAGES через пробіл, наприклад PAPERLESS_OCR_LANGUAGES=tur ces, і перезапустіть контейнер. Під час запуску контейнер завантажує пакети даних Tesseract, тому перше завантаження після цієї зміни триватиме довше.
Створення резервної копії бази даних і медіафайлів
Копіювання Docker volumes під час роботи PostgreSQL створює резервну копію, яку може бути неможливо відновити. Paperless має власний експортер. Він записує документи та JSON-маніфест усіх метаданих у bind mount ./export:
docker compose exec webserver document_exporter ../export --delete --no-progress-bar--delete видаляє експортовані файли, які більше не відповідають поточному документу. Тому папка залишається дзеркальною копією, а не збільшується без обмежень. --no-progress-bar не виводить зайві повідомлення, коли команда запускається з cron.
Відновлення виконується за допомогою document_importer з тієї самої папки в новому stack. Отже, потрібно захищати лише каталог експорту. Регулярно передавайте його за межі сервера за допомогою зашифрованих резервних копій restic із дедуплікацією з VPS. Спочатку запускайте експорт, щоб restic ніколи не захоплював архів, записаний не повністю.
Перевіряйте резервну копію, переконавшись, що export/manifest.json існує, а кількість файлів відповідає кількості документів в інтерфейсі. Резервна копія, вміст якої ніколи не перевіряли, не є резервною копією.
FAQ
Чому після прив’язування домену до сервера кожна сторінка повертає "Bad Request (400)"?
Django відхилив заголовок Host, оскільки ваш домен не вказано в ALLOWED_HOSTS. Установіть PAPERLESS_URL=https://paperless.example.com у docker-compose.env без завершального слеша, потім виконайте docker compose up -d, щоб повторно створити контейнер. Саме редагування env-файлу нічого не змінює, оскільки запущений контейнер використовує середовище, з яким його було запущено.
Я додав PDF до папки consume, але нічого не сталося. У чому проблема?
Спочатку перевірте docker compose logs webserver. Помилка доступу означає, що USERMAP_UID і USERMAP_GID не відповідають обліковому запису, якому належить файл. Виправте їх і повторно створіть контейнер. Повна відсутність запису в журналі означає, що подія створення файлу не надійшла. Таке трапляється з мережевими ресурсами, оскільки сповіщення ядра не проходять через них. Установіть PAPERLESS_CONSUMER_POLLING_INTERVAL, наприклад, у 30, і paperless скануватиме папку кожні 30 секунд.
Чи можна запускати paperless-ngx із SQLite замість PostgreSQL?
Так, docker-compose.sqlite.yml підтримується та використовує менше пам’яті, тому підходить для невеликого VPS. Недолік стає помітним зі зростанням архіву: повнотекстовий пошук і масове редагування тегів помітно сповільнюються, коли кількість документів сягає тисяч. Подальша міграція потребує експорту та імпорту, тому виберіть PostgreSQL уже зараз, якщо очікуєте подальшого зростання архіву.
Скільки місця на диску насправді потребує архів сканованих документів?
Приблизно вдвічі більше за розмір вихідних файлів. Paperless зберігає оригінал без змін і створює другий PDF із результатом OCR та текстовим шаром для пошуку, а також невеликі ескізи. Чорно-білий скан лише з текстом розміром 200 KB залишається невеликим. Кольоровий скан довгого договору розміром 30 MB займає приблизно 60 MB. Якщо каталог експорту зберігається на тому самому диску, додайте його обсяг. Тоді той самий архів займатиме на диску утричі більше місця.
Чи потрібні мені контейнери Tika і Gotenberg?
Лише якщо потрібно індексувати файли Word, Excel або OpenDocument разом із PDF. Вони перетворюють ці формати на PDF, щоб paperless міг виконувати OCR і пошук. Вони також додають два запущені контейнери та потребують ще кількох сотень мегабайтів пам’яті. Тому не використовуйте їх на невеликому сервері, якщо всі файли, які ви зберігаєте, уже є PDF або зображеннями.