SSD Nodes Learn Hosting plans →
ガイド Matt Connor著者 Matt Connor ・更新日 2026-10-13

VPSにPaperless-ngxを構築する方法

Docker Composeで公式Postgres構成を使い、PAPERLESS_URL、consumeフォルダー、OCR言語、HTTPS、バックアップまで設定する手順を解説します。

構築するもの

VPS 上の Paperless-ngx は、スキャンした書類のフォルダーを検索可能なアーカイブに変換します。監視対象ディレクトリに PDF を置くと、サーバーが OCR(光学文字認識)を実行し、テキストを抽出して日付と通信相手を推測し、整理して保存します。インストールは 4 つのサービスを定義した 1 つの Docker Compose ファイルで完了します。その後に必要なのは設定です。このガイドの大半を設定に割いているのは、インストールで問題が発生するのが主にこの部分だからです。Paperless-ngx は写真ライブラリではありません。OCR と通信相手の推測は、休暇中に撮影した JPEG のフォルダーには役立たないため、それらは写真向けに構築されたフォトサーバーに保存し、Paperless-ngx は書類用に使ってください。動画も同様です。リッピングした映画のコレクションはメディアサーバーに置くべきです。そこでは、たとえば90 年代のレンタル店風に仕立てた Jellyfin フロントエンドによって、検索よりも閲覧を中心にできます。

Paperless-ngx は、元の Paperless プロジェクトから派生した、コミュニティによって保守されているフォークです。無料で利用でき、自分でホスティングでき、ドキュメントをディスク上の通常のファイルとして保存するため、自分のアーカイブから締め出されることはありません。自宅のマシンではなく VPS で実行すると、自宅ルーターでポートを開放せずに、どこからでもスキャンにアクセスできます。また、書類以外のファイル用のプライベートな Nextcloud インスタンスとも相性がよい構成です。同じ考え方は、スキャナーを接続したデスクトップにも当てはまります。その VPS 上で自分専用の RustDesk リレーを実行すれば、ルーターに穴を開けずに、別の場所からそのマシンを操作できます。

実際にスタックで実行されるもの

公式の compose ファイルは 4 つのコンテナを起動します。それぞれの役割を把握すると、ログを読みやすくなります。

  • webserver: paperless-ngx のイメージそのものです。Web インターフェース、API、入力フォルダーを監視する consumer、OCR を実行する Celery タスクワーカーを起動します。
  • db: PostgreSQL です。メタデータ、タグ、correspondent、全文検索用のインデックステーブルを保持します。PDF は保持しません。
  • broker: Redis 互換のキーバリューストアである Valkey です。Web プロセスとワーカーの間のタスクキューとして機能します。
  • gotenberg と tika: -tika の compose バリアントでのみ使用するオプションのコンテナです。Office ドキュメント(.docx、.xlsx、.odt)を PDF に変換し、paperless でインデックスを作成できるようにします。

2026 年 10 月時点では、postgres compose ファイルで docker.io/library/postgres:18 と docker.io/valkey/valkey:9-alpine が固定され、アプリは ghcr.io/paperless-ngx/paperless-ngx:latest から取得されます。

前提条件

  • sudo アクセスが可能な Ubuntu 24.04 KVM VPS と、Compose plugin がインストール済みの Docker。ここが初めての場合は、まず VPS 向け Docker Compose の基礎を確認してから戻ってきてください。
  • VPS を指す A レコードが設定されたドメイン名。Paperless は、登録されていないホスト名への HTTPS サインインを受け付けません。そのため、想定より早い段階でこの設定が必要になります。
  • 実際の制約はメモリです。PostgreSQL、Valkey、Granian web server、Tesseract OCR worker を同時に常駐させても、軽い用途なら 2 GB に収まります。数百件のスキャンをまとめてインポートする場合は 4 GB を割り当ててください。大きな複数ページ PDF の OCR がメモリ使用量の急増を引き起こし、worker が kernel の out-of-memory killer に終了させられるためです。
  • ディスク領域: アーカイブは元のファイルと OCR 済みのアーカイブ PDF の 2 つとして保存されます。そのため、スキャンデータのサイズのおよそ 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つの設定は必須です。プロジェクトのドキュメントに記載されたコマンドでSecret keyを生成します。

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=1000

PAPERLESS_SECRET_KEYは、リテラル値change-meの状態で提供されます。これはセッションTokenへの署名に使用されます。現在のリリースでは、このプレースホルダーは未設定として扱われます。Keyが未設定、空、またはchange-meのままだとpaperlessは起動を拒否します。そのため、置き換えるまでstackは起動しません。初回起動前に設定してください。後から変更すると、すべてのユーザーがログアウトされます。

PAPERLESS_URLは、1時間の切り分け時間を節約できる設定です。PaperlessはDjangoアプリケーションです。DjangoはHostヘッダーと、サインインフォームではブラウザーが報告するOriginの両方を確認します。PAPERLESS_URLを設定すると、アドレスがALLOWED_HOSTS、CORS_ALLOWED_HOSTS、CSRF_TRUSTED_ORIGINSに自動的に追加されます。空のままにしてホストをドメインに向けても、サインインページ自体は読み込まれます。これは、PAPERLESS_ALLOWED_HOSTSのデフォルト値が*だからです。ただし、HTTPS経由で送信するとCSRF verification failed. Request aborted.で失敗します。ドメインが信頼済みのOriginではないためです。末尾のスラッシュやパスを付けずに記述してください。

USERMAP_UIDとUSERMAP_GIDは、containerを実行するユーザーを設定します。id -uとid -gで確認した自分のアカウントに合わせてください。一致しない場合、consume folderにコピーしたファイルをconsumerが読み取れません。ログにはimportではなく、permission errorが記録されます。

