Paperless-ngx na VPS: instalacja Docker Compose
Instalacja Paperless-ngx na serwerze VPS przy użyciu Docker Compose. Poradnik obejmuje konfigurację Postgres, zmienną PAPERLESS_URL, OCR, HTTPS oraz procedurę tworzenia kopii.
Co budujesz
Paperless-ngx na serwerze VPS przekształca folder zeskanowanych dokumentów w przeszukiwalne archiwum. Po umieszczeniu pliku PDF w monitorowanym katalogu, serwer przeprowadza na nim proces OCR (optyczne rozpoznawanie znaków), wyodrębnia tekst, przewiduje datę oraz nadawcę i archiwizuje dokument. Instalacja składa się z jednego pliku Docker Compose z czterema usługami. Wszystko, co następuje później, to konfiguracja, której poświęcono większość tego przewodnika, ponieważ to właśnie tam najczęściej dochodzi do błędów. System ten nie jest biblioteką zdjęć: OCR i mechanizmy rozpoznawania nadawców nie sprawdzają się w przypadku folderów ze zdjęciami z wakacji, dlatego należy je umieścić w serwerze zdjęć przeznaczonym do tego celu, a Paperless zachować wyłącznie dla dokumentów papierowych.
Paperless-ngx to utrzymywana przez społeczność wersja rozwijana na bazie oryginalnego projektu Paperless. Jest bezpłatny i przeznaczony do samodzielnego hostowania. Dokumenty są przechowywane jako zwykłe pliki na dysku, więc dostęp do własnego archiwum nie zależy od zewnętrznej usługi. Uruchomienie go na VPS zamiast na komputerze domowym oznacza dostęp do skanów z dowolnego miejsca bez otwierania portu na domowym routerze. Dobrze współpracuje też z prywatną instancją Nextcloud do przechowywania plików, które nie są papierowe. Ta sama zasada dotyczy komputera, do którego podłączono skaner. Własny relay RustDesk na tym VPS umożliwia zdalną obsługę tego komputera bez otwierania portu na routerze.
Co faktycznie uruchamia stos
Oficjalny plik compose uruchamia cztery kontenery, a zrozumienie roli każdego z nich pozwala na czytelną analizę logów.
webserver: sam obraz paperless-ngx. Obsługuje interfejs WWW, API, konsumenta monitorującego folder wejściowy oraz procesy robocze Celery wykonujące OCR.db: PostgreSQL. Przechowuje metadane, tagi, korespondentów oraz tabele indeksu pełnotekstowego. Nie przechowuje plików PDF.broker: Valkey, magazyn klucz-wartość zgodny z Redis. Pełni rolę kolejki zadań między procesem WWW a procesami roboczymi.gotenbergoraztika: opcjonalne, dostępne tylko w wariantach compose-tika. Konwertują dokumenty Office (.docx,.xlsx,.odt) do formatu PDF, aby paperless mógł je zaindeksować.
Według stanu na lipiec 2026 plik compose dla postgres przypisuje wersje docker.io/library/postgres:18 i docker.io/valkey/valkey:9-alpine oraz pobiera aplikację z ghcr.io/paperless-ngx/paperless-ngx:latest.
Wymagania wstępne
- Serwer VPS z systemem Ubuntu 24.04 działający w środowisku KVM, z dostępem sudo oraz zainstalowanym Dockerem wraz z wtyczką Compose. Jeśli te zagadnienia są nowe, należy zacząć od podstaw Docker Compose dla VPS i wrócić tutaj.
- Nazwa domeny z rekordem A wskazującym na adres IP serwera VPS. Paperless odmawia obsługi żądań dla nazw hostów, które nie zostały zdefiniowane w konfiguracji, dlatego jest to istotne na wcześniejszym etapie, niż można przypuszczać.
- Pamięć RAM jest głównym ograniczeniem. PostgreSQL, Valkey, gunicorn oraz proces roboczy Tesseract OCR wymagają łącznie około 2 GB pamięci RAM przy lekkim obciążeniu. Należy przydzielić 4 GB, jeśli planowane jest zaimportowanie dużej liczby skanów, ponieważ proces OCR dla obszernych plików PDF generuje skoki zapotrzebowania na pamięć, które mogą prowadzić do zakończenia procesu przez mechanizm out-of-memory killer jądra systemu.
- Dysk: archiwum jest przechowywane w dwóch kopiach – pliku oryginalnego oraz pliku PDF z warstwą OCR, dlatego należy zaplanować przestrzeń dyskową o rozmiarze około dwukrotnie większym niż suma wszystkich skanów.
Pobieranie oficjalnych plików compose
Dostępny jest interaktywny instalator:
bash -c "$(curl --location --silent --show-error https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/install-paperless-ngx.sh)"Narzędzie zadaje pytania i automatycznie tworzy pliki. Ręczna konfiguracja wymaga wykonania czterech poleceń, ale pozwala na pełne zrozumienie struktury plików, co jest kluczowe w przypadku serwera przeznaczonego do długoterminowego utrzymania.
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/.envWarianty znajdują się w tym samym katalogu: docker-compose.sqlite.yml, docker-compose.mariadb.yml oraz wersja -tika dla każdego z nich. W przypadku nowej instalacji należy wybrać postgres. SQLite sprawdza się przy kilkuset dokumentach, jednak indeks wyszukiwania pełnotekstowego zwalnia znacznie szybciej niż w przypadku PostgreSQL.
Plik .env zawiera jedną linię: COMPOSE_PROJECT_NAME=paperless. Ta nazwa staje się prefiksem dla każdego kontenera i wolumenu, dlatego nie należy jej usuwać, aby uniknąć problemów z odnalezieniem danych przez docker compose down -v.
Konfiguracja docker-compose.env przed pierwszym uruchomieniem
Dwa ustawienia nie są opcjonalne. Wygeneruj klucz tajny za pomocą polecenia wskazanego w dokumentacji projektu:
python3 -c "import secrets; print(secrets.token_urlsafe(64))"Następnie edytuj 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 jest dostarczany z wartością dosłowną change-me. Służy on do podpisywania plików cookie sesji, więc pozostawienie go w tym stanie oznacza, że każdy, kto zna wartość domyślną, może sfałszować sesję. Ustaw go przed pierwszym uruchomieniem, ponieważ późniejsza zmiana spowoduje wylogowanie wszystkich użytkowników.
PAPERLESS_URL to ustawienie, które pozwala zaoszczędzić godzinę pracy. Paperless jest aplikacją Django, a Django weryfikuje nagłówek Host każdego żądania. Ustaw PAPERLESS_URL, a system automatycznie uzupełni ALLOWED_HOSTS, CORS_ALLOWED_HOSTS oraz CSRF_TRUSTED_ORIGINS. Jeśli pozostawisz to pole puste, skierujesz domenę na serwer, a każda strona zwróci błąd Bad Request (400) z informacją DisallowedHost w dzienniku kontenera. Wpisz adres bez końcowego ukośnika i bez ścieżki.
USERMAP_UID oraz USERMAP_GID określają użytkownika, z uprawnieniami którego działa kontener. Dopasuj je do swojego konta, które sprawdzisz za pomocą id -u oraz id -g. Jeśli wartości te nie będą zgodne, pliki skopiowane do folderu consume będą nieczytelne dla procesu przetwarzania, a w dzienniku pojawi się błąd uprawnień zamiast importu.
Uruchomienie stosu i utworzenie pierwszego użytkownika
docker compose pull
docker compose up -d
docker compose run --rm webserver createsuperuser
docker compose logs -f webservercreatesuperuser wymaga podania nazwy użytkownika, adresu e-mail oraz hasła. Brak domyślnych danych logowania sprawia, że pominięcie tego kroku pozostawi użytkownika na stronie logowania, która nie zaakceptuje żadnych danych. Przed próbą połączenia przez przeglądarkę należy zaczekać na wpis w dzienniku informujący, że serwer nasłuchuje na porcie 8000. Pierwsze uruchomienie obejmuje również migrację bazy danych, co może potrwać od jednej do dwóch minut.
Przed konfiguracją domeny należy sprawdzić działanie lokalnie:
curl -I http://127.0.0.1:8000Przekierowanie 302 na /accounts/login/ oznacza, że stos działa poprawnie.
Zabezpieczenie HTTPS przed aplikacją
Domyślny plik compose publikuje 8000:8000, co powoduje powiązanie z każdym interfejsem sieciowym. Na publicznym serwerze VPS udostępnia to całe archiwum dokumentów przez nieszyfrowany protokół HTTP każdemu, kto trafi na dany adres. Należy zmienić linię portu tak, aby powiązać ją wyłącznie z interfejsem loopback:
ports:
- "127.0.0.1:8000:8000"Następnie należy przeprowadzić terminację TLS (transport layer security) w reverse proxy i przekierować ruch na 127.0.0.1:8000. Jeśli jest to jedyna aplikacja na serwerze, sprawdzi się dowolne proxy z klientem ACME (automatic certificate management environment). W przypadku uruchamiania kilku kontenerów za jednym zestawem certyfikatów, należy postępować zgodnie z wzorcem reverse proxy Traefik dla wielu aplikacji Docker Compose i podłączyć usługę webserver do sieci proxy bez publikowania żadnego portu.
Niezależnie od użytego proxy, musi ono przesyłać nagłówek X-Forwarded-Proto: https. Bez niego Django uznaje, że żądanie nadeszło przez HTTP, weryfikacja pochodzenia w formularzu logowania kończy się niepowodzeniem, a użytkownik otrzymuje CSRF verification failed. Request aborted. na stronie, która wygląda poprawnie. Drugą częścią tego rozwiązania jest ustawienie PAPERLESS_URL na dokładnie taki adres https://, jaki wpisuje się w przeglądarce.
Należy również zwiększyć limit rozmiaru przesyłanych plików w konfiguracji proxy. Skan o rozmiarze 40 MB przesyłany przez proxy z limitem 1 MB zostanie odrzucony, zanim dotrze do paperless, a przeglądarka zgłosi ogólny błąd przesyłania.
Jak działa katalog consume
Plik compose montuje ./consume z katalogu compose do kontenera. Każdy plik umieszczony w tym miejscu jest importowany, a następnie usuwany z folderu, ponieważ plik znajduje się już w wolumenie media pod zarządem paperless.
cp ~/scan-2026-07-14.pdf ~/paperless/consume/
docker compose logs -f webserverKonsument powinien wykryć nazwę pliku, przeprowadzić proces OCR i zakończyć działanie komunikatem o dodaniu dokumentu. Cały cykl trwa kilka sekund w przypadku skanu jednostronicowego i może zająć minutę lub dłużej dla długiego dokumentu.
Dwa ustawienia zmieniają sposób wyszukiwania plików. PAPERLESS_CONSUMER_RECURSIVE=true sprawia, że paperless przeszukuje podkatalogi, a PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS=true zamienia nazwę każdego podkatalogu na tag, więc umieszczenie pliku w consume/invoices/2026/ nadaje mu tagi invoices oraz 2026. Jest to najtańszy system archiwizacji, jaki można zbudować.
Wykrywanie to druga połowa procesu. Domyślnie PAPERLESS_CONSUMER_POLLING_INTERVAL ma wartość 0, co oznacza, że paperless korzysta z powiadomień jądra systemu plików, które są wyzwalane natychmiastowo. Powiadomienia te nie działają w sieciowych systemach plików. Jeśli folder consume jest udziałem NFS lub SMB, aby skaner sieciowy mógł do niego zapisywać, pliki nie będą wykrywane. Rozwiązaniem jest ustawienie interwału na liczbę dodatnią (w sekundach), aby paperless skanował folder w określonych odstępach czasu.
Języki OCR i ich koszt
PAPERLESS_OCR_LANGUAGE przyjmuje trzyliterowy kod Tesseract, domyślnie eng. Języki można łączyć znakiem plus, jak w przykładzie deu+eng. Tesseract sprawdza każdy z nich i wybiera najlepszy wynik, więc każdy dodatkowy język zwiększa czas użycia procesora dla każdej strony. Na współdzielonym VPS z vCPU różnica może wynosić od dziesięciu sekund do minuty na skanowanie. Należy uwzględniać tylko te języki, w których faktycznie napisane są dokumenty.
Obraz zawiera języki angielski, niemiecki, włoski, hiszpański i francuski. W przypadku innych języków należy dodać je do PAPERLESS_OCR_LANGUAGES jako listę rozdzieloną spacjami, na przykład PAPERLESS_OCR_LANGUAGES=tur ces, a następnie zrestartować kontener. Kontener pobiera pakiety danych Tesseract podczas uruchamiania, więc pierwsze uruchomienie po takiej zmianie będzie trwać dłużej.
Tworzenie kopii zapasowej bazy danych i plików multimedialnych
Kopiowanie wolumenów Docker podczas pracy PostgreSQL może skutkować utworzeniem kopii, której nie da się przywrócić. Paperless dostarcza własne narzędzie eksportujące, które zapisuje dokumenty wraz z manifestem JSON zawierającym wszystkie metadane w punkcie montowania ./export:
docker compose exec webserver document_exporter ../export --delete --no-progress-barFlaga --delete usuwa wyeksportowane pliki, które nie odpowiadają już bieżącym dokumentom, dzięki czemu folder pozostaje odzwierciedleniem stanu bazy, zamiast stale rosnąć. Flaga --no-progress-bar zapewnia czysty wynik działania polecenia, gdy jest ono uruchamiane z poziomu cron.
Przywracanie odbywa się za pomocą document_importer w odniesieniu do tego samego folderu na nowej instancji, co oznacza, że katalog eksportu jest jedynym elementem wymagającym zabezpieczenia. Należy przesyłać go poza serwer zgodnie z harmonogramem, korzystając z szyfrowanych i deduplikowanych kopii zapasowych restic z VPS, pamiętając o wcześniejszym uruchomieniu eksportu, aby restic nie przechwycił niekompletnego archiwum.
Zweryfikuj kopię zapasową, sprawdzając, czy istnieje export/manifest.json oraz czy liczba plików odpowiada liczbie dokumentów wyświetlanej w interfejsie. Kopia zapasowa, której nigdy nie sprawdzono, nie jest kopią zapasową. Jeszcze gorzej, gdy nocny eksport po cichu zaczyna kończyć się błędem. Dlatego skonfiguruj zadanie cron tak, aby wysyłało swój kod zakończenia do własnego serwera ntfy. Dzięki temu dowiesz się o awarii w tygodniu, w którym wystąpiła, a nie dopiero w dniu, gdy trzeba będzie przywrócić dane.
FAQ
Dlaczego każda strona zwraca "Bad Request (400)" po skierowaniu na nią domeny?
Django odrzuciło nagłówek Host, ponieważ domena nie znajduje się w ALLOWED_HOSTS. Ustaw PAPERLESS_URL=https://paperless.example.com w pliku docker-compose.env, bez końcowego ukośnika, a następnie wykonaj docker compose up -d, aby odtworzyć kontener. Edycja samego pliku środowiskowego nie przynosi efektu, ponieważ uruchomiony kontener zachowuje zmienne środowiskowe, z którymi został wystartowany.
Umieściłem plik PDF w folderze consume i nic się nie stało. Co jest nie tak?
Sprawdź najpierw docker compose logs webserver. Błąd uprawnień oznacza, że USERMAP_UID oraz USERMAP_GID nie są zgodne z kontem, do którego należy plik; napraw te wartości i odtwórz kontener. Brak jakiegokolwiek wpisu w dzienniku oznacza, że zdarzenie plikowe nie dotarło do aplikacji, co zdarza się w przypadku udziałów sieciowych, ponieważ powiadomienia jądra nie są przez nie przekazywane. Ustaw PAPERLESS_CONSUMER_POLLING_INTERVAL na wartość typu 30, a paperless będzie skanować folder co 30 sekund.
Czy mogę uruchomić paperless-ngx z SQLite zamiast PostgreSQL?
Tak, docker-compose.sqlite.yml jest wspierane i zużywa mniej pamięci, co sprawdza się na małych serwerach VPS. Wadą jest spadek wydajności wraz z rozrostem archiwum: wyszukiwanie pełnotekstowe i masowa edycja tagów wyraźnie zwalniają przy tysiącach dokumentów. Migracja w późniejszym terminie wymaga eksportu i importu danych, więc wybierz PostgreSQL już teraz, jeśli przewidujesz dalszy rozwój archiwum.
Ile miejsca na dysku faktycznie potrzebuje archiwum skanów?
Mniej więcej dwukrotność rozmiaru plików źródłowych. Paperless przechowuje oryginał w niezmienionej formie i zapisuje drugi plik PDF po procesie OCR z warstwą tekstową umożliwiającą wyszukiwanie, a także niewielkie miniatury. Skan tekstowy o rozmiarze 200 KB pozostaje mały. Kolorowy skan długiej umowy o rozmiarze 30 MB zajmuje około 60 MB. Jeśli przechowujesz katalog eksportu na tym samym dysku, to samo archiwum zajmuje na nim miejsce trzykrotnie.
Czy potrzebuję kontenerów Tika oraz Gotenberg?
Tylko jeśli chcesz, aby pliki Word, Excel lub OpenDocument były indeksowane wraz z plikami PDF. Kontenery te konwertują wspomniane formaty do PDF, aby paperless mógł przeprowadzić OCR i umożliwić wyszukiwanie. Dodają one również dwa kolejne uruchomione kontenery i zajmują kilkaset megabajtów pamięci, więc na małym serwerze można je pominąć, jeśli wszystkie archiwizowane pliki to już PDF-y lub obrazy.