VPSにPaperless-ngxを構築して文書を自宅管理
Docker ComposeでPaperless-ngxをVPSに構築します。公式PostgreSQL構成、PAPERLESS_URL、consumeフォルダー、OCR言語、HTTPS、バックアップの設定を具体的に解説します。
構築するもの
VPS上のPaperless-ngxを使用すると、スキャンした紙文書のフォルダーを検索可能なアーカイブにできます。監視対象ディレクトリにPDFを置くと、サーバーがOCR(光学文字認識)を実行し、テキストを抽出して、日付と通信相手を推測し、文書を整理します。インストールは4つのサービスを含む1つのDocker Composeファイルで行います。その後は設定が中心です。インストールで問題が発生するのは主にこの設定部分であるため、このガイドでは大部分を設定に割きます。
Paperless-ngxは、元のPaperlessプロジェクトから派生した、コミュニティによって保守されているフォークです。無料で利用できるセルフホスト型ソフトウェアで、文書をディスク上の通常のファイルとして保存します。そのため、自分のアーカイブから締め出されることはありません。自宅の機器ではなくVPSで実行すれば、自宅ルーターのポートを開放せずに、どこからでもスキャンを利用できます。また、紙ではないファイル用のプライベートなNextcloudインスタンスとも相性がよい構成です。
スタックで実際に実行されるもの
公式の compose ファイルは4つのコンテナを起動します。それぞれの役割を把握すると、ログを読みやすくなります。
webserver: paperless-ngx のイメージそのものです。Web インターフェース、API、入力フォルダーを監視する consumer、OCR を実行する Celery タスクワーカーを動かします。db: PostgreSQL です。メタデータ、タグ、対応者、全文検索インデックスのテーブルを保持します。PDF は保持しません。broker: Redis 互換のキーバリューストアである Valkey です。Web プロセスとワーカーの間のタスクキューとして機能します。gotenbergとtika:-tikaの compose バリアントでのみ使用するオプションのコンテナです。Office ドキュメント(.docx、.xlsx、.odt)を PDF に変換し、paperless がインデックスを作成できるようにします。
2026年7月時点では、postgres compose ファイルで docker.io/library/postgres:18 と docker.io/valkey/valkey:9-alpine が固定され、アプリケーションは ghcr.io/paperless-ngx/paperless-ngx:latest から取得されます。
前提条件
- sudo access があり、Docker と Compose plugin がすでにインストールされている Ubuntu 24.04 KVM VPS。ここが初めての場合は、まず VPS 向け Docker Compose の基礎を確認してから戻ってください。
- VPS を指す A record が設定された domain name。Paperless は、設定されていない hostname での提供を拒否するため、この設定は想定より早い段階で必要になります。
- 実際の制約は memory です。軽い用途であれば、PostgreSQL、Valkey、gunicorn、Tesseract OCR worker が同時に常駐しても 2 GB に収まります。数百件の scan の backlog を取り込む予定がある場合は 4 GB を割り当ててください。大きな複数ページの PDF に対する OCR が memory 使用量の急増を引き起こし、kernel の out-of-memory killer によって worker が終了するためです。
- Disk: archive は original file と OCR 済みの archive PDF の2つとして保存されるため、scan のサイズの約2倍を確保してください。
公式の compose ファイルを取得する
対話式のインストーラーがあります。
bash -c "$(curl --location --silent --show-error https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/install-paperless-ngx.sh)"質問に回答すると、ファイルが書き込まれます。手動で行う場合は4つのコマンドで完了します。また、すべてのファイルの場所を把握できます。これは、今後管理するサーバーでは重要です。
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 という1行が含まれています。この名前は、すべてのコンテナとボリュームのプレフィックスになります。そのため、このファイルを削除してから docker compose down -v がデータを見つけられない理由を調べることにならないようにしてください。
初回起動前に docker-compose.env を設定する
2つの設定は必須です。プロジェクトのドキュメントに記載されたコマンドでシークレットキーを生成します。
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フォルダーにコピーしたファイルをconsumerが読み取れず、インポートではなく権限エラーがログに記録されます。
スタックを起動して最初のユーザーを作成する
docker compose pull
docker compose up -d
docker compose run --rm webserver createsuperuser
docker compose logs -f webservercreatesuperuser はユーザー名、メールアドレス、パスワードを要求します。デフォルトのログイン情報はありません。この手順を省略すると、何も受け付けないサインインページが表示されます。ブラウザーで接続する前に、サーバーがポート 8000 でリッスンしていることを示すログ行が出力されるまで待ちます。初回起動時にはデータベースのマイグレーションも実行されるため、1〜2分かかります。
ドメインを使用する前に、ローカルで確認します。
curl -I http://127.0.0.1:8000302 が /accounts/login/ にリダイレクトされれば、スタックは正常に動作しています。
HTTPSを前段に配置する
標準の compose ファイルは 8000:8000 を公開し、すべてのインターフェースにバインドします。パブリック VPS では、アドレスを知っている人なら誰でも、ドキュメントアーカイブ全体を暗号化されていない HTTP で取得できます。ポート行を変更して、loopback のみにバインドします。
ports:
- "127.0.0.1:8000:8000"次に、reverse proxy で TLS(transport layer security)を終端し、127.0.0.1:8000 に転送します。このサーバー上でこのアプリケーションだけを実行する場合は、ACME(automatic certificate management environment)クライアントを備えた任意の proxy を使用できます。1つの証明書設定で複数のコンテナを運用する場合は、複数の Docker Compose アプリケーション向け Traefik reverse proxy パターンに従い、公開ポートを設定せずに webserver サービスを proxy network に接続します。
使用する proxy に関係なく、X-Forwarded-Proto: https を送信する必要があります。これがないと Django はリクエストが HTTP 経由で到着したと判断します。その結果、login form の origin check に失敗し、正しく表示されるページ上に CSRF verification failed. Request aborted. が表示されます。この修正のもう一方の要件は、ブラウザーで入力する正確な https:// アドレスを PAPERLESS_URL に設定することです。
proxy の upload size limit も引き上げてください。proxy が body のサイズを 1 MB に制限している場合、40 MB の scan は paperless に到達する前に拒否され、ブラウザーには一般的な upload failure が表示されます。
consumeディレクトリの動作
composeファイルは、./consumeをcomposeディレクトリからコンテナにbind mountします。そこに置いたファイルはすべてインポートされた後、フォルダから削除されます。ファイルはpaperlessの管理下にあるmediaボリュームに保存されるためです。
cp ~/scan-2026-07-14.pdf ~/paperless/consume/
docker compose logs -f webserverconsumerがファイル名を取得し、OCRを実行し、ドキュメントが追加されたことを示す行を出力して終了することを確認できます。1ページのスキャンなら処理全体は数秒です。長いドキュメントでは1分以上かかる場合があります。
ファイルの検索方法を変える設定が2つあります。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共有で、ネットワークスキャナーがそこに書き込む場合、ファイルは検出されません。対処するには、間隔を正の秒数に設定し、paperlessがフォルダをスキャンするようにします。
OCR言語と処理コスト
PAPERLESS_OCR_LANGUAGE には、デフォルトで eng という3文字のTesseractコードを指定します。言語は、deu+eng のようにプラス記号で結合します。Tesseractは各言語を試して最適な結果を保持するため、言語を1つ追加するたびに、各ページの処理に必要なCPU時間が増加します。共有vCPUのVPSでは、スキャンが10秒で完了するか、1分かかるかの違いになります。文書で実際に使用している言語だけを指定してください。
このイメージには、英語、ドイツ語、イタリア語、スペイン語、フランス語が含まれています。それ以外の言語を使用する場合は、PAPERLESS_OCR_LANGUAGES にスペース区切りのリストとして追加します。例: PAPERLESS_OCR_LANGUAGES=tur ces。その後、再起動してください。コンテナは起動時にTesseractのデータパックをダウンロードするため、この変更後の初回起動には通常より時間がかかります。
データベースとメディアをバックアップする
PostgreSQLの実行中にDockerボリュームをコピーすると、復元できないバックアップになる可能性があります。Paperlessには専用のエクスポーターがあり、すべてのメタデータを含むドキュメントとJSONマニフェストを./export bind mountに書き込みます。
docker compose exec webserver document_exporter ../export --delete --no-progress-bar--deleteは、現在のドキュメントに一致しなくなったエクスポート済みファイルを削除します。これにより、フォルダーは増え続けず、ミラーとして維持されます。--no-progress-barは、cronから実行したときの出力をクリーンに保ちます。
復元は、新しいstackで同じフォルダーに対してdocument_importer実行します。つまり、安全に保管する必要があるのはエクスポートディレクトリだけです。VPSから暗号化および重複排除されたresticバックアップをスケジュールに従ってオフサイトへ送信します。resticが書き込み途中のアーカイブを取得しないように、先にエクスポートを実行してください。
export/manifest.jsonが存在し、ファイル数がインターフェースに表示されるドキュメント数と一致することを確認して、バックアップを検証します。一度も一覧表示していないバックアップは、バックアップとはいえません。
FAQ
ドメインを指定した後、すべてのページで「Bad Request (400)」が返るのはなぜですか?
Django は、ドメインが ALLOWED_HOSTS に含まれていないため Host ヘッダーを拒否しました。末尾のスラッシュを付けずに docker-compose.env で PAPERLESS_URL=https://paperless.example.com を設定し、その後 docker compose up -d を実行してコンテナを再作成してください。環境変数ファイルを編集するだけでは反映されません。実行中のコンテナは、起動時に設定された環境を保持するためです。
consume フォルダーに PDF を置いても何も起きません。何が問題ですか?
まず docker compose logs webserver を確認してください。権限エラーは、USERMAP_UID と USERMAP_GID がファイルの所有アカウントと一致していないことを示します。値を修正して、コンテナを再作成してください。ログ行がまったくない場合、ファイルイベントが届いていません。カーネル通知はネットワーク共有を越えないため、この問題が発生します。PAPERLESS_CONSUMER_POLLING_INTERVAL を 30 のような値に設定すると、paperless は代わりに 30 秒ごとにフォルダーをスキャンします。
PostgreSQL の代わりに SQLite で paperless-ngx を実行できますか?
はい。docker-compose.sqlite.yml はサポートされており、メモリ使用量も少ないため、小規模な VPS に適しています。ただし、アーカイブが増えると、全文検索と大量のタグ編集が数千件の文書で明らかに遅くなります。後から移行するにはエクスポートとインポートが必要です。そのため、アーカイブが増え続ける見込みなら、最初から PostgreSQL を選択してください。
スキャンのアーカイブには実際にどのくらいのディスク容量が必要ですか?
おおよそ元ファイルの 2 倍です。paperless は元ファイルを変更せずに保持し、検索可能なテキストレイヤーを含む OCR 済みの PDF をもう 1 つ保存します。小さなサムネイルも保存します。テキストだけの 200 KB のスキャンなら容量は小さいままです。長い契約書をカラーでスキャンした 30 MB のファイルでは、約 60 MB が保存されます。同じディスクにエクスポートディレクトリも保持する場合は、その容量を追加してください。アーカイブ全体ではディスク上で 3 倍になります。
Tika と Gotenberg のコンテナは必要ですか?
PDF と一緒に Word、Excel、または OpenDocument ファイルもインデックス化する場合だけ必要です。これらの形式を PDF に変換するため、paperless で OCR と検索が可能になります。ただし、実行中のコンテナが 2 つ増え、メモリも数百 MB 使用します。保存するファイルがすべて PDF または画像なら、小規模なサーバーでは省略してください。