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 не є фототекою: OCR і визначення кореспондента не допоможуть із папкою святкових JPEG, тому зберігайте їх у фотосервері, призначеному для цього, а Paperless використовуйте для документів. Те саме стосується відео: колекцію скопійованих фільмів слід зберігати на медіасервері, де щось на кшталт інтерфейсу Jellyfin у стилі відеопрокату 90-х робить зручну навігацію основною функцією, а не пошук.
Paperless-ngx — це підтримуваний спільнотою форк початкового проєкту Paperless. Це безкоштовне self-hosted рішення, яке зберігає документи як звичайні файли на диску, тому ви завжди матимете доступ до власного архіву. Запуск на VPS замість домашнього комп’ютера дає змогу отримувати доступ до сканів з будь-якого місця без відкриття порту на домашньому маршрутизаторі. Рішення також добре поєднується з приватним екземпляром Nextcloud для файлів, які не є документами. Те саме стосується комп’ютера, до якого підключено сканер: власний relay RustDesk на цьому VPS дає змогу керувати цим комп’ютером віддалено, також не відкриваючи порт на маршрутизаторі.
Що насправді запускає стек
Офіційний compose-файл запускає чотири контейнери. Розуміння призначення кожного з них спрощує читання журналів.
webserver: сам образ paperless-ngx. Він запускає вебінтерфейс, API, consumer, який стежить за вхідною папкою, і робочі процеси Celery, що виконують OCR.db: PostgreSQL. Він зберігає метадані, теги, кореспондентів і таблиці індексу повнотекстового пошуку. PDF-файли в ньому не зберігаються.broker: Valkey, сховище ключ-значення, сумісне з Redis. Воно працює як черга завдань між веб-процесом і робочими процесами.gotenbergіtika: необов’язкові компоненти, які використовуються лише у варіантах compose для-tika. Вони перетворюють документи Office (.docx,.xlsx,.odt) на PDF, щоб paperless міг їх індексувати.
Станом на October 2026 compose-файл для postgres фіксує версії docker.io/library/postgres:18 і docker.io/valkey/valkey:9-alpine, а образ застосунку завантажується з ghcr.io/paperless-ngx/paperless-ngx:latest.
Передумови
- Ubuntu 24.04 KVM VPS із доступом через sudo, а також уже встановлені Docker і Compose plugin. Якщо ця частина для вас нова, почніть із основ Docker Compose для VPS, а потім поверніться.
- Доменне ім’я з A-записом, що вказує на VPS. Paperless не приймає вхід через HTTPS на імені хоста, про яке його не повідомили, тому це потрібно налаштувати раніше, ніж ви очікуєте.
- Основне обмеження — пам’ять. PostgreSQL, Valkey, вебсервер Granian і worker Tesseract OCR можуть одночасно працювати в межах 2 GB для невеликого навантаження. Виділіть 4 GB, якщо плануєте імпортувати чергу зі сотень сканів, оскільки OCR великого багатосторінкового PDF створює пік споживання пам’яті, через який kernel може завершити worker через нестачу пам’яті.
- Диск: ваш архів зберігається двічі — як оригінальний файл і як 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. Цей параметр підписує токени сеансів. У поточних випусках це значення вважається невстановленим: paperless відмовляється запускатися, якщо ключ відсутній, порожній або досі має значення change-me. Тому стек не запуститься, доки ви не заміните його. Встановіть ключ до першого запуску, оскільки подальша зміна призведе до виходу всіх користувачів із системи.
PAPERLESS_URL — це параметр, який заощаджує годину діагностики. Paperless — це застосунок Django, а Django перевіряє заголовок Host і, у формі входу, джерело, яке передає браузер. Встановіть PAPERLESS_URL, і застосунок додасть вашу адресу до ALLOWED_HOSTS, CORS_ALLOWED_HOSTS та CSRF_TRUSTED_ORIGINS. Якщо залишити цей параметр порожнім і спрямувати домен на сервер, сторінка входу все одно завантажиться, оскільки PAPERLESS_ALLOWED_HOSTS за замовчуванням має значення *. Проте надсилання форми через HTTPS завершиться помилкою CSRF verification failed. Request aborted., оскільки ваш домен не є довіреним джерелом. Записуйте адресу без кінцевої косої риски та без шляху.
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 ще відкритий для інтернету, цим відвідувачем можете бути не ви. Тому одразу створіть обліковий запис із shell. Перш ніж відкривати сторінку в браузері, дочекайтеся рядка журналу про те, що сервер прослуховує порт 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 (transport layer security) у reverse proxy та передавайте запити на 127.0.0.1:8000. Якщо це єдиний застосунок на сервері, підійде будь-який проксі з ACME (automatic certificate management environment) client. Якщо за однією конфігурацією сертифікатів працює кілька контейнерів, скористайтеся шаблоном reverse proxy Traefik для кількох застосунків Docker Compose і під’єднайте сервіс webserver до мережі проксі без опублікованого порту.
Незалежно від вибраного проксі, саме перевірка origin у формі входу часто спричиняє проблеми в конфігураціях HTTPS. Paperless ігнорує X-Forwarded-Proto, якщо не встановити PAPERLESS_PROXY_SSL_HEADER. За замовчуванням цей параметр вимкнений, тому Django обробляє кожен запит через проксі як звичайний HTTP-запит, а origin https://, який надсилає браузер, проходить перевірку лише тоді, коли його внесено до списку довірених. Параметр PAPERLESS_URL, встановлений точно в адресу https://, яку ви вводите в браузері, додає її до цього списку. Без нього ви отримуєте CSRF verification failed. Request aborted. на сторінці, яка виглядає правильно.
Також збільште ліміт розміру завантажень у проксі. Скан розміром 40 MB через проксі, який обмежує тіло запиту до 1 MB, буде відхилено ще до того, як його побачить paperless, а браузер покаже загальну помилку завантаження.
Як працює каталог consume
Файл compose монтує каталог ./consume з каталогу compose у контейнер. Усе, що ви туди поміщаєте, імпортується, а потім видаляється з цього каталогу, оскільки файл тепер зберігається в media volume під керуванням 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 використовує системні сповіщення файлової системи, які надходять негайно. Такі сповіщення ненадійні в мережевій файловій системі. Якщо каталог consume розміщений на NFS або SMB share, щоб мережевий сканер міг записувати туди файли, вони можуть залишатися невиявленими. Щоб виправити це, задайте додатне значення інтервалу в секундах, щоб paperless сканував каталог замість використання сповіщень.
Мови OCR і їхня вартість
PAPERLESS_OCR_LANGUAGE приймає трибуквений код Tesseract; за замовчуванням використовується eng. Поєднуйте мови знаком «+», наприклад deu+eng. Після цього Tesseract обробляє кожну сторінку всіма переліченими мовами, тому кожна додаткова мова збільшує час використання CPU для кожного сканування. На VPS зі спільним vCPU це може означати, що сканування триватиме не десять секунд, а хвилину. Вказуйте лише ті мови, якими фактично написані ваші документи.
В образі доступні англійська, німецька, італійська, іспанська та французька мови. Для інших мов додайте їх до PAPERLESS_OCR_LANGUAGES як список, розділений пробілами, наприклад PAPERLESS_OCR_LANGUAGES=tur ces, і перезапустіть контейнер. Контейнер завантажує пакети даних Tesseract під час запуску, тому перший запуск після цієї зміни триватиме довше.
Резервне копіювання бази даних і медіафайлів
Копіювання Docker volumes під час роботи PostgreSQL створює резервну копію, яку може бути неможливо відновити. Paperless має власний exporter. Він записує документи разом із JSON-маніфестом усіх метаданих у bind mount ./export:
docker compose exec -T webserver document_exporter ../export --delete --no-progress-bar--delete видаляє експортовані файли, які більше не відповідають поточному документу. Тому папка залишається дзеркальною копією, а не збільшується без обмежень. --no-progress-bar не виводить зайвий текст, коли команда запускається через cron, а -T не дає Compose запитувати термінал, якого cron не має.
Відновлення виконується через document_importer з тієї самої папки в новому stack. Це означає, що захищати від втрати потрібно лише каталог експорту. За розкладом передавайте його на віддалене сховище за допомогою зашифрованих дедуплікованих резервних копій restic із вашого VPS, а спочатку запускайте експорт, щоб restic ніколи не захоплював архів, який ще записується.
Перевірте резервну копію: переконайтеся, що існує export/manifest.json, а кількість файлів відповідає кількості документів в інтерфейсі. Резервна копія, яку ви ніколи не перевіряли списком файлів, не є надійною резервною копією. Ще гірше, коли нічний експорт непомітно починає завершуватися з помилкою. Тому cron job має передавати свій статус завершення на власний ntfy server. Так ви дізнаєтеся про проблему в день її виникнення, а не в день, коли знадобиться відновлення.
FAQ
Чому після прив’язування домену до сервісу кожна сторінка повертає "Bad Request (400)"?
У штатних файлах цього бути не повинно, оскільки PAPERLESS_ALLOWED_HOSTS за замовчуванням має значення *. Помилка 400 із DisallowedHost у журналі контейнера означає, що для PAPERLESS_ALLOWED_HOSTS задано список, до якого не входить ваш домен. Укажіть PAPERLESS_URL=https://paperless.example.com у docker-compose.env без кінцевої косої риски, і paperless додасть це ім’я хоста до списку, або видаліть рядок PAPERLESS_ALLOWED_HOSTS. Потім виконайте 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 і додати документи до пошуку. Вони також додають ще 2 запущені контейнери та потребують кількасот мегабайт пам’яті. Тому на невеликому сервері їх можна не використовувати, якщо всі файли вже мають формат PDF або є зображеннями.