スタックを起動して最初のユーザーを作成する

docker compose pull
docker compose up -d
docker compose run --rm webserver createsuperuser
docker compose logs -f webserver

createsuperuserでは、ユーザー名、メールアドレス、パスワードの入力を求められます。デフォルトのログインはありません。この手順を省略すると、Web インターフェースを最初に開いたユーザーにスーパーユーザーの作成を求められます。port 8000 がインターネットに公開されたままだと、そのユーザーが自分になるとは限りません。そのため、すぐに shell からアカウントを作成してください。ブラウザーで接続する前に、server が port 8000 で待ち受けていることを示すログ行が出るまで待ちます。最初の起動では database migration も実行されるため、1〜2 分かかります。

domain を設定する前に、ローカルで確認します。

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"

次に、reverse proxy で TLS(transport layer security)を終端し、127.0.0.1:8000 へ転送します。このサーバーで稼働させるアプリがこれだけであれば、ACME(automatic certificate management environment)クライアントを備えた任意の proxy を使用できます。1 つの証明書設定の背後で複数のコンテナを実行する場合は、複数の Docker Compose アプリで Traefik reverse proxy を使う構成に従い、webserver service を公開ポートなしで proxy network に接続します。

使用する proxy にかかわらず、HTTPS 構成で問題が起きるのはログインフォームの origin チェックです。Paperless は、PAPERLESS_PROXY_SSL_HEADER を設定しない限り X-Forwarded-Proto を無視します。これはデフォルトで無効なため、Django は proxy 経由のすべてのリクエストを平文の HTTP として扱います。また、ブラウザーが送信する https:// origin は、信頼済みとして登録されている場合にのみ通過します。ブラウザーで入力する正確な https:// アドレスを PAPERLESS_URL に設定すると、そのアドレスが信頼済みリストに追加されます。これを設定しないと、正しく表示されるページで CSRF verification failed. Request aborted. が発生します。

proxy の upload サイズ上限も引き上げます。本文サイズを 1 MB に制限する proxy 経由で 40 MB のスキャンを送信すると、paperless が受け取る前に拒否され、ブラウザーには一般的な upload 失敗が表示されます。

consume ディレクトリの動作

compose ファイルは、compose ディレクトリにある ./consume をコンテナ内へ bind mount します。ここに置いたファイルは取り込まれた後、フォルダーから削除されます。ファイルは paperless の管理下にある media ボリュームへ移動するためです。

cp ~/scan-2026-07-14.pdf ~/paperless/consume/
docker compose logs -f webserver

consumer がファイル名を検出し、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 volume をコピーすると、復元できない可能性があるバックアップになります。Paperless には専用のエクスポーターがあり、ドキュメントとすべてのメタデータを含む JSON マニフェストを ./export bind mount に書き出します。

docker compose exec -T webserver document_exporter ../export --delete --no-progress-bar

--delete は、現在のドキュメントに対応しなくなったエクスポート済みファイルを削除します。そのため、フォルダーは増え続けず、常にミラーとして保たれます。--no-progress-bar により、cron から実行したときの出力を整理できます。-T により、cron に存在しない端末を Compose が要求しなくなります。

新しい stack で同じフォルダーから復元するには document_importer を実行します。つまり、安全に保管する必要があるのはエクスポートディレクトリだけです。VPS から暗号化と重複排除に対応した restic バックアップをスケジュール実行してオフサイトに送信し、restic が書き込み途中のアーカイブを取得しないよう、先にエクスポートを実行します。

バックアップを検証するには、export/manifest.json が存在することと、ファイル数がインターフェースに表示されるドキュメント数と一致することを確認します。一度も一覧を取得していないバックアップは、バックアップとはいえません。夜間のエクスポートが気付かないうちに失敗し始めると、さらに危険です。そのため、cron ジョブから終了ステータスを自分の ntfy サーバーへ送信します。そうすれば、復元が必要になった日ではなく、失敗した週に把握できます。

FAQ

ドメインを割り当てた後、すべてのページで「Bad Request (400)」が返るのはなぜですか?

標準のファイルを使っている場合は、通常この問題は発生しません。PAPERLESS_ALLOWED_HOSTS のデフォルト値は * だからです。コンテナログに DisallowedHost を伴う 400 が表示される場合、PAPERLESS_ALLOWED_HOSTS に、ドメインを含まないリストが設定されています。末尾にスラッシュを付けずに PAPERLESS_URL=https://paperless.example.com を docker-compose.env に設定すると、paperless はそのホスト名をリストに追加します。または、PAPERLESS_ALLOWED_HOSTS の行を削除します。その後、docker compose up -d を実行してコンテナを再作成します。env ファイルを編集するだけでは反映されません。実行中のコンテナは、起動時の環境変数を保持しているためです。

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 と、小さなサムネイルを追加で保存します。テキストだけの 200 KB のスキャンは小さいままです。長い契約書をカラーでスキャンした 30 MB のファイルは、約 60 MB を使用します。同じディスクにエクスポートディレクトリも保存する場合は、その分を加算してください。アーカイブ全体ではディスク上で 3 倍になります。

Tika と Gotenberg のコンテナは必要ですか?

PDF と一緒に Word、Excel、OpenDocument ファイルもインデックス化する場合だけ必要です。これらは該当形式を PDF に変換するため、paperless で OCR 処理と検索が可能になります。ただし、実行中のコンテナが 2 つ増え、メモリも数百メガバイト使用します。保存するファイルがすべて PDF または画像であれば、小規模なサーバーでは省略してください。

この記事は役に立ちましたか?

#paperless-ngx#documents#self-hosting#docker#ocr