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 zmienia folder zeskanowanych dokumentów w przeszukiwalne archiwum. Po umieszczeniu pliku PDF w monitorowanym katalogu, serwer wykonuje na nim operację 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 awarii instalacji. System ten nie jest biblioteką zdjęć: OCR i mechanizmy rozpoznawania nadawców nie sprawdzają się w przypadku folderu zdjęć z wakacji, dlatego należy je umieścić w serwerze zdjęć stworzonym do tego celu, a Paperless zachować dla dokumentów papierowych. Podobna zasada dotyczy wideo: kolekcja zgranych filmów powinna znaleźć się na serwerze multimediów, gdzie rozwiązanie takie jak interfejs Jellyfin wystylizowany na wypożyczalnię z lat 90. sprawia, że przeglądanie staje się głównym celem, zamiast wyszukiwania.
Paperless-ngx to utrzymywany przez społeczność fork oryginalnego projektu Paperless. Jest to oprogramowanie darmowe, typu self-hosted, które przechowuje dokumenty jako zwykłe pliki na dysku, dzięki czemu użytkownik nigdy nie traci dostępu do własnego archiwum. Uruchomienie go na serwerze VPS zamiast na domowym sprzęcie oznacza, że skany są dostępne z dowolnego miejsca bez konieczności otwierania portów na domowym routerze. Rozwiązanie to dobrze współgra z prywatną instancją Nextcloud dla plików, które nie są dokumentami papierowymi. Ta sama logika dotyczy komputera, do którego podłączony jest skaner, ponieważ własny przekaźnik RustDesk na tym samym VPS pozwala na zdalne sterowanie tą maszyną bez konieczności tworzenia dziur w zaporze routera.
Co faktycznie uruchamia stos
Oficjalny plik compose uruchamia cztery kontenery, a zrozumienie roli każdego z nich pozwala na czytanie logów.
webserver: sam obraz paperless-ngx. Uruchamia on interfejs webowy, API, konsumenta monitorującego folder wejściowy oraz procesy robocze Celery, które wykonują OCR.db: PostgreSQL. Przechowuje metadane, tagi, korespondentów oraz tabele indeksu wyszukiwania pełnotekstowego. Nie przechowuje plików PDF.broker: Valkey, magazyn klucz-wartość zgodny z Redis. Pełni rolę kolejki zadań między procesem webowym 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 październik 2026 plik compose dla postgres przypina 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, dostęp z uprawnieniami sudo oraz zainstalowany Docker z wtyczką Compose. Jeśli te zagadnienia są nowe, należy rozpocząć od podstaw Docker Compose dla VPS, a następnie powrócić do tego przewodnika.
- Nazwa domeny z rekordem A wskazującym na adres IP serwera VPS. Paperless nie zaakceptuje logowania przez HTTPS na nazwie hosta, która nie została zdefiniowana w konfiguracji, dlatego kwestia ta jest istotna na wczesnym etapie.
- Pamięć RAM jest kluczowym ograniczeniem. PostgreSQL, Valkey, serwer WWW Granian oraz proces roboczy Tesseract OCR wymagają łącznie około 2 GB pamięci RAM przy lekkim obciążeniu. W przypadku planowanego importu dużej liczby zeskanowanych dokumentów zaleca się 4 GB pamięci RAM, ponieważ proces OCR dla dużych plików PDF o wielu stronach powoduje skokowe zużycie pamięci, co może doprowadzić do przerwania procesu przez mechanizm OOM (out-of-memory) jądra systemu.
- Dysk: archiwum jest przechowywane w dwóch kopiach – pliku oryginalnego oraz przetworzonego pliku PDF z warstwą tekstową OCR. 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 ona do podpisywania tokenów sesji, a bieżące wydania traktują ten symbol zastępczy jako nieustawiony: paperless odmawia uruchomienia, gdy klucz jest brakujący, pusty lub nadal ma wartość change-me, więc stos nie zostanie uruchomiony, dopóki nie zostanie on zastąpiony. 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 sprawdza zarówno nagłówek Host, jak i źródło (origin) zgłaszane przez przeglądarkę w formularzu logowania. Ustaw PAPERLESS_URL, a adres zostanie automatycznie dodany do ALLOWED_HOSTS, CORS_ALLOWED_HOSTS oraz CSRF_TRUSTED_ORIGINS. Jeśli pozostawisz to pole puste i skierujesz domenę na serwer, strona logowania nadal się załaduje, ponieważ PAPERLESS_ALLOWED_HOSTS domyślnie przyjmuje wartość *, jednak przesyłanie danych przez HTTPS zakończy się błędem CSRF verification failed. Request aborted., ponieważ domena nie znajduje się na liście zaufanych źródeł. 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 własnego konta, sprawdzając je 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 konsumującego, a w dzienniku zamiast importu pojawi się błąd uprawnień.
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 jest domyślnych danych logowania. Pominięcie tego kroku spowoduje, że interfejs WWW poprosi pierwszą osobę, która go otworzy, o utworzenie konta superużytkownika. Ponieważ port 8000 jest w tym momencie otwarty na świat, należy utworzyć konto bezpośrednio z poziomu powłoki. 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ż migracje bazy danych, co trwa zazwyczaj od jednej do dwóch minut.
Przed przypisaniem 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 usługą
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 zna adres. Należy zmienić linię portu, aby ograniczyć powiązanie wyłącznie do interfejsu loopback:
ports:
- "127.0.0.1:8000:8000"Następnie należy przeprowadzić terminację TLS (transport layer security) na reverse proxy i przekierować ruch do 127.0.0.1:8000. Jeśli jest to jedyna aplikacja na serwerze, wystarczy dowolne proxy z klientem ACME (automatic certificate management environment). W przypadku uruchamiania kilku kontenerów za jednym certyfikatem, 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, konfiguracje HTTPS najczęściej zawodzą podczas weryfikacji pochodzenia (origin check) formularza logowania. Paperless ignoruje X-Forwarded-Proto, dopóki nie zostanie ustawione PAPERLESS_PROXY_SSL_HEADER, które domyślnie jest wyłączone. W rezultacie Django traktuje każde żądanie z proxy jako zwykłe HTTP, a pochodzenie https:// wysyłane przez przeglądarkę jest akceptowane tylko wtedy, gdy znajduje się na liście zaufanych. Ustawienie PAPERLESS_URL na dokładny adres https:// wpisywany w przeglądarce dodaje go do tej listy. Bez tego kroku wystąpi błąd CSRF verification failed. Request aborted. na stronie, która wizualnie wygląda poprawnie.
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ż 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, uruchomić 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 przy długim dokumencie.
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 w 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 istotna kwestia. Domyślnie PAPERLESS_CONSUMER_POLLING_INTERVAL ma wartość 0, co oznacza, że paperless korzysta z natywnych powiadomień systemu plików, które są wyzwalane natychmiast. Powiadomienia te nie są niezawodne w przypadku sieciowych systemów plików. Jeśli folder consume znajduje się na udziale NFS lub SMB, aby skaner sieciowy mógł do niego zapisywać, pliki mogą pozostawać niewykryte. Rozwiązaniem jest ustawienie interwału na dodatnią liczbę sekund, aby paperless zamiast tego skanował folder.
Języki OCR i ich wpływ na wydajność
PAPERLESS_OCR_LANGUAGE przyjmuje trzyliterowy kod Tesseract, domyślnie eng. Języki można łączyć znakiem plus, jak w przykładzie deu+eng. Tesseract przetwarza wtedy każdy wymieniony język na każdej stronie, co oznacza, że każdy dodatkowy język zwiększa obciążenie procesora podczas skanowania. Na serwerze VPS z współdzielonymi vCPU różnica może wynosić od dziesięciu sekund do minuty czasu pracy. Należy uwzględniać wyłącznie języki, w których faktycznie sporządzono dokumenty.
Obraz zawiera pakiety dla języka angielskiego, niemieckiego, włoskiego, hiszpańskiego i francuskiego. 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ć usługę. Kontener pobiera pakiety danych Tesseract podczas uruchamiania, dlatego pierwsze uruchomienie po wprowadzeniu tej zmiany będzie trwało dłużej.
Tworzenie kopii zapasowej bazy danych i plików multimedialnych
Kopiowanie wolumenów Docker podczas pracy PostgreSQL może skutkować niepoprawną kopią zapasową. Paperless posiada własne narzędzie eksportujące, które zapisuje dokumenty oraz manifest JSON ze wszystkimi metadanymi w punkcie montowania ./export:
docker compose exec -T webserver document_exporter ../export --delete --no-progress-bar--delete usuwa wyeksportowane pliki, które nie odpowiadają już aktualnym dokumentom, dzięki czemu folder pozostaje odzwierciedleniem stanu bazy, zamiast nieograniczenie zwiększać swój rozmiar. --no-progress-bar zapewnia przejrzystość danych wyjściowych podczas uruchamiania zadania przez cron, a -T zapobiega próbom uzyskania dostępu do terminala przez Compose, co w środowisku cron jest niemożliwe.
Przywracanie danych odbywa się za pomocą document_importer z tego samego folderu na nowej instancji, co oznacza, że katalog eksportu jest jedynym elementem wymagającym zabezpieczenia. Należy go regularnie przesyłać poza serwer za pomocą szyfrowanych, deduplikowanych kopii zapasowych restic z VPS, uruchamiając eksport przed wykonaniem kopii, aby restic nie przechwycił niekompletnego archiwum.
Poprawność kopii zapasowej należy weryfikować poprzez sprawdzenie istnienia export/manifest.json oraz zgodności liczby plików z liczbą dokumentów widoczną w interfejsie. Kopia zapasowa, której nigdy nie sprawdzono, nie jest kopią zapasową. Nocny eksport, który po cichu przestaje działać, jest jeszcze gorszy, dlatego warto skonfigurować zadanie cron tak, aby przesyłało kod wyjścia do własnego serwera ntfy, co pozwoli wykryć awarię w momencie jej wystąpienia, a nie w dniu, w którym konieczne będzie przywrócenie danych.
FAQ
Dlaczego każda strona zwraca "Bad Request (400)" po skierowaniu na nią domeny?
Przy standardowych plikach nie powinno do tego dochodzić, ponieważ PAPERLESS_ALLOWED_HOSTS domyślnie przyjmuje wartość *. Wpis 400 z DisallowedHost w logu kontenera oznacza, że PAPERLESS_ALLOWED_HOSTS ustawiono na listę, która nie zawiera Twojej domeny. Ustaw PAPERLESS_URL=https://paperless.example.com w docker-compose.env bez końcowego ukośnika, a paperless doda tę nazwę hosta do listy, albo usuń linię PAPERLESS_ALLOWED_HOSTS. Następnie wykonaj docker compose up -d, aby odtworzyć kontener. Sama edycja pliku env nie przynosi efektu, ponieważ uruchomiony kontener zachowuje środowisko, z którym wystartował.
Wrzuciłem plik PDF do folderu consume i nic się nie stało. Co jest nie tak?
Najpierw sprawdź 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, więc popraw je i odtwórz kontener. Brak jakiegokolwiek wpisu w logu oznacza, że zdarzenie plikowe nigdy nie dotarło, co zdarza się na udziałach sieciowych, ponieważ natywne powiadomienia systemu plików nie są tam niezawodne. 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 VPS. Kompromis staje się widoczny wraz ze wzrostem archiwum: wyszukiwanie pełnotekstowe i masowa edycja tagów wyraźnie zwalniają przy tysiącach dokumentów. Późniejsza migracja wymaga eksportu i importu, 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 pozostawia oryginał w stanie nienaruszonym i przechowuje drugi plik PDF po OCR z warstwą tekstową umożliwiającą wyszukiwanie, a także małe miniatury. Skan tekstowy o rozmiarze 200 KB pozostaje mały. Kolorowy skan długiej umowy o rozmiarze 30 MB zajmuje około 60 MB. Dodaj do tego katalog eksportu, jeśli przechowujesz go na tym samym dysku, a to samo archiwum znajduje się na dysku trzykrotnie.
Czy potrzebuję kontenerów Tika oraz Gotenberg?
Tylko jeśli chcesz, aby pliki Word, Excel lub OpenDocument były indeksowane obok Twoich plików PDF. Konwertują one te formaty do PDF, aby paperless mógł wykonać OCR i przeszukać ich treść. Dodają one również dwa kolejne uruchomione kontenery i kilkaset megabajtów pamięci, więc pomiń je na małej maszynie, jeśli wszystko, co archiwizujesz, jest już plikiem PDF lub obrazem